Skip to content
← All examples

Example 10

Parameter sweep

A small parameter sweep: the same strategy at three window lengths, all

Category
Host: needs the terminal's data and engine
Permissions
market.readbacktest.run
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

// Category: host
// Permissions: market.read, backtest.run
// A small parameter sweep: the same strategy at three window lengths, all
// submitted at once (each is its own job on TTT's engine), then compared
// on net result. Every run uses the same explicit execution costs.
//
// Grant in the T0 Lang window: market.read, backtest.run.

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 ema_cross(fast: Int, slow: Int, size: Quantity) version "1.0.0" {
    let fast_ema = features.ema(fast);
    let slow_ema = features.ema(slow);
    rule enter when cross_above(fast_ema, slow_ema) && exposure_is(Exposure.Flat)
        => order(Side.Buy, Intent.Open, OrderType.Market, Sizing.Fixed(size));
    rule exit when cross_below(fast_ema, slow_ema) && exposure_is(Exposure.Open)
        => order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(size));
    flatten_on_stop;
}

fn costs() -> backtest.Execution {
    return 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,
    };
}

fn main() -> Result<Unit, Error> {
    let ds = market.datasets("BTCUSDT")?.first().expect("no published BTCUSDT dataset on this machine");
    let slows = [21, 50, 100];
    let mut jobs: List<backtest.Job> = [];
    for slow in slows {
        let job = backtest.submit(backtest.Spec {
            strategy: ema_cross(9, slow, qty(0.01)),
            dataset: ds,
            start: None,
            end: None,
            starting_balance: money(10000, USDT),
            execution: costs(),
        })?;
        jobs.push(job);
    }
    let mut best: Option<Money> = None;
    let mut best_slow = 0;
    let mut i = 0;
    while i < jobs.len() {
        let report = backtest.wait(jobs[i])?;
        let net = report.realized_pnl;
        print("slow " + to_string(slows[i]) + ": " + to_string(report.status) + ", " + to_string(report.trades) + " trades, net " + to_string(net));
        let better = match best {
            Some(b) => net > b,
            None => true,
        };
        if better {
            best = Some(net);
            best_slow = slows[i];
        }
        i = i + 1;
    }
    print("best slow window: " + to_string(best_slow) + " (one dataset, one period: not evidence of an edge)");
    return Ok(());
}