Skip to content
All strategies

Strategies

BTC Inverse Perp 4H Donchian Breakout Long-Short (BINANCE_CM, 40-Bar Lookback, OHLCV-Only)

Outcome: Abandoned

BtcCmInverseFourHourDonchianBreakoutLS

BTC Inverse Perp 4H Donchian Breakout Long-Short (BINANCE_CM, 40-Bar Lookback, OHLCV-Only)

Hypothesis

Classical Turtle-style Donchian channel breakout long-short strategy on BTCUSD_PERP.BINANCE_CM (BTC-margined inverse perpetual) using 4-hour bars. Pure OHLCV-only mechanism with NO supplementary data dependency — completely avoids the 13+ liquidation/funding/supp-data infrastructure failures this session. Targets the BINANCE_CM venue bucket (currently 1.0% of portfolio vs 5% quota target — the deepest under-represented executable venue in the factory). Mechanism is dead-simple: 3 parameters total (lookback, ATR……Show moreShow less

Classical Turtle-style Donchian channel breakout long-short strategy on BTCUSD_PERP.BINANCE_CM (BTC-margined inverse perpetual) using 4-hour bars. Pure OHLCV-only mechanism with NO supplementary data dependency — completely avoids the 13+ liquidation/funding/supp-data infrastructure failures this session. Targets the BINANCE_CM venue bucket (currently 1.0% of portfolio vs 5% quota target — the deepest under-represented executable venue in the factory). Mechanism is dead-simple: 3 parameters total (lookback, ATR stop multiplier, max hold), classic Donchian breakout (long when close > N-bar high; short when close < N-bar low) with an ATR trailing stop and hard time stop. The 4H timeframe + 40-bar lookback (~6.7 days) produces ~40-70 entries/year based on historical BTC volatility, comfortably above the 5-trades-per-OOS-window walk-forward floor. The strategy DOES NOT include narrative-driven filters, regime gates, or volume confirmations — these multi-filter AND-gates have repeatedly produced 'walk-forward empty OOS' failures this session (13+ daily Donchian/multi-filter abandons). Instead, the strategy exposes itself fully to Donchian's natural trade frequency. COIN-M (inverse) margining adds genuine novelty: P&L is denominated in BTC, not USDT, creating a non-linear return profile that's structurally different from USD-M (long positions get BTC-margin upside leverage when BTC rises; short positions face BTC-margin compression when BTC falls). This makes the strategy a NEW mechanism-class test rather than a re-port of an existing USD-M mechanism — the cross-margining-type generalization question is intellectually distinct from cross-instrument or cross-venue generalization. Position sizing is volatility-targeted in BTC-units rather than USDT to align with the contract's settlement currency.

Iteration 3 fixes the 1-bar-hold pathology at its actual root cause, which the previous two iterations misdiagnosed. The defect was the TIME STOP, keyed on position.id. Under the backtest's NETTING OMS, NautilusTrader gives every position of a (instrument, strategy) pair the SAME PositionId string - execution/engine.pyx _determine_netting_position_id returns PositionId(f'{instrument_id}-{strategy_id}'), and _open_position re-creates the next Position with that identical id (verified in the installed v1.222……Show moreShow less

Iteration 3 fixes the 1-bar-hold pathology at its actual root cause, which the previous two iterations misdiagnosed. The defect was the TIME STOP, keyed on position.id. Under the backtest's NETTING OMS, NautilusTrader gives every position of a (instrument, strategy) pair the SAME PositionId string - execution/engine.pyx _determine_netting_position_id returns PositionId(f'{instrument_id}-{strategy_id}'), and _open_position re-creates the next Position with that identical id (verified in the installed v1.222 sources). The old guard 'if pos_id != self._pos_id: self._entry_bar = self._bar_index' therefore stamped the entry bar for the FIRST position only; every subsequent position inherited a stale _entry_bar, so 'bar_index - entry_bar >= max_hold_bars' was already true on its very first bar and the time stop closed it after one 4H bar - permanently. That is exactly why the pathology was byte-identical (572 trades, 4h28m mean hold) across two completely different exit rules and why disabling the bracket path changed nothing: neither exit rule was ever reached, the time stop fired first every time. The fix measures hold time in calendar time from the engine's own position.ts_opened against the current bar timestamp, divided by the bar interval - no cross-position state, so it cannot go stale and is restart-safe. Nothing else changed: entry rule, continuous channel-position signal, Donchian trailing exit and inverse-contract sizing are untouched, so entry frequency is unchanged (Layer 1/2/3 behaviour that already passed cannot regress) and holds should return to the ~40-50 bar regime the hypothesis describes, cutting round trips roughly 4-5x and with them the 22.6% commission drag that made avg_trade_return_pct negative. The fee-viability verdict from the last review measured the defect, not the mechanism; it can only be re-measured now.

verification_loop: Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `@staticmethod _bar_ts(bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 144) - `self._bar_ts = ...` binds a value over framework method `@staticmethod _bar_ts(bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 110)

Implementation

Turtle-style Donchian channel breakout, long and short, on the BTC-margined inverse perpetual BTCUSD_PERP.BINANCE_CM using 4-hour bars. Entry: close above the prior 40-bar high goes long, close below the prior 40-bar low goes short - no regime gate, no volume filter, no narrative overlay. Exit: the opposite 20-bar Donchian channel (long exits on a close below the prior 20-bar low, short on a close above the prior 20-bar high) plus a 90-bar hard time stop. Sizing is volatility-targeted (1.5% of equity risked over a 3xATR reference stop, capped at 75% of equity notional) and floored to whole $100-face inverse contracts via contracts_for_notional, so PnL and fees settle in BTC.

Verification Results

Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `@staticmethod _bar_ts(bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 144) - `self._bar_ts = ...` binds a value over framework method `@staticmethod _bar_ts(bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 110)

Iteration History

Verification failed (Layer 4 — QA review) [class=no_edge]: - NET LOSER — the unfiltered Donchian breakout has no viable edge on 4H BTC COIN-M and is chopped by false breakouts. Sandbox: profit_factor 0.892 (<1), avg_trade_return_pct -0.0936% (negative), total_return -13.03%, Sharpe -0.037 (CI [-0.71, 0.59]), expectancy -$17.8, over 572 trades. The tell is the ~1-bar mean hold (avg_holding 4h29m): with a wide 3xATR trail and a 90-bar time stop, a genuine trend-follower should hold many bars, so a ~1-bar hold means……Show moreShow less

Verification failed (Layer 4 — QA review) [class=no_edge]: - NET LOSER — the unfiltered Donchian breakout has no viable edge on 4H BTC COIN-M and is chopped by false breakouts. Sandbox: profit_factor 0.892 (<1), avg_trade_return_pct -0.0936% (negative), total_return -13.03%, Sharpe -0.037 (CI [-0.71, 0.59]), expectancy -$17.8, over 572 trades. The tell is the ~1-bar mean hold (avg_holding 4h29m): with a wide 3xATR trail and a 90-bar time stop, a genuine trend-follower should hold many bars, so a ~1-bar hold means most 4H BTC breakouts reverse >=3 ATR almost immediately — the false-breakout chop the hypothesis named as the family's failure mode. win_rate 0.42 with avg_win $348 ~= avg_loss $286 nets slightly negative after fees/impact ($6,619 total impact, turnover 45.6). The developer's private replay (204 trades, 36-bar hold, +1.03%/trade, Sharpe 0.68) is contradicted by the authoritative engine (572 trades, 1-bar hold, net loss) — a 10x divergence and sign flip the developer flagged and deferred to the sandbox. Removing filters exposes the mechanism to the chop; adding them back is the abandoned multi-filter path. - Worth the developer's attention (not a confirmed defect): the ~1-bar mean hold is anomalous for a 3xATR-trail 'let-winners-run' trend follower and wildly inconsistent with the developer's own 36-bar-hold replay of the same rules. I traced should_exit's chandelier trail and found it correct (peak/trough from best close, restart-safe anchor, non-degenerate ATR), so I couldn't confirm a bug — but the divergence should be understood before trusting any replay figures. Even if the trail behaves as designed, an unfiltered 4H BTC Donchian is a dead-family breakout, so this doesn't change the verdict.

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Secondary to the defect: the sandbox is net-negative (total_return -13.26%, PF 0.892, avg_trade_return_pct -0.094%, commission 22.61% of gross over 572 trades) — but that number is uninformative because the strategy is not running the mechanism it claims to. Do NOT read this as a clean refutation of the Donchian-trend family; the book never trend-followed. - The strategy does NOT exercise its stated trend-following mechanism: the……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Secondary to the defect: the sandbox is net-negative (total_return -13.26%, PF 0.892, avg_trade_return_pct -0.094%, commission 22.61% of gross over 572 trades) — but that number is uninformative because the strategy is not running the mechanism it claims to. Do NOT read this as a clean refutation of the Donchian-trend family; the book never trend-followed. - The strategy does NOT exercise its stated trend-following mechanism: the sandbox holds ~1 bar on average (avg_holding_period 4h 28m on 4H bars) with 572 trades, so winners are never let to run. Decisive evidence that the coded exit does NOT govern position lifetime: iteration-1 (a 3xATR chandelier trail) produced avg_holding_period 4h 29m and iteration-2 (this code, a completely different 20-bar opposite-Donchian-channel exit) produced 4h 28m — a full exit-logic REPLACEMENT changed the mean hold by one minute. Neither coded exit CAN fire ~1 bar after a fresh 40-bar-extreme entry: a LONG needs close < the trailing 20-bar low (or, iter-1, < entry-3xATR), both far below a 40-bar-high breakout, so a 1-bar exit needs a full-range single-bar collapse — rare, not the average. So positions are closed after ~1 bar by something INDEPENDENT of should_exit, persisting unexplained across two iterations (developer's replay says 36-bar holds vs engine's 1 bar — a 10x divergence). Until root-caused, the code does not implement the hypothesis and downstream stats are artifacts. - Independent of the defect, an unfiltered 4H BTC Donchian breakout is a high-turnover directional-OHLCV trend family the analyst has repeatedly recorded as no-edge on this venue (BTC/ETH CM Donchian, dual-EMA siblings). Even with the hold bug fixed, the per-trade edge must clear the ~0.10% CM round trip against a weak prior.

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Cannot assess the real edge yet: every performance number here is produced by the unresolved 1-bar-hold pathology (572 trades, 22.61% commission drag, avg_trade_return_pct -0.0937%), so total_return -13.3% reflects the defect, not the Turtle mechanism. Re-measure only after the actual closer is found and holds return to the ~45-bar regime the developer's offline replay shows. - THE FIX DID NOT WORK AND THE ROOT CAUSE IS MISDIAGNOSED.……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Cannot assess the real edge yet: every performance number here is produced by the unresolved 1-bar-hold pathology (572 trades, 22.61% commission drag, avg_trade_return_pct -0.0937%), so total_return -13.3% reflects the defect, not the Turtle mechanism. Re-measure only after the actual closer is found and holds return to the ~45-bar regime the developer's offline replay shows. - THE FIX DID NOT WORK AND THE ROOT CAUSE IS MISDIAGNOSED. The developer blames the bracket-order path and overrides _use_bracket_orders() to False, but THIS submission's sandbox (running the fixed code) still shows the identical pathology: total_trades=572, avg_holding_period=4h28m (one 4H bar), commission_pct_of_gross=22.61%. This falsifies the bracket theory twice: (a) the config carries NO bracket keys, so the bracket path was never active, and (b) disabling it changed nothing. Positions are still closed after ~1 bar by something other than should_exit, still unidentified. The executed behavior (1-bar holds, 572 round trips) is not the hypothesis mechanism (40-bar breakout held until the 20-bar opposite channel or 90-bar time stop, ~45-bar holds), so as-run it does not implement the hypothesis. - FEE NONVIABLE. avg_trade_return_pct is -0.0937% (negative) over 572 trades with metrics_reliable=true and commissions 22.61% of gross — the book loses money net of the CM round trip. Direct consequence of the 1-bar overtrading in issue 1 (a ~40-70/yr system charged the round trip ~100+/yr). Structural fee-death, not a smoke-test artifact.

Abandon Reason

verification_loop: Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `@staticmethod _bar_ts(bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 144) - `self._bar_ts = ...` binds a value over framework method `@staticmethod _bar_ts(bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 110)

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