본문으로 건너뛰기
← 모든 예제

예제 69

Risk sweep

how much of the account to risk on each trade, found by running instead of guessing. The same strategy and the same dataset are backtested once for each value of the

분류
Host: 터미널의 데이터와 엔진 필요
권한
market.readbacktest.run
실행하기 전에
a BTCUSDT dataset, and the terminal's risk bindings binding example 64's policy (`risk-per-trade`) to the `backtest` or `demo` account.
하는 일
runs one backtest for each value (a few seconds each on a day of data); nothing else.
실행 방법
Run in the T0 Lang window, granting market.read and backtest.run.
언어
>=0.2.0, <0.3.0
호스트
>=0.1.22, <0.2.0
필요한 것
market >=0.1.0, <0.2.0features >=0.1.0, <0.2.0backtest >=0.1.5, <0.2.0

소스

// Purpose: how much of the account to risk on each trade, found by running instead of guessing. The same strategy and the same dataset are backtested once for each value of the
// policy's `risk_per_trade` (example 64's policy, bound to the `backtest` or `demo` account in the terminal's risk bindings), and the table says what each value made, how deep it fell
// and what it ended with. `backtest.run_with_risk` gives the policy these numbers for the run: the script is the same one, pinned by the same hash, and the bindings file is not changed.
// HONEST NOTE: a table of results over one stretch of one market says what happened there, not what will; compare the Sharpe and the deepest fall, not only the profit, and look at
// how many trades stand behind each line (the Performance window shows how sure the figures are).
// Category: host
// Permissions: market.read, backtest.run
// Preconditions: a BTCUSDT dataset, and the terminal's risk bindings binding example 64's policy (`risk-per-trade`) to the `backtest` or `demo` account.
// Side effects: runs one backtest for each value (a few seconds each on a day of data); nothing else.
// Run: Run in the T0 Lang window, granting market.read and backtest.run.

lang ">=0.2.0, <0.3.0";
host "ttt" ">=0.1.22, <0.2.0";
requires "market" ">=0.1.0, <0.2.0";
requires "features" ">=0.1.0, <0.2.0";
requires "backtest" ">=0.1.5, <0.2.0";

strategy trend_sized_by_risk(window: Int, stop_pct: Decimal) version "1.0.0" {
    let px = features.price(features.Source.AggTrades);
    let average = features.sma(window);
    let stop = px * (1 - stop_pct / 100);
    rule enter when cross_above(px, average) && exposure_is(Exposure.Flat)
        => order(Side.Buy, Intent.Open, OrderType.Market, Sizing.ByRisk, Protect.StopLoss(stop));
    rule exit when cross_below(px, average) && exposure_is(Exposure.Open)
        => order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(qty(1)));
    flatten_on_stop;
}

fn configuration(share: String) -> String {
    return "{\"risk_per_trade\": \"" + share + "\", \"max_gross\": \"500000 USDT\", \"max_age\": \"5s\", \"step\": \"0.001\"}";
}

fn main() -> Result<Unit, Error> {
    let datasets = market.datasets("BTCUSDT")?;
    let ds = datasets.first().expect("no published BTCUSDT dataset on this machine");
    let spec = backtest.Spec {
        strategy: trend_sized_by_risk(20, 1.0),
        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.Fraction(bps(1)),
            latency: 50ms,
            market_fill: backtest.MarketFill.NextPrint,
            limit_fill: backtest.LimitFill.TradeThrough,
        },
    };
    print("risk per trade | trades | realized | deepest fall | final equity");
    for share in ["0.0025", "0.005", "0.01", "0.02"] {
        match backtest.run_with_risk(spec, configuration(share)) {
            Ok(report) => {
                print(share + " | " + to_string(report.trades) + " | " + to_string(report.realized_pnl) + " | " + to_string(report.max_drawdown) + " | " + to_string(report.final_equity));
            }
            Err(e) => {
                print(share + " | not run: " + e.code + " " + e.message);
            }
        }
    }
    return Ok(());
}