← All examples
Example 46
Vwap 3sd reversion
a VWAP reversion strategy, described in T0 and run by TTT's engine -- the same strategy, with the same rules, in a backtest and live on the
- 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 that holds aggregated prints and 1-minute bars (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.8, <0.2.0- Needs
- market >=0.1.1, <0.2.0features >=0.1.3, <0.2.0backtest >=0.1.1, <0.2.0
Source
// Purpose: a VWAP reversion strategy, described in T0 and run by TTT's engine -- the same strategy, with the same rules, in a backtest and live on the // demo account (it reads only what the live feed delivers: aggregated prints and closed bars). // // ENTRY a bar CLOSES beyond the third deviation of the session VWAP (a bar closed below the lower 3-sigma band buys; one closed above the // upper 3-sigma band sells) -- price stretched three deviations from where the volume traded, and bet on it coming back. // TARGET the second deviation (`target_k` = +2.0 puts it on the 2-sigma band FARTHEST from the entry, which is the one on the other side of the // VWAP; `target_k` = -2.0 puts it on the 2-sigma band nearest the entry, on the entry's own side). It is measured from the VWAP and the // spread AS THEY WERE WHEN THE ENTRY WAS DECIDED and held, so the target does not drift while the trade is open. // STOP 1:1 -- as far from the entry on the losing side as the target is on the winning one. // // It is run twice on the newest published BTCUSDT dataset, with the target on the far and on the near second deviation, and the two reports are // printed side by side: which of them the data likes is the point of a backtest. Note what the far target means: from a 3-sigma entry to the opposite // 2-sigma band is five deviations, so a 1:1 stop sits five deviations the other way -- in practice a stop that is rarely reached. // // The VWAP here is the engine's session VWAP: every aggregated print since the run began, weighted by volume, with the volume-weighted spread of the // prices around it (West's update). Rules are evaluated on every print, so the target and the stop are checked at each print, and filled by the // engine's own execution model (fees, slippage and latency in `backtest.Execution`). // Category: host // Permissions: market.read, backtest.run // Preconditions: at least one published BTCUSDT dataset on this machine that holds aggregated prints and 1-minute bars (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. // Live: the `vwap_3sd_reversion` strategy runs unchanged on a replay or the demo account -- see 47_live_vwap_reversion.tzs (every feature it reads is served live). lang ">=0.1.1, <0.2.0"; host "ttt" ">=0.1.8, <0.2.0"; requires "market" ">=0.1.1, <0.2.0"; requires "features" ">=0.1.3, <0.2.0"; requires "backtest" ">=0.1.1, <0.2.0"; strategy vwap_3sd_reversion(entry_k: Decimal, target_k: Decimal, size: Quantity) version "1.0.0" { // What the market is doing: the last print, the last closed bar, the VWAP and its spread. let px = features.price(features.Source.AggTrades); let bar = features.bar_close(); let vw = features.vwap(features.Source.AggTrades); let sd = features.vwap_spread(features.Source.AggTrades); let lower = vw - entry_k * sd; let upper = vw + entry_k * sd; // True on the one step where a new bar has closed -- so a bar is looked at once, not at every print until the next one. let new_bar = bar != prev(bar); // The open trade, as it was decided: which side, at what price, and where the VWAP and its spread were then. let held = features.position_sign(); let entry = features.entry_price(); let entry_vw = features.entry_vwap(features.Source.AggTrades); let entry_sd = features.entry_spread(features.Source.AggTrades); let long_target = entry_vw + target_k * entry_sd; let long_stop = entry - (long_target - entry); let short_target = entry_vw - target_k * entry_sd; let short_stop = entry + (entry - short_target); rule long_target_hit when exposure_is(Exposure.Open) && held > 0 && px >= long_target => order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(size)); rule long_stop_hit when exposure_is(Exposure.Open) && held > 0 && px <= long_stop => order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(size)); rule short_target_hit when exposure_is(Exposure.Open) && held < 0 && px <= short_target => order(Side.Buy, Intent.Close, OrderType.Market, Sizing.Fixed(size)); rule short_stop_hit when exposure_is(Exposure.Open) && held < 0 && px >= short_stop => order(Side.Buy, Intent.Close, OrderType.Market, Sizing.Fixed(size)); rule enter_long when exposure_is(Exposure.Flat) && new_bar && bar < lower => order(Side.Buy, Intent.Open, OrderType.Market, Sizing.Fixed(size)); rule enter_short when exposure_is(Exposure.Flat) && new_bar && bar > upper => order(Side.Sell, Intent.Open, OrderType.Market, Sizing.Fixed(size)); flatten_on_stop; } fn run(ds: market.Dataset, label: String, target_k: Decimal) -> Result<Unit, Error> { let job = backtest.submit(backtest.Spec { strategy: vwap_3sd_reversion(3.0, target_k, 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(label + ": " + 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, "target on the far 2-sigma band", 2.0)?; run(ds, "target on the near 2-sigma band", -2.0)?; return Ok(()); }