BtcDailyPriceVolumeDivergenceReversalLongShort
Hypotheses
BTC Single-Asset Price-Volume Divergence Reversal Long-Short on BINANCE Daily (Wyckoff Effort-vs-Result, Bearish-Higher-High-Lower-Volume / Bullish-Lower-Low-Lower-Volume)
Hypotheses
Single-asset price-volume divergence reversal strategy on BTCUSDT.BINANCE — detects 'effort vs result' divergences where price extends to a new extreme BUT volume is LOWER than at the previous extreme, signaling weakening participation. Mechanism: (1) BULLISH DIVERGENCE TRIGGER: current bar makes a new 20-bar low (bar[0].low < min(low[-20:-1])) AND volume[0] < volume at the prior 20-bar low × (1 - divergence_threshold) (default 30% lower volume) — selling pressure is mechanically weaker despite price reaching a fresh low, indicating capitulation exhaustion. (2) BEARISH DIVERGENCE TRIGGER: current bar makes a new 20-bar high AND volume[0] < volume at the prior 20-bar high × (1 - divergence_threshold) — buying interest weakening despite a new high, indicating distribution. (3) FLAT otherwise. This implements Richard Wyckoff's 1931 'Trading and Investments in Securities' effort-vs-result principle (foundational to all volume-spread analysis), with quantitative thresholds from Martin Pring 1985 'Technical Analysis Explained' and statistical validation in Bulkowski 2008 (~62% follow-through for genuine volume divergences). Distinct from my 10 in-pipeline event-driven BTC patterns: Climax (range + close), Engulfing (body engulfing), Key Reversal (3-bar sequence), Pin Bar (wick), Volume Surge (absolute high-volume + close), Macro Regime (slow MA), Vol Compression (multi-bar contraction), Harami (containment), Outside Bar (range engulfing), Tweezer (matched extremes). Price-Volume Divergence uses RELATIVE VOLUME between two specific events — a unique signal topology compared to all prior patterns. Volume Surge fires when volume is ABSOLUTELY high; Divergence fires when volume is RELATIVELY LOW at a meaningful price event. The mechanism is FUNDAMENTALLY EVENT-DRIVEN AND TRANSLATION-ROBUST. Single-asset scope = no multi-asset translation curse. Helps direction quota (90.4% long-only).
Hypotheses
Iteration 4 fixes ONLY the Layer-2 frozen-signal failure with the smallest change; entry/exit/sizing logic is unchanged. Root cause: the synthetic scenarios use constant volume, so in steady up/down trends every bar makes a fresh extreme, the divergence branch computed deficit = 1 - volume/vol_at_extreme = 0, and the code RETURNED the sub-threshold value ±deficit = 0.0 on all 490 bars (frozen). Additionally the fallback proxy hard-clamped to ±0.9, which saturates to a constant in trends. Fix: (1) the two extreme branches now RETURN only when the divergence is CONFIRMED (deficit >= divergence_threshold); non-confirmed extremes fall through. (2) The fallback proxy is rebuilt from the close's range-position PLUS a non-saturating soft-sigmoid x/(1+|x|) of ROC, so it stays distinct every bar even in a perfectly steady trend (ROC decreases monotonically as price grows) and in flat-ranging/zero-volume/gap scenarios, while |proxy| <= 0.9 < entry_threshold keeps non-event bars from ever entering. No new imports (soft-sigmoid is inline arithmetic), so Layer 1 stays green. Venue remains BINANCE USD-M futures (long-short), leverage 1.0 wired into sizing.
Hypotheses
Negative-expectancy single-name OHLCV price-volume divergence fade — no edge to optimize. profit_factor 0.65, total_return -25.99%, Sharpe -0.38, expectancy -$362.78/trade, and avg_trade_return_pct -1.35% (NEGATIVE, well below any fee floor). The Wyckoff effort-vs-result fade is systematically on the wrong side: it loses in 6 of 7 years and both long and short legs have PF<1. Result is also fragile (skew -6.7, kurtosis 178.4 — one -12% day on 2020-03-12 dominates). This is the zero-survivor single-name mean-reversion/fade class (L55); the sign of the per-trade edge is negative, which no parameter tuning can flip. Not worth 2 hours of optimization. Failure pattern: fee_edge/no_edge net-loser fade.
Implementation
Single-asset BTCUSDT.BINANCE daily, OHLCV-only, long-short Wyckoff effort-vs-result price-volume divergence reversal. Fires LONG when a bar makes a new 20-bar low on volume >=30% below the volume at the prior 20-bar low (capitulation exhaustion), and SHORT when a bar makes a new 20-bar high on volume >=30% below the volume at the prior 20-bar high (distribution). calculate_signal returns +(1+deficit)/-(1+deficit) on confirmed divergences (>= entry_threshold) and a continuous, strictly sub-threshold, always-varying proxy otherwise. Exits on 10% take-profit, 5% hard stop, or 20-bar time stop; risk-based sizing off the hard-stop distance.
Verification Results
CLEAN RESTART 2026-09-04 — this run's verdict history and learning records were removed and it was restarted from verification. Its previous abandonment came from the pipeline, not from the market: the Layer-2 harness mis-bound @staticmethod helpers (fixed), QA issued terminal performance verdicts on an unoptimized smoke test (removed — QA now judges correctness only), and sandbox timeouts came from backtest-slot starvation (fixed). The hypothesis and the strategy code are unchanged. Verify the code on its merits; performance is decided later by the full backtest and the optimizer.
Verification Results
Consider a stop_market() bracket at entry if the analyst wants the stated 5% risk honored intra-bar.
Verification Results
The 5% hard-stop and 10% take-profit are evaluated only on daily bar close in should_exit(), not intra-bar. On a 1-DAY timeframe a large adverse gap can blow past the 5% stop before the close is seen, so realized stop losses can exceed the stated 5% risk budget. The hypothesis does not mandate an intra-bar bracket, so this is advisory, not blocking.
Backtest Review
Well-implemented, correctly instrumented (63 entries over 2437 days, no data/warmup issues, funding available)
Backtest Review
Clean signal topology distinct from other patterns
Backtest Review
Net loser: total_return -25.99%, Sharpe -0.38 (CI straddles into deeply negative, sharpe_ci_low -0.95), Sortino -0.30
Backtest Review
profit_factor 0.65 and expectancy -$362.78/trade — the fade is systematically on the wrong side of the move
Backtest Review
avg_trade_return_pct -1.35% is NEGATIVE — no fee floor to clear, the raw edge sign is wrong
Backtest Review
Losing in 6 of 7 years (2020 -17.4%, 2021 -6.0%, 2023 -2.0%, 2024 -2.5%, 2025 -2.9%); only 2022 meaningfully positive
Backtest Review
information_ratio -0.80, negative alpha (-0.027); return_skew -6.7, kurtosis 178 — result dominated by one -12% day (2020-03-12), i.e. fragile fat left tail
Backtest Review
Single-name OHLCV mean-reversion/fade — zero-survivor class (L55)
Iteration History
SolFourHourBullFlagBreakoutContinuationLong
Iteration History
Verification failed (Layer 2 — synthetic scenarios):
Parameters used: ['tp_cap', 'flag_max', 'flag_min', 'risk_pct', 'tp_floor', 'min_notional', 'min_stop_pct', 'hard_stop_pct', 'max_hold_bars', 'per_trade_pct', 'pole_min_bars', 'pole_min_move', 'flag_drift_tol', 'flag_range_ratio']
Check that __init__ sets all attributes from self.parameters.get().
- steady_uptrend: Frozen signal: all 490 signals are identical (value=0.0). calculate_signal() likely has a bug — the signal never varies.
- steady_downtrend: Frozen signal: all 490 signals are identical (value=0.0). calculate_signal() likely has a bug — the signal never varies.
- flat_ranging: Frozen signal: all 490 signals are identical (value=0.0). calculate_signal() likely has a bug — the signal never varies.
- volatility_spike: Frozen signal: all 490 signals are identical (value=0.0). calculate_signal() likely has a bug — the signal never varies.
- zero_volume: Frozen signal: all 490 signals are identical (value=0.0). calculate_signal() likely has a bug — the signal never varies.
- price_gap: Frozen signal: all 490 signals are identical (value=0.0). calculate_signal() likely has a bug — the signal never varies.
Iteration History
Verification failed (Layer 2 — synthetic scenarios):
Parameters used: ['risk_pct', 'min_notional', 'hard_stop_pct', 'max_hold_bars', 'per_trade_pct', 'entry_threshold', 'take_profit_pct', 'divergence_lookback', 'divergence_threshold']
Check that __init__ sets all attributes from self.parameters.get().
- steady_uptrend: TypeError: BtcDailyPriceVolumeDivergenceReversalLongShort._soft() takes 1 positional argument but 2 were given (bar timestamp: 1735690860000)
- steady_downtrend: TypeError: BtcDailyPriceVolumeDivergenceReversalLongShort._soft() takes 1 positional argument but 2 were given (bar timestamp: 1735690860000)
- flat_ranging: TypeError: BtcDailyPriceVolumeDivergenceReversalLongShort._soft() takes 1 positional argument but 2 were given (bar timestamp: 1735690860000)
- volatility_spike: TypeError: BtcDailyPriceVolumeDivergenceReversalLongShort._soft() takes 1 positional argument but 2 were given (bar timestamp: 1735690860000)
- zero_volume: TypeError: BtcDailyPriceVolumeDivergenceReversalLongShort._soft() takes 1 positional argument but 2 were given (bar timestamp: 1735690860000)
- price_gap: TypeError: BtcDailyPriceVolumeDivergenceReversalLongShort._soft() takes 1 positional argument but 2 were given (bar timestamp: 1735690860000)
Backtest and paper results are hypothetical. Trading involves risk of loss.