Thesis
The language Chartix indicators and bots are written in. Small, statically typed and deterministic: the same script on the same bars gives the same result, bit for bit, everywhere it runs.
Why Thesis
Deterministic
No clock, no randomness, no I/O. A backtest you run today reproduces exactly tomorrow and on any other host.
Small
A construct earns its place by being needed for indicators or trading. No classes, no closures, no recursion.
Declarative
A typical script is a few assignments and one plot or one trade.*. Series are implicit and history is an operator.
Typed, invisibly
Every expression's type is known at compile time, and you rarely write one. A type error is a compile error with a position.
Fast and flat
Each bar is a walk over an instruction array with no allocation: registers and series buffers are assigned at compile time.
Explicit failure
A script that cannot do what it says fails with a position and a hint. A missing value is na, never a silent zero.
How a script runs
A script runs once per bar, oldest bar first. Within a bar it runs top to bottom, exactly once. An indicator records every
plot of every bar; a bot calls trade.* and the order is executed at the moment the call is reached.
thesis build turns sources into a package holding a compiled program, and the interpreter executes that program bar by bar.
An indicator
Where the price sits inside its own range, with a band and a breakout mark.
#thesis 1
// Channel.thesis
indicator("Channel position", label="CP", precision=1)
length = input.int(20, "Channel length", min=2, max=200, step=1)
source = input.source(close, "Source")
upper = ta.highest(source, length)
lower = ta.lowest(source, length)
span = upper - lower
position_in_range = span == 0 ? 50.0 : (source - lower) * 100 / span
plot(position_in_range, "Position", color=color.blue, width=2)
hline(80, "High", color=color.red, style=dashed)
hline(20, "Low", color=color.green, style=dashed)
marker(ta.crossover(position_in_range, 80), "Breakout", shape=triangle_up, position=above, color=color.red)
A bot
Two averages, one position at a time, protection as a distance, and a refused order that the log records rather than hides.
#thesis 1
// Crossover.thesis
bot("Average crossover", qty_type=units, qty_value=1, warmup=200)
fast_length = input.int(20, "Fast length", min=2, max=100, step=1)
slow_length = input.int(50, "Slow length", min=5, max=400, step=5)
stop_distance = input.float(0.0040, "Stop distance", min=0.0005, max=0.0200, step=0.0005)
fast = ta.sma(close, fast_length)
slow = ta.sma(close, slow_length)
turns_up = ta.crossover(fast, slow)
turns_down = ta.crossunder(fast, slow)
if turns_up and not position.is_long
trade.close_all()
accepted = trade.buy(1, sl_dist=stop_distance, tp_dist=stop_distance * 2)
if not accepted
log.warn("buy refused: " + trade.error)
if turns_down and position.is_long
trade.close_all()