Skip to content

View translation

BtcSpotDualTimeframeMomentumConfluenceLong

Hypotheses

BTC Spot Dual-Timeframe Momentum Confluence, LONG-ONLY 1x (BTCUSDT.BINANCE_SPOT, 4H Primary + 1D Confirm, Long Only When BOTH Timeframes Point UP, FLAT to Cash Otherwise, ATR-Trailing + Daily-Regime-Break Exit, Source-Optimised Parameters Retained)

Hypotheses

A LONG-ONLY, 1x, CASH-account port of the factory's best-performing promoted strategy (BtcDualTimeframeMomentumConfluenceLS, Sharpe 1.99, 458 trades, +$4,774 over 29 days of paper) onto BTCUSDT.BINANCE_SPOT, created specifically as the FIRST LIVE-TRADING CANDIDATE because the live venue is spot-only. Mechanism unchanged, pure-OHLCV, single-instrument: FAST leg = sign of the trailing 18-bar return on 4-HOUR bars; SLOW leg = sign of the trailing 15-bar return on DAILY bars (read via on_extra_bar as a context leg, never a sync barrier). Both must AGREE. The only structural change the CASH account forces is collapsing {LONG/FLAT/SHORT} into {LONG/FLAT}: where the futures parent goes SHORT on joint-down confluence, the spot version holds 100% USDT. Everything else is preserved verbatim: ATR(14) trailing stop at 3.033x ATR from the running extreme, daily-regime-break exit, vol-scaled capital-relative sizing (risk_pct 1.011% / 3.033xATR stop distance), 49.7% max-notional cap, and the exact parameter vector the source optimisation selected (fast_lookback 18, slow_lookback 15, trail_atr_mult 3.033). Three tunables, no supplementary data, no shorts, no leverage. Spot-specific code changes: BINANCE_SPOT CASH venue (no leverage multiplier in position_size), size_precision 5 / price_precision 2, min_notional $10, sells sized to held base quantity only. Data verified present: BTCUSDT.BINANCE_SPOT 4-HOUR and 1-DAY bars run continuously from 2017-08-17 (~9 years, ~19,800 4H bars).

Hypotheses

Direct port of the promoted futures parent BtcDualTimeframeMomentumConfluenceLS onto the spot venue, because the intended live venue is spot-only. Mechanism preserved verbatim: the same two momentum legs, the same AGREE-or-flat gate, the same ATR(14) 3.033x trailing stop from the running extreme, the same daily-regime-break exit, the same vol-scaled risk_pct / stop-distance sizing with a 49.7% notional cap, and the exact parameter vector the source optimisation selected (fast_lookback 18, slow_lookback 15, trail_atr_mult 3.033). The CASH account forces exactly one structural change, as the hypothesis specifies: {LONG,FLAT,SHORT} collapses to {LONG,FLAT} — should_enter returns BUY only on joint-up and None otherwise, so joint-down is 100% USDT rather than a short. Spot-specific edits: venue BINANCE_SPOT with leverage 1.0 and no leverage term in position_size, size_precision 5 / price_precision 2 (BTCUSDT spot), min_notional raised to $10, and all exits routed through the base template's reduce-only close_position so a SELL can only ever shed held base quantity (a naked spot short is impossible by construction, and the base's _would_open_spot_short guard is never tripped). calculate_signal returns the continuous 18-bar 4H return every bar (it varies bar to bar); the discrete confluence gate lives in should_enter, as required. Per-bar cost is O(1): the ATR uses a fixed 15-bar window and the fast return is a single indexed lookup, so there is no history-scaling work. One honest caveat for the Analyst: spot round-trip cost is ~0.20% versus ~0.10% on the futures parent, so the per-trade edge must clear double the friction — the trend-following exit structure (trail + regime break, no fixed take-profit) is what makes the average hold long enough for that to be plausible, but it is the metric to watch first.

Implementation

LONG-ONLY 1x BTCUSDT spot (BINANCE_SPOT, CASH) dual-timeframe momentum confluence. FAST leg = sign of the trailing 18-bar return on 4-HOUR bars (primary); SLOW leg = sign of the trailing 15-bar return on 1-DAY bars, consumed via on_extra_bar as a slower-timeframe context leg (different timeframe, so the base template's cross-leg alignment barrier never defers the 4H signal). Both legs must point UP to hold BTC; joint-down (where the futures parent shorts) and disagreement both mean hold 100% USDT. Exits: ATR(14) trailing stop at 3.033x ATR below the position's running peak, OR the daily leg flipping down under the long (higher-timeframe regime break). Sizing is vol-scaled and capital-relative: equity * 1.011% / (3.033 * ATR), capped at 49.7% of the USDT balance in notional, with NO leverage multiplier (CASH account cannot borrow), quantized to size_precision 5 and rejected below the $10 spot min notional. Three tunables, pure OHLCV, no supplementary data, no shorts.

Verification Results

Either use self._atr (current) in should_exit's trail comparison to match the parent exactly, or state explicitly in the description that the trail is anchored to entry-bar ATR so the analyst does not attribute divergence from the parent to the venue change.

Verification Results

The trailing-stop ATR is FROZEN at entry (self._entry_atr, captured in should_enter / on position adoption) and never refreshed, so the trail distance is 3.033 x ATR-at-entry rather than 3.033 x current ATR(14). The promoted futures parent (BtcDualTimeframeMomentumConfluenceLS4H) ratchets its trail with the CURRENT ATR each bar. The hypothesis's wording ('ATR(14) trailing stop at 3.033x ATR from the running extreme') is satisfied either way, and since _extreme is monotone the stop level still only ratchets up, so this is not a correctness defect — but on a vol expansion after entry the effective stop is tighter than the parent's, which is a real behavioural difference from the strategy this is claimed to port 'verbatim'.

Verification Results

Set the trade state from the fill (on_order_filled / first bar with an open position) rather than inside should_enter, or reset it defensively at the top of calculate_signal before the position lookup.

Verification Results

should_enter() mutates trade state (_in_long = True, _extreme, _entry_atr) BEFORE the base template has sized the order and applied the min-notional gate. If position_size() returns 0 or the $10 min-notional check drops the entry, the strategy is left flagged as in-long with an armed trail while flat. In practice this self-heals on the next bar (calculate_signal sees position is None and resets the flags, and should_exit is never reached while flat), so there is no observable behavioural bug — but the invariant is fragile if the exit path is ever changed.

Verification Results

No fix required for correctness. If exact same-bar consumption is wanted, recompute _slow_dir lazily from self._extra_bars[...] inside calculate_signal instead of caching it in on_extra_bar.

Verification Results

One-bar latency on the daily leg: the DAILY bar and the last 4H bar of the day share the same close timestamp. If the engine delivers the primary 4H bar first, _maybe_process_primary() runs and stamps _last_processed_ts, and the daily bar's _slow_dir update (arriving at the same ts) is only consumed from the NEXT 4H bar. This is conservative (it can only delay a signal, never leak future data), but it means the daily-regime-break exit can fire up to 4 hours after the daily close in about half the cases, and backtest/paper parity depends on the delivery order being stable.

Verification Results

Faithful implementation, but three things the analyst should weigh on full history: (1) spot round-trip is ~0.20% vs the futures parent's ~0.10%, so the same gross edge nets roughly half — the sandbox already shows commission at 10.03% of gross; (2) the parameter vector (fast 18 / slow 15 / trail 3.033) was selected by an optimisation run on a DIFFERENT venue, timeframe mix and direction set (LONG/SHORT futures), so this is an out-of-sample transfer, not an optimised config — the sandbox Sharpe is 0.527 with sharpe_ci_low -0.233 and information_ratio -0.760 vs the benchmark, i.e. a long-only BTC book that underperforms holding BTC on the smoke window; (3) dropping the short leg removes roughly half the parent's trade population (250 trades, all long, 43.6% exposure), so realised Sharpe should be expected well below the parent's 1.99 rather than close to it. First thing to check on the real backtest: avg_trade_return_pct against the 0.25% spot floor (sandbox shows 0.82%, but that window excludes 2017 and is only 2401 days) and whether the 'normal' vol tercile (-8.77% in sandbox) stays negative over full history.

Backtest Review

314 closed trades over 3146 days (2017-08 → 2026-03), metrics_reliable=true, entry_diag 314/314 entries submitted with 0 dropped.

Backtest Review

Trades implement the hypothesis: all 314 LONG / 0 short (correct CASH collapse of the parent's LONG/FLAT/SHORT), max notional/equity 0.50 vs the 0.497 cap, avg hold 4d8h and 43.6% exposure reproducing the futures parent.

Backtest Review

avg_trade_return_pct 1.20% vs a ~0.20% BINANCE_SPOT round trip; commissions only 8.97% of gross, impact 4.33% — not fee-marginal.

Backtest Review

max_drawdown 10.3% (under the 15% spot default), PF 1.41, payoff 2.07 on a 40.4% hit rate (correct trend shape), stressed vol tercile +28.0%.

Backtest Review

Extreme concentration: top 10 of 314 trades = 93.8% of summed per-trade return, top 20 = 141% (trades 21–314 net negative).

Backtest Review

Only 6/10 calendar years positive — exactly at the hard floor — and both recent years negative (2025 −10.5%, 2026 −9.9%).

Backtest Review

Sharpe 0.664 with sharpe_ci_low 0.0042 and PSR 0.924 (<0.95): already marginal before the ~225-trial selection bar.

Backtest Review

QA's concern partly confirmed: 'normal' vol tercile stays negative (−7.70%, 16.1% DD), information_ratio −0.757, alpha only +2.4%/yr — it underperforms holding BTC, and benchmark_meaningful=true so that shortfall is real.

Backtest Review

Parameters are an out-of-sample transfer from another venue/direction set — untuned rather than overfit, but also untested here.
Strategy report

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