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()

Where to go next