Appearance
Personalize your experience
Multi-Horizon Momentum
Admin's ChoiceFreeTime-series momentum voted across twelve horizons
Live engine preview
The QuaScript engine's own output on the chart the terminal uses. Change the settings or edit the code and press Run to recompute it.
About
Time-series momentum voted across twelve horizons. Each horizon votes with the sign of its volatility-scaled return; the position follows the vote only when a clear majority agrees, and goes flat when the horizons disagree. Using many horizons avoids betting on one lookback. Entry: net votes at or beyond the majority in either direction. Exit: the vote falls back below the hold level; a protective ATR stop on every entry; one entry per vote episode. Risk: 95 % of equity while invested, 30 % drawdown halt. Method: time-series momentum (Moskowitz, Ooi and Pedersen, 2012).
Settings
Shortest horizon: 12
Horizon step: 12
Majority (of 12): 8
Hold level: 4
Protective stop (ATR): 5
Measured backtests
Run by the backtest engine on public candles, 2025-11-01 to 2026-11-01, with the script's own commission and slippage, on BTCUSDT 1h (Binance) and ETHUSDT 1h (Binance). The figures per market are in the Measured backtest panel below. Past results do not predict future returns.
Backtest it yourself
Open it on the backtesting page to run it on any symbol, interval and date range, then use Monte Carlo, optimisation, walk-forward and robustness tests.
Strategy Rules
Entry Conditions
- ▸ net votes at or beyond the majority in either direction
Exit Conditions
- ▸ the vote falls back below the hold level; a protective ATR stop on every entry; one entry per vote episode
Measured backtest
BTCUSDT 1h (Binance), 2025-11-01 to 2026-11-01 · backtest engine, script costs included
-18.1%
Return
-0.69
Sharpe
-30.1%
Max DD
23.3%
Win Rate
86
Trades
| Market | Trades | Return | Sharpe | Max DD | Win rate | Profit factor |
|---|---|---|---|---|---|---|
| BTCUSDT 1h | 86 | -18.1% | -0.69 | -30.1% | 23.3% | 0.76 |
| ETHUSDT 1h | 70 | -21.5% | -0.58 | -30.2% | 21.4% | 0.74 |
Past results on historical data do not predict future returns.
Source Code
Edit in IDE| 1 | strategy "Multi-Horizon Momentum" { |
| 2 | overlay = true |
| 3 | capital = 100000 |
| 4 | commission = 0.05 |
| 5 | slippage = 1 |
| 6 | sizing = percent_equity |
| 7 | size = 95 |
| 8 | pyramiding = 1 |
| 9 | } |
| 10 | /// Time-series momentum voted across twelve horizons. Each horizon votes with the sign of its |
| 11 | /// volatility-scaled return; the position follows the vote only when a clear majority agrees, and |
| 12 | /// goes flat when the horizons disagree. Using many horizons avoids betting on one lookback. |
| 13 | /// Entry: net votes at or beyond the majority in either direction. Exit: the vote falls back below |
| 14 | /// the hold level; a protective ATR stop on every entry; one entry per vote episode. |
| 15 | /// Risk: 95 % of equity while invested, 30 % drawdown halt. |
| 16 | /// Method: time-series momentum (Moskowitz, Ooi and Pedersen, 2012). |
| 17 | use "viz/premium" as pr |
| 18 | h_min = param.int(12, title = "Shortest horizon", min = 2, max = 200, group = "Vote") |
| 19 | h_step = param.int(12, title = "Horizon step", min = 1, max = 200, group = "Vote") |
| 20 | enter = param.int(8, title = "Majority (of 12)", min = 6, max = 12, group = "Vote") |
| 21 | keep = param.int(4, title = "Hold level", min = 0, max = 11, group = "Vote") |
| 22 | stop_k = param.float(5, title = "Protective stop (ATR)", min = 1, max = 20, step = 0.5, group = "Risk") |
| 23 | sd = math.max(stat.stdev(math.log(close / close[1]), 100), 1e-12) |
| 24 | net = 0 |
| 25 | for k in 0..=11 { |
| 26 | hz = h_min + k * h_step |
| 27 | z = math.log(close / close[hz]) / (sd * math.sqrt(hz)) |
| 28 | if z > 0.25 { net := net + 1 } |
| 29 | if z < -0.25 { net := net - 1 } |
| 30 | } |
| 31 | a = ta.atr(50) |
| 32 | vote = net >= enter ? 1 : net <= -enter ? -1 : 0 |
| 33 | persist used = 0 |
| 34 | if vote != vote[1] { used := 0 } |
| 35 | if vote == 1 and position.side <= 0 and used != 1 { |
| 36 | order.market("L", side.long) |
| 37 | exit.bracket("L exit", entry = "L", stop = close - stop_k * a) |
| 38 | used := 1 |
| 39 | } |
| 40 | if vote == -1 and position.side >= 0 and used != -1 { |
| 41 | order.market("S", side.short) |
| 42 | exit.bracket("S exit", entry = "S", stop = close + stop_k * a) |
| 43 | used := -1 |
| 44 | } |
| 45 | if (position.side > 0 and net < keep) or (position.side < 0 and net > -keep) { exit.close_all() } |
| 46 | risk.max_drawdown(30) |
| 47 | render.line(ta.ema(close, h_min + 6 * h_step), "Momentum mean", color = net > 0 ? color.emerald.300 : net < 0 ? color.rose.300 : color.slate.500, width = 2) |
| 48 | render.background(vote == 1 ? color.translucent(color.emerald.500, 93) : vote == -1 ? color.translucent(color.rose.500, 93) : na) |
| 49 | persist t = draw.table.create(anchor.top_right, 4, 2) |
| 50 | if bar.is_last { |
| 51 | pr.heading(t, 2, "Multi-Horizon Momentum", "Strategy · 12-horizon vote") |
| 52 | pr.row(t, 2, "Net votes", str.format("{:+d}", net), net > 0 ? pr.BULL : net < 0 ? pr.BEAR : pr.LABEL) |
| 53 | pr.row(t, 3, "Stance", vote > 0 ? "Long" : vote < 0 ? "Short" : "Flat", color.white) |
| 54 | } |
| 55 |
Reviews
No reviews yet. Be the first to review this listing.