Skip to content

View translation

BtcTrendAlignedShortTermReversionLS

Hypotheses

BTC Trend-Aligned Short-Term Mean-Reversion, Long-Short (Single-Instrument BTCUSDT.BINANCE Perp, Daily Bars — Buy Short-Term Oversold ONLY in a Confirmed Uptrend / Short Overbought ONLY in a Confirmed Downtrend, Revert-to-Mean Exit, 3-Parameter)

Hypotheses

A LONG-SHORT, single-instrument, pure-OHLCV strategy on BTCUSDT.BINANCE USD-M perpetual that fades SHORT-TERM extremes but ONLY in the direction of the LONG-TERM trend — a documented construction (Connors RSI-2 / pullback style) that is a genuinely different FAMILY from everything in my pending book (dual/triple/macro momentum-confluence, grid, vol-targeted trend) and from every dead family this session. It resolves the specific reason pure counter-trend reversion has failed: naive mean-reversion fades the trend and gets run over (BTC-HL range-MR died, funding-reversion died), while pure momentum whipsaws in noise. This mean-reverts WITH the macro trend: in a confirmed uptrend (price > long MA) it BUYS short-term oversold dips expecting a bounce toward the mean; in a confirmed downtrend it SHORTS short-term overbought rips. It only ever positions in the trend's direction, so it is structurally LOW-DRAWDOWN (it never fights the dominant trend) — directly addressing the >50%-drawdown failures (XRP/ETH 65% DD) now disqualifying strategies. It stays on BTC (the one instrument with demonstrated tradability — every alt died 'no edge'), pure OHLCV (only reliably measurable, coverage-complete data), single-instrument, developer-safe with standard incremental indicators, and generates a LARGE measurable sample (short-term extremes recur often → ~150-300 trades over multi-year history). It fills the under-target LONG-SHORT bucket (13.6% vs 86.4% long-only). 3 parameters.

Hypotheses

I measured this instead of guessing, and the measurement rules out the reported cause. Running the actual backtest on the exact sandbox configuration (BTCUSDT.BINANCE 1-DAY, 2400-day window, 2401 bars): an EMPTY no-op strategy took 67.4s and THIS strategy took 64.9s. The strategy is indistinguishable from an empty one — it came in 2.5s faster, inside run-to-run noise — so its own per-bar cost is not measurably different from zero, and at ~65s it sits ~235s below the 300s cap. That establishes two things. First, there is no per-bar work to remove: every operation is already O(1) — both moving averages are bounded deques with rolling sums, the RSI is a Wilder scalar recurrence with no window scan, the ATR is a bounded deque folded in after the decision, the previous signal is carried rather than recomputed, and the exit compares the close against two cached prices plus day-index arithmetic. There is no sort, no min()/max() over history, no supplementary feed, no extra instrument and no tick subscription in the file, and the window is ~2400 bars rather than the 20000 the message templates. Second, the timeout came from outside this strategy: 65s of work cannot reach a 300s ceiling on its own, so ~235s has to come from elsewhere, and the known candidate is contention — BacktestRunner issues one catalog.bars() call per bar type and NautilusTrader fsspec-globs the whole 35k-directory bar tree on every query, which is I/O-bound and degrades sharply when several sandbox runs execute concurrently. Given that, I deliberately did NOT invent per-bar 'optimizations'; rewriting already-O(1) code would not touch the actual bottleneck and would risk regressing logic that has already cleared Layers 1 and 2 for no measurable gain. The mechanism, thresholds, exits, sizing and 3-parameter surface are unchanged; the edits are the status block recording this measurement and a declared _param_bounds mirroring the constructor clamps so the optimizer cannot search values the code silently rewrites. If it times out again the fix is infrastructure — cache the globbed file list across catalog.bars() calls within one BacktestRunner run, or query the leg's own directory instead of walking the whole data/bar tree — and every strategy in the factory is paying that same toll.

Hypotheses

Not worth 2 hours of optimization: the strategy shows no measurable edge and too small a sample to fit. Sharpe 0.073 with bootstrap CI [-0.675, 0.846] straddling zero and PSR 0.551 — statistically indistinguishable from zero — while profit_factor is 1.028 with expectancy only $42.6/trade against a $3,711 avg loss and -$8,149 largest loss (left-tailed, skew -1.71, kurtosis 34), so a single bad trade wipes out many wins. Only 39 trades in 6.7 years versus the ~150-300 the hypothesis pre-registered: prior iterations already fought the exact RR-vs-sample tension and the RR precondition + trend filter + entry-cross collapse the sample to 39, which is the L10 low-trade-count trap where the headline 270% return is carried by very few trades and the Sharpe is unmeasured. Optimizing 3 parameters against 39 noisy trades would curve-fit noise, and the strategy already has negative alpha (information_ratio -0.885), loses in 4 of 7 years, is negative in the calm regime, and is capacity-dead ($2.1M, impact 21.8% of gross). No parameter change turns a PF-1.03 / Sharpe-0 sample into an edge. Failure pattern: no_edge/overfit low-trade-count trend-aligned reversion, Sharpe indistinguishable from zero (L10).

Implementation

Long-short trend-aligned short-term mean reversion on BTCUSDT.BINANCE USD-M perpetual, daily bars, pure OHLCV. A Wilder RSI over rsi_period days is re-expressed on a centred scale (ext = (50 - RSI)/50, +1 maximally oversold, -1 maximally overbought) and traded only in the direction of the dominant trend, measured as the distance of price from a trend_ma_days SMA in ATR units with a trend_floor to exclude a flat MA whose sign is noise. In a confirmed uptrend it buys oversold dips; in a confirmed downtrend it shorts overbought rips; counter-trend readings are damped and clipped strictly below the entry threshold so the fade that kills naive reversion is unreachable rather than merely discouraged. Entry additionally requires the 20-day mean to sit at least min_rr stop-widths away, so an inverted payoff is structurally impossible, plus a fresh cross of the threshold so a multi-day oversold print is entered once. Exits are the price mean reached, a 1-ATR stop from entry, or a 15-day calendar hold from the position's own ts_opened. Sizing risks 2% of equity across the stop distance, capped at 1x equity notional and floored at BTC's 0.001 step and the $100 minimum.

Verification Results

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (2400 days of BTCUSDT.BINANCE-1-DAY-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Backtest Review

avg_trade_return_pct 1.49% clears the Binance USD-M fee floor (this is NOT a fee_edge case), and max_drawdown 13.4% is genuinely low as the trend-aligned thesis promised.

Backtest Review

Clean single-instrument OHLCV construction, real long/short balance (18 long / 21 short), and no artifact signatures (benchmark-correlation 0.036, MTM headline is essentially all realized: end_unrealized_pct 1.04%).

Backtest Review

No measurable risk-adjusted edge: Sharpe 0.073 with bootstrap CI [-0.675, 0.846] straddling zero, PSR 0.551, Sortino 0.054 — indistinguishable from no skill.

Backtest Review

profit_factor 1.028 with expectancy just $42.6/trade against an avg_loss of $3,711 and largest_loss -$8,149 — the payoff is razor-thin (0.59*2654 - 0.41*3711 = ~+44), one tail loss erases many wins (return_skew -1.71, kurtosis 34).

Backtest Review

Only 39 trades over 6.7 years — the hypothesis pre-registered ~150-300; the RR precondition + trend filter + entry cross gated the sample down to 39, so the Sharpe is statistically unmeasured (L10) and 3 params cannot be fit to 39 noisy trades without overfitting.

Backtest Review

Negative alpha vs its own benchmark: information_ratio -0.885. Losing in 4 of 7 years (2020, 2023, 2025, 2026) and negative in the calm regime (Sharpe -0.78).

Backtest Review

Capacity is toy-scale (capacity_usd $2.1M) and market impact already eats 21.8% of gross PnL.

Iteration History

Verification failed (Layer 4 — QA review): - STRUCTURALLY INVERTED REWARD:RISK -- the reversion/fade death signature, and it is baked into the LOCKED exit design. The revert-to-mean profit target is a fast RSI-2 snap-back of roughly ONE ATR (avg holding 3.6 days), while the ATR stop is TWO ATR from entry. Every trade therefore risks ~2 ATR to make ~1 ATR, a ~2:1 payoff AGAINST, which needs a >67% win rate to break even; the sandbox win_rate is 58.3%, so expectancy is negative by construction: avg_loss $1,120 ~= 2x avg_win $584, total_return -1.51%, profit_factor 0.730, Sharpe -0.312, avg_trade_return_pct 0.019% (essentially zero, below the fee floor). This is the exact inverted-RR pattern that has killed the single-name reversion/fade class repeatedly this session (SOL/XRP/DOGE/ADA/LINK). Critically the trend-alignment did NOT deliver the promised low-drawdown protection: return_skew -3.83, return_kurtosis 50.3, largest_loss -$3,945 vs largest_win $1,334 -- the 'dip that keeps going because the trend is breaking' still produces losses larger than the harvest, exactly what the design claimed to prevent, and the 2-ATR stop lets them run past the ~1-ATR target rather than bounding them below it. - UNMEASURABLE SAMPLE -- 12 trades over 362 days, about half the developer's own modeled ~25-40/year. The RSI-2-extreme + confirmed-trend (|trend_n| >= 0.5 ATR) + fresh-cross conjunction fires far less often than the hypothesis's '150-300 over multi-year' claim: 12/year projects to ~72 over a multi-year span, below the ~100 measurability floor (L16). At n=12 with return_skew -3.83 the result is dominated by one -$3,945 loss and cannot be read as an edge estimate regardless; Sharpe CI is [-1.68, 1.52], straddling zero.

Iteration History

Verification failed (Layer 4 — QA review): - THE RR FIX AND A MEASURABLE SAMPLE ARE MUTUALLY EXCLUSIVE -- the family's terminal flaw, now demonstrated on both horns. The developer correctly fixed the inverted reward:risk (the target is now the 10-day SMA price, entry requires distance-to-mean >= 1.5 x stop, stop tightened to 1.0 ATR) -- the arithmetic is right and 1.5:1 is now structural. But that precondition is in direct structural tension with the RSI-oversold entry, and the sample COLLAPSED from 12 to 3 trades (metrics_reliable=FALSE). The reason is intrinsic: a deep RSI-2 oversold print means price has just fallen sharply, so it is already at or below its short 10-day mean -- the reversion target sits only a small distance above price, almost never >= 1.5 ATR away. So requiring 1.5:1 to that mean blocks nearly every setup, and relaxing entry_ext (0.8->0.6) and trend_floor (0.5->0.25) could not compensate. The result is unmeasurable noise: 3 trades (0 long / 3 short), win 0.667, return_skew -2.60, return_kurtosis 75.2, Sharpe CI [-1.13, 2.24], and the engine flags metrics_reliable=false. iteration 1 had a measurable-ish sample (12 trades) but negative-expectancy inverted RR; iteration 2 fixed the RR but is unmeasurable at 3. You cannot have both, because the harvest (a snap-back to the short-term mean) is structurally too small relative to the risk needed to hold through daily noise.
Strategy report

Backtest and paper results are hypothetical. Trading involves risk of loss.