Skip to content
← All examples

Example 59

Handler with context

a handler that reads what the strategy holds. `handler bar(5) = on_bar(ctx, px, average)` gives the function a `handlers.Context` -- the open positions

Category
Host: needs the terminal's data and engine
Permissions
market.readbacktest.run
Before you run it
at least one published BTCUSDT dataset on this machine (Datasets window)
What it does
starts a backtest job (it appears in the Jobs panel); writes its run under the terminal's runs folder.
How to run it
grant market.read and backtest.run in the T0 Lang window, Run. Put on a replay or the live market, the same handlers run there.
Language
>=0.2.0, <0.3.0
Host
>=0.1.15, <0.2.0
Needs
market >=0.1.1, <0.2.0features >=0.1.0, <0.2.0handlers >=0.1.1, <0.2.0backtest >=0.1.1, <0.2.0

Source

// Purpose: a handler that reads what the strategy holds. `handler bar(5) = on_bar(ctx, px, average)` gives the function a `handlers.Context` -- the open positions
// of every book, the orders still working, the equity -- as of the bar that closed, so it keeps no copy of that in its state. The state it does keep is a `Map`
// from a price level (to the dollar, written as text) to how many bars closed there; `for (level, bars) in memory.visits` walks it in price order.
//
//   BUY   when flat, with nothing working, price over its average, and the level it is at has been visited before (the market came back to it).
//   SELL  when it holds a long and price falls under the average.
//
// What a handler cannot do is look ahead: it sees the signals and the context as of the event that closed the bar, and only when every signal is known.
// HONEST NOTE: written to show the shape; its edge has not been measured.
// Category: host
// Permissions: market.read, backtest.run
// Preconditions: at least one published BTCUSDT dataset on this machine (Datasets window)
// Side effects: starts a backtest job (it appears in the Jobs panel); writes its run under the terminal's runs folder.
// Run: grant market.read and backtest.run in the T0 Lang window, Run. Put on a replay or the live market, the same handlers run there.

lang ">=0.2.0, <0.3.0";
host "ttt" ">=0.1.15, <0.2.0";
requires "market" ">=0.1.1, <0.2.0";
requires "features" ">=0.1.0, <0.2.0";
requires "handlers" ">=0.1.1, <0.2.0";
requires "backtest" ">=0.1.1, <0.2.0";

struct Memory {
    visits: Map<String, Int>,
}

struct Step {
    state: Memory,
    actions: List<handlers.Act>,
}

fn begin() -> Memory {
    return Memory { visits: Map.new() };
}

fn order(side: handlers.Side, intent: handlers.Intent, size: Quantity) -> handlers.Act {
    return handlers.Act.Submit(handlers.Order { book: "main", side: side, intent: intent, qty: size, limit: None, stop: None, take_profit: None, stop_loss: None, tag: 0, post_only: false, immediate: false, expire_minutes: 0 });
}

fn level_of(px: Float) -> String {
    return to_string(decimal_from_float(px, 0));
}

fn most_visited(visits: Map<String, Int>) -> Int {
    let mut best = 0;
    for (level, bars) in visits {
        if bars > best {
            best = bars;
        }
    }
    return best;
}

fn on_bar(memory: Memory, ctx: handlers.Context, px: Float, average: Float) -> Step {
    let level = level_of(px);
    let seen = match memory.visits.get(level) {
        Some(bars) => bars,
        None => 0,
    };
    let mut visits = memory.visits;
    visits.insert(level, seen + 1);
    let next = Memory { visits: visits };
    if ctx.positions.len() == 0 && ctx.orders.len() == 0 && px > average && seen > 0 && seen < most_visited(memory.visits) + 1 {
        return Step { state: next, actions: [order(handlers.Side.Buy, handlers.Intent.Open, qty(0.1))] };
    }
    if ctx.positions.len() > 0 && px < average {
        return Step { state: next, actions: [order(handlers.Side.Sell, handlers.Intent.Close, qty(0.1))] };
    }
    return Step { state: next, actions: [] };
}

strategy returning_to_levels(period: Int) version "1.0.0" {
    let px = features.price(features.Source.AggTrades);
    let average = features.sma(period);
    state = begin();
    handler bar(5) = on_bar(ctx, px, average);
}

fn main() -> Result<Unit, Error> {
    let ds = market.datasets("BTCUSDT")?.first().expect("no published BTCUSDT dataset on this machine");
    let report = backtest.run(backtest.Spec {
        strategy: returning_to_levels(3),
        dataset: ds,
        start: None,
        end: None,
        starting_balance: money(100000, USDT),
        execution: backtest.Execution {
            maker_fee: bps(2),
            taker_fee: bps(5),
            slippage: backtest.Slippage.None,
            latency: 0ms,
            market_fill: backtest.MarketFill.NextPrint,
            limit_fill: backtest.LimitFill.TradeThrough,
        },
    })?;
    print(report.status);
    print(report.trades);
    print(report.final_equity);
    return Ok(());
}