Appearance
Personalize your experience
OU Half-Life Reversion
Admin's ChoiceFreeFades z-score stretches only when the half-life is short
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
Trades mean reversion only when the market is measurably mean-reverting. An Ornstein-Uhlenbeck fit gives the fair value, the z-score of price against it and the half-life of deviations; a stretched z-score is faded only while the half-life is short enough to pay within a few bars, and the trade is given twice the half-life to work. Entry: z beyond the entry level and turning back, half-life below the limit. Exit: target at the fitted mean, stop beyond the stretch, or a time stop at twice the half-life. Risk: 1 % per trade. Method: Uhlenbeck and Ornstein (1930), AR(1) half-life.
Settings
Fit window: 120
Entry z-score: 2
Maximum half-life (bars): 30
Stop (ATR): 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
- ▸ z beyond the entry level and turning back, half-life below the limit
Exit Conditions
- ▸ target at the fitted mean, stop beyond the stretch, or a time stop at twice the half-life
Measured backtest
BTCUSDT 1h (Binance), 2025-11-01 to 2026-11-01 · backtest engine, script costs included
-4.0%
Return
-0.60
Sharpe
-7.8%
Max DD
48.0%
Win Rate
50
Trades
| Market | Trades | Return | Sharpe | Max DD | Win rate | Profit factor |
|---|---|---|---|---|---|---|
| BTCUSDT 1h | 50 | -4.0% | -0.60 | -7.8% | 48.0% | 0.81 |
| ETHUSDT 1h | 46 | 6.2% | 1.11 | -3.6% | 56.5% | 1.40 |
Past results on historical data do not predict future returns.
Source Code
Edit in IDE| 1 | strategy "OU Half-Life Reversion" { |
| 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 | /// Trades mean reversion only when the market is measurably mean-reverting. An Ornstein-Uhlenbeck |
| 12 | /// fit gives the fair value, the z-score of price against it and the half-life of deviations; |
| 13 | /// a stretched z-score is faded only while the half-life is short enough to pay within a few |
| 14 | /// bars, and the trade is given twice the half-life to work. |
| 15 | /// Entry: z beyond the entry level and turning back, half-life below the limit. Exit: target at |
| 16 | /// the fitted mean, stop beyond the stretch, or a time stop at twice the half-life. |
| 17 | /// Risk: 1 % per trade. Method: Uhlenbeck and Ornstein (1930), AR(1) half-life. |
| 18 | use "viz/premium" as pr |
| 19 | len = param.int(120, title = "Fit window", min = 30, max = 2000, group = "Model") |
| 20 | z_in = param.float(2.0, title = "Entry z-score", min = 0.5, max = 4, step = 0.1, group = "Entry") |
| 21 | hl_max = param.float(30, title = "Maximum half-life (bars)", min = 2, max = 500, group = "Entry") |
| 22 | stop_k = param.float(2.5, title = "Stop (ATR)", min = 0.5, max = 8, step = 0.1, group = "Risk") |
| 23 | (m, hl, z) = hft.ou(close, len) |
| 24 | a = ta.atr(20) |
| 25 | ok = hl < hl_max and hl > 0 |
| 26 | flat = position.size == 0 |
| 27 | if flat and ok and z[1] < -z_in and z > z[1] { |
| 28 | order.market("L", side.long) |
| 29 | exit.bracket("L exit", entry = "L", stop = close - stop_k * a, target = m) |
| 30 | } |
| 31 | if flat and ok and z[1] > z_in and z < z[1] { |
| 32 | order.market("S", side.short) |
| 33 | exit.bracket("S exit", entry = "S", stop = close + stop_k * a, target = m) |
| 34 | } |
| 35 | persist time_limit = 0 |
| 36 | if flat { time_limit := math.round(math.max(2 * math.coalesce(hl, 10), 3)) } |
| 37 | if position.size != 0 and position.bars_held >= time_limit { exit.close_all() } |
| 38 | risk.max_drawdown(25) |
| 39 | render.line(m, "OU mean", color = ok ? color.emerald.300 : color.slate.500, width = 2) |
| 40 | render.mark(flat and ok and z[1] < -z_in and z > z[1], shape = shape.triangle_up, at = anchor.below_bar, color = color.emerald.300, text = "Revert", title = "Long setup") |
| 41 | render.mark(flat and ok and z[1] > z_in and z < z[1], shape = shape.triangle_down, at = anchor.above_bar, color = color.rose.300, text = "Revert", title = "Short setup") |
| 42 | persist t = draw.table.create(anchor.top_right, 5, 2) |
| 43 | if bar.is_last { |
| 44 | pr.heading(t, 2, "OU Half-Life Reversion", "Strategy · Ornstein-Uhlenbeck") |
| 45 | pr.row(t, 2, "Half-life", math.is_na(hl) ? "none" : str.format("{:.1f} bars", hl), ok ? pr.BULL : pr.LABEL) |
| 46 | pr.row(t, 3, "Z-score", str.format("{:+.2f}", z), math.abs(z) > z_in ? pr.WARN : color.white) |
| 47 | pr.row(t, 4, "Fair value", str.format("{:.2f}", m), color.white) |
| 48 | } |
| 49 |
Reviews
No reviews yet. Be the first to review this listing.