Appearance
Personalize your experience
Momentum Term Pullback
Admin's ChoiceFreeLong-horizon trend, short-horizon dip, entry on the turn
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
Buys dips that the long horizons have not noticed. Momentum is measured on a short and a long horizon band as volatility-scaled t-statistics (return over the horizon divided by the volatility expected over it). When the long band is strongly positive while the short band has gone negative, price is pulling back inside an established trend; the entry waits until the short band turns back up, so the pullback is already ending. Shorts mirror this. Entry: long-band t above `long_t`, short-band t below `-dip_t` within the last `memory` bars and now rising for two bars (mirror for shorts). Exit: ATR stop under the pullback low, target `reward` R, or the long band losing its sign. Risk: 1 % per trade.
Settings
Short band from: 3
Short band to: 12
Long band from: 60
Long band to: 160
Volatility window: 200
Trend strength (t): 1
Pullback depth (t): 0.6
Pullback memory (bars): 8
Target (R): 2.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
- ▸ long-band t above `long_t`, short-band t below `-dip_t` within the last `memory` bars and now rising for two bars (mirror for shorts)
Exit Conditions
- ▸ ATR stop under the pullback low, target `reward` R, or the long band losing its sign
Measured backtest
BTCUSDT 1h (Binance), 2025-11-01 to 2026-11-01 · backtest engine, script costs included
4.6%
Return
0.52
Sharpe
-7.9%
Max DD
36.4%
Win Rate
44
Trades
| Market | Trades | Return | Sharpe | Max DD | Win rate | Profit factor |
|---|---|---|---|---|---|---|
| BTCUSDT 1h | 44 | 4.6% | 0.52 | -7.9% | 36.4% | 1.16 |
| ETHUSDT 1h | 32 | 10.2% | 1.18 | -7.4% | 40.6% | 1.48 |
Past results on historical data do not predict future returns.
Source Code
Edit in IDE| 1 | strategy "Momentum Term Pullback" { |
| 2 | overlay = true |
| 3 | capital = 100000 |
| 4 | commission = 0.05 |
| 5 | slippage = 1 |
| 6 | sizing = risk_percent |
| 7 | max_leverage = 1 |
| 8 | size = 1 |
| 9 | pyramiding = 1 |
| 10 | } |
| 11 | /// Buys dips that the long horizons have not noticed. Momentum is measured on a short and a long |
| 12 | /// horizon band as volatility-scaled t-statistics (return over the horizon divided by the |
| 13 | /// volatility expected over it). When the long band is strongly positive while the short band has |
| 14 | /// gone negative, price is pulling back inside an established trend; the entry waits until the |
| 15 | /// short band turns back up, so the pullback is already ending. Shorts mirror this. |
| 16 | /// Entry: long-band t above `long_t`, short-band t below `-dip_t` within the last `memory` bars and |
| 17 | /// now rising for two bars (mirror for shorts). Exit: ATR stop under the pullback low, target |
| 18 | /// `reward` R, or the long band losing its sign. Risk: 1 % per trade. |
| 19 | use "viz/premium" as pr |
| 20 | s_lo = param.int(3, title = "Short band from", min = 1, max = 50, group = "Horizons") |
| 21 | s_hi = param.int(12, title = "Short band to", min = 2, max = 100, group = "Horizons") |
| 22 | l_lo = param.int(60, title = "Long band from", min = 10, max = 500, group = "Horizons") |
| 23 | l_hi = param.int(160, title = "Long band to", min = 20, max = 1000, group = "Horizons") |
| 24 | vol_n = param.int(200, title = "Volatility window", min = 20, max = 5000, group = "Horizons") |
| 25 | long_t = param.float(1.0, title = "Trend strength (t)", min = 0.2, max = 4, step = 0.1, group = "Signals") |
| 26 | dip_t = param.float(0.6, title = "Pullback depth (t)", min = 0.1, max = 4, step = 0.1, group = "Signals") |
| 27 | memory = param.int(8, title = "Pullback memory (bars)", min = 1, max = 100, group = "Signals") |
| 28 | reward = param.float(2.5, title = "Target (R)", min = 0.5, max = 10, step = 0.1, group = "Risk") |
| 29 | r1 = math.log(close / close[1]) |
| 30 | sig = stat.stdev(r1, vol_n) |
| 31 | /// Average t-statistic of the horizons lo .. hi (four evenly spaced samples). |
| 32 | fn band_t(lo: int, hi: int, sig: float) -> float { |
| 33 | acc = 0.0 |
| 34 | for j in 0..4 { |
| 35 | h = math.max(1, math.round(lo + (hi - lo) * j / 3)) |
| 36 | acc := acc + math.log(close / close[h]) / math.max(sig * math.sqrt(h), 1e-12) |
| 37 | } |
| 38 | return acc / 4 |
| 39 | } |
| 40 | st = band_t(s_lo, s_hi, sig) |
| 41 | lt = band_t(l_lo, l_hi, sig) |
| 42 | a = ta.atr(14) |
| 43 | dipped = st < -dip_t |
| 44 | popped = st > dip_t |
| 45 | since_dip = ta.bars_since(dipped) |
| 46 | since_pop = ta.bars_since(popped) |
| 47 | turn_up = st > st[1] and st[1] > st[2] |
| 48 | turn_dn = st < st[1] and st[1] < st[2] |
| 49 | go_l = lt > long_t and math.coalesce(since_dip, 1000000) <= memory and turn_up and st < 0.5 |
| 50 | go_s = lt < -long_t and math.coalesce(since_pop, 1000000) <= memory and turn_dn and st > -0.5 |
| 51 | lo_n = ta.lowest(low, memory + 2) |
| 52 | hi_n = ta.highest(high, memory + 2) |
| 53 | persist used = 0 |
| 54 | if dipped { used := used == 1 ? 0 : used } |
| 55 | if popped { used := used == -1 ? 0 : used } |
| 56 | flat = position.size == 0 |
| 57 | if flat and go_l and used != 1 { |
| 58 | stop = math.min(lo_n - 0.25 * a, close - a) |
| 59 | order.market("L", side.long) |
| 60 | exit.bracket("L exit", entry = "L", stop = stop, target = close + (close - stop) * reward) |
| 61 | used := 1 |
| 62 | } |
| 63 | if flat and go_s and used != -1 { |
| 64 | stop = math.max(hi_n + 0.25 * a, close + a) |
| 65 | order.market("S", side.short) |
| 66 | exit.bracket("S exit", entry = "S", stop = stop, target = close - (stop - close) * reward) |
| 67 | used := -1 |
| 68 | } |
| 69 | if position.size > 0 and lt < 0 { exit.close("L") } |
| 70 | if position.size < 0 and lt > 0 { exit.close("S") } |
| 71 | risk.max_drawdown(25) |
| 72 | render.candle_color(lt > long_t ? (st < 0 ? color.teal.300 : na) : lt < -long_t ? (st > 0 ? color.orange.300 : na) : na) |
| 73 | render.mark(go_l and flat and used[1] != 1, shape = shape.triangle_up, at = anchor.below_bar, color = color.emerald.300, text = "Dip", title = "Long pullback entry") |
| 74 | render.mark(go_s and flat and used[1] != -1, shape = shape.triangle_down, at = anchor.above_bar, color = color.rose.300, text = "Pop", title = "Short pullback entry") |
| 75 | persist t = draw.table.create(anchor.top_right, 5, 2) |
| 76 | if bar.is_last { |
| 77 | pr.heading(t, 2, "Momentum Term Pullback", "Strategy · short vs long momentum") |
| 78 | pr.row(t, 2, "Long band (t)", str.format("{:+.2f}", lt), lt > 0 ? pr.BULL : pr.BEAR) |
| 79 | pr.row(t, 3, "Short band (t)", str.format("{:+.2f}", st), st > 0 ? pr.BULL : pr.BEAR) |
| 80 | pr.row(t, 4, "Setup", lt > long_t and st < 0 ? "Pullback in uptrend" : lt < -long_t and st > 0 ? "Bounce in downtrend" : "None", pr.WARN) |
| 81 | } |
| 82 |
Reviews
No reviews yet. Be the first to review this listing.