Skip to content
← All examples

Example 45

Orderflow strategy

an order-flow strategy, described in T0 and run by TTT's engine. It buys when sellers were absorbed -- heavy one-sided selling that did not

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 two backtest jobs (they appear in the Jobs panel); writes their runs under the terminal's runs folder.
How to run it
grant market.read and backtest.run in the T0 Lang window, Run. "Stop job" stops only the backtest; "Stop program" stops both.
Language
>=0.1.1, <0.2.0
Host
>=0.1.1, <0.2.0
Needs
market >=0.1.1, <0.2.0features >=0.1.0, <0.2.0backtest >=0.1.1, <0.2.0

Source

// Purpose: an order-flow strategy, described in T0 and run by TTT's engine. It buys when sellers were absorbed -- heavy one-sided selling that did not
// move price (`inferred_absorption_sell_side`: INFERRED from the tape, never observed) -- while price is under the session VWAP, and sells when price
// crosses back up through the VWAP. It is run twice on the newest published BTCUSDT dataset, with a loose and a strict notion of "heavy", and the two
// reports are printed side by side: the point of a backtest is the comparison, and a rule that only works at one threshold is a curve fit.
// `min_volume` is in units of the instrument, like an order's size: 1.0 and 5.0 below are one and five bitcoin sold into the bar.
// Category: host
// Permissions: market.read, backtest.run
// Preconditions: at least one published BTCUSDT dataset on this machine (Datasets window)
// Side effects: starts two backtest jobs (they appear in the Jobs panel); writes their runs under the terminal's runs folder.
// Run: grant market.read and backtest.run in the T0 Lang window, Run. "Stop job" stops only the backtest; "Stop program" stops both.

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

strategy vwap_absorption(min_volume: Decimal, imbalance_bps: Decimal, size: Quantity) version "1.0.0" {
    let px = features.price(features.Source.AggTrades);
    let vw = features.vwap(features.Source.AggTrades);
    let absorbed = features.inferred_absorption_sell_side(features.Source.AggTrades, min_volume, imbalance_bps);
    rule enter when absorbed && px < vw && exposure_is(Exposure.Flat)
        => order(Side.Buy, Intent.Open, OrderType.Market, Sizing.Fixed(size));
    rule exit when cross_above(px, vw) && exposure_is(Exposure.Open)
        => order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(size));
    flatten_on_stop;
}

fn run(ds: market.Dataset, min_volume: Decimal, imbalance_bps: Decimal) -> Result<Unit, Error> {
    let job = backtest.submit(backtest.Spec {
        strategy: vwap_absorption(min_volume, imbalance_bps, qty(0.01)),
        dataset: ds,
        start: None,
        end: None,
        starting_balance: money(10000, USDT),
        execution: backtest.Execution {
            maker_fee: bps(2),
            taker_fee: bps(5),
            slippage: backtest.Slippage.Fraction(bps(1)),
            latency: 50ms,
            market_fill: backtest.MarketFill.NextPrint,
            limit_fill: backtest.LimitFill.TradeThrough,
        },
    })?;
    let report = backtest.wait(job)?;
    print("heavy from " + to_string(min_volume) + " at " + to_string(imbalance_bps) + " bps: " + to_string(report.status) + ", " + to_string(report.trades) + " trades, realized " + to_string(report.realized_pnl) + ", fees " + to_string(report.fees_paid));
    return Ok(());
}

fn main() -> Result<Unit, Error> {
    let ds = market.datasets("BTCUSDT")?.first().expect("no published BTCUSDT dataset on this machine");
    run(ds, 1.0, 6000.0)?;
    run(ds, 5.0, 8000.0)?;
    return Ok(());
}