Skip to content
← All examples

Example 52

Flow supertrend kalman

a trend strategy on the order-flow Supertrend. It enters when the fifteen-minute Supertrend made of what traded turns

Category
Host: needs the terminal's data and engine
Permissions
orders.submit
Before you run it
a chart in hand on the live market or a replay, whose instrument the live feed serves (aggregated prints and closed
What it does
asks the terminal to run the strategy on that market; once running it places orders on the account the person chose.
How to run it
Add script on the Market Replay window or the title bar's script icon (replay or real time); Insert strategy in the Datasets
Language
>=0.1.2, <0.2.0
Host
>=0.1.11, <0.2.0
Needs
features >=0.1.6, <0.2.0trade >=0.1.0, <0.2.0

Source

// Purpose: a trend strategy on the order-flow Supertrend. It enters when the fifteen-minute Supertrend made of what traded turns
// (`flow_supertrend_side`: hung from each bar's VWAP, as wide as the core of its volume, turning only with the aggressors behind the
// close), and only in the direction of a Kalman trend on the hour -- and only when the market is calm enough that the stop beyond
// the last swing is not a wide one. The target is `reward` times the risk (1:2 to 1:3), and once the trade is a risk in favour its
// stop trails `trail_atr` fifteen-minute ranges behind the last closed minute.
//
//   SETUP    the 15-minute flow Supertrend turns: up for a long, down for a short.
//   FILTER   the 60-minute Kalman trend slopes the same way (`kalman_slope`);
//            the 15-minute range is not among the widest it has been (`volatility_rank` <= `max_rank`, 0 the calmest of the last
//            `vol_lookback` bars, 100 the wildest);
//            the stop is not wide: at most `max_risk_atr` 15-minute ranges from the price.
//   STOP     past the last confirmed 15-minute swing low (for a long; high for a short), `pad_atr` ranges beyond it.
//   TARGET   `reward` times the risk, set with the stop as one bracket.
//   TRAIL    from 1 R in favour, on each closed minute, the stop moves to `trail_atr` ranges behind the close -- never back.
//   EXIT     also when the Supertrend turns against the trade.
//
// HONEST NOTE: written to the specification and checked that it compiles and hands over a strategy; its edge has not been measured.
// Backtest it on the Datasets window over several months, with fees, and on a stretch it was not tuned on, before trusting it.
// Category: host
// Permissions: orders.submit
// Preconditions: a chart in hand on the live market or a replay, whose instrument the live feed serves (aggregated prints and closed
// bars); or a dataset holding aggTrades and 1-minute klines (Datasets window).
// Side effects: asks the terminal to run the strategy on that market; once running it places orders on the account the person chose.
// Run: Add script on the Market Replay window or the title bar's script icon (replay or real time); Insert strategy in the Datasets
// window (backtest).

lang ">=0.1.2, <0.2.0";
host "ttt" ">=0.1.11, <0.2.0";
requires "features" ">=0.1.6, <0.2.0";
requires "trade" ">=0.1.0, <0.2.0";

strategy flow_supertrend_kalman(
    st_minutes: Int,
    st_period: Int,
    st_factor: Decimal,
    trend_minutes: Int,
    smoothness: Int,
    atr_period: Int,
    vol_lookback: Int,
    max_rank: Decimal,
    max_risk_atr: Decimal,
    swing_strength: Int,
    pad_atr: Decimal,
    reward: Decimal,
    trail_atr: Decimal,
    size: Quantity
) version "1.0.0" {
    let px = features.price(features.Source.AggTrades);
    // True on the one step where a minute has closed: the trail moves once a minute, not on every print.
    let close = features.bar_close();
    let new_bar = close != prev(close);

    // The setup: the flow Supertrend on the lower timeframe turns. Its side changes when a 15-minute bar closes, so a turn is true
    // for one step.
    let side = features.flow_supertrend_side(st_minutes, st_period, st_factor);
    let turned_up = side > 0 && prev(side) < 0;
    let turned_down = side < 0 && prev(side) > 0;

    // The trend it has to agree with: a Kalman trend of the closes on the higher timeframe.
    let slope = features.kalman_slope(trend_minutes, smoothness);

    // How wide the market is: the 15-minute range, and where it sits among its recent values.
    let atr = features.atr(st_minutes, atr_period);
    let rank = features.volatility_rank(st_minutes, atr_period, vol_lookback);

    // The stops: past the last confirmed swing, a pad of the range beyond it -- and the risk each makes.
    let long_stop = features.swing_low(st_minutes, swing_strength) - pad_atr * atr;
    let short_stop = features.swing_high(st_minutes, swing_strength) + pad_atr * atr;
    let long_risk = px - long_stop;
    let short_risk = short_stop - px;

    // Calm enough, and a stop that is not a wide one.
    let calm = rank <= max_rank;
    let long_fits = calm && long_risk > 0 && long_risk <= max_risk_atr * atr;
    let short_fits = calm && short_risk > 0 && short_risk <= max_risk_atr * atr;

    // The open trade: its side, its price, and the risk it was taken with -- held from the entry, so that it is still the same
    // after the stop that defined it has been moved.
    let held = features.position_sign();
    let entry = features.entry_price();
    let risk_long = at_entry(long_risk);
    let risk_short = at_entry(short_risk);
    let long_trail = close - trail_atr * atr;
    let short_trail = close + trail_atr * atr;

    // The trail: from one risk in favour, the stop follows the close, never back.
    rule trail_long when exposure_is(Exposure.Open) && held > 0 && new_bar && px >= entry + risk_long && long_trail > features.stop_price()
        => protect(Protect.StopLoss(long_trail));
    rule trail_short when exposure_is(Exposure.Open) && held < 0 && new_bar && px <= entry - risk_short && short_trail < features.stop_price()
        => protect(Protect.StopLoss(short_trail));

    // The trend turned against the trade: out.
    rule long_out when exposure_is(Exposure.Open) && held > 0 && turned_down
        => order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(size));
    rule short_out when exposure_is(Exposure.Open) && held < 0 && turned_up
        => order(Side.Buy, Intent.Close, OrderType.Market, Sizing.Fixed(size));

    // The entries: a turn with the trend, in a market calm enough, the stop past the swing, the target `reward` risks away.
    rule go_long when exposure_is(Exposure.Flat) && turned_up && slope > 0 && long_fits
        => order(Side.Buy, Intent.Open, OrderType.Market, Sizing.Fixed(size), Protect.Bracket(px + reward * long_risk, long_stop));
    rule go_short when exposure_is(Exposure.Flat) && turned_down && slope < 0 && short_fits
        => order(Side.Sell, Intent.Open, OrderType.Market, Sizing.Fixed(size), Protect.Bracket(px - reward * short_risk, short_stop));
    flatten_on_stop;
}

fn main() -> Result<Unit, Error> {
    // The 15-minute flow Supertrend (10 bars, 3 ranges); the hour's Kalman trend (smoothness 10); 14-bar ranges ranked over the last
    // day (96 bars), trading only under the 80th percentile, with a stop no wider than 2.5 ranges; swings of 3 bars a side, the stop
    // a fifth of a range past it; the target 2.5 risks away (between 1:2 and 1:3); the trail 2 ranges behind once 1 R in favour.
    let run = trade.run(flow_supertrend_kalman(15, 10, 3.0, 60, 10, 14, 96, 80.0, 2.5, 3, 0.2, 2.5, 2.0, qty(0.01)))?;
    print(run.name + " on " + run.target + ": " + run.state);
    return Ok(());
}