Skip to content

View original

BtcFourHourTakerRatioExtremeMeanReversionLongShort

Hypotheses

BTC Taker Buy/Sell Ratio Extreme Mean Reversion Long-Short (BINANCE USD-M Futures, 4H, Single Instrument, Taker Ratio Driven)

Hypotheses

Single-instrument long-short contrarian strategy on BTCUSDT.BINANCE that trades the taker buy/sell ratio's mean-reversion tendency at extremes. The taker buy ratio = (aggressive market-buy volume) / (total market-buy + market-sell volume) measured per bar; values > 0.50 indicate aggressive buyers dominate, < 0.50 indicate aggressive sellers dominate. Persistent extreme readings (> 0.55 or < 0.45 sustained over 6 four-hour bars = 24h) historically mark sentiment exhaustion: peak FOMO buying precedes local tops, peak panic selling precedes local bottoms. Strategy fades the extreme: SHORT BTC when 24h average taker buy ratio > 0.55, LONG BTC when 24h average < 0.45. Deliberately chosen for MAXIMUM DATA SAFETY after observing that my two prior cross-venue / multi-bar-type hypotheses (EthSpotPerpFundingCarry and SolFundingExtremeCounter) BOTH failed at Layer 3 with `data_unavailable: refill pending` for bar types the developer added beyond my original specification. This hypothesis uses ONE instrument (BTCUSDT.BINANCE — the most heavily trafficked symbol in the catalog), ONE bar type (4-HOUR — proven by landed BtcFourHourVolumeBreakoutLong), and ONE supplementary stream (taker_ratio). Fills three portfolio gaps: (1) long-short direction (currently 9.8% vs ≤55% long-only ceiling — the most violated quota), (2) the taker_ratio supplementary data stream (zero strategies use this currently across the entire 941-experiment portfolio), (3) a mechanism class (sentiment/positioning contrarian via aggressor flow) that none of my four prior accepted hypotheses (funding carry, funding extreme, sharp move volume reversal, ETH/BTC pairs) used. ~50-70 entry cycles per year × 6 years = 300-420 trades, comfortably above the 100-trade walk-forward floor. Simple parameter surface (3-4 tunable values) reduces overfit risk that killed AVAX volume breakout.

Hypotheses

Implements the hypothesis exactly: ONE instrument (BTCUSDT.BINANCE, most liquid symbol), ONE bar type (4-HOUR, proven landable), ONE supplementary stream (taker_ratio) for maximum data safety. Fills the three targeted portfolio gaps — long-short direction, first use of the taker_ratio stream, and the sentiment/positioning contrarian mechanism class. Futures venue is required because the edge trades both directions (shorts). iter-6 change addresses the reported Layer-2 failure `TypeError: argument of type 'strategy' is not iterable`: the sole `in` membership operator (`k in r` in module-level `_row_get`) is removed and replaced with an isinstance-guarded `r.get(k)`, so no code path can invoke `in` on a non-iterable object. The helpers stay module-level (immune to the Layer-2 proxy's MethodType attribute rebinding), and all signal/entry/exit/sizing logic is byte-for-byte unchanged from iter-5, so Layer 1 (static) stays green and no previously passing behavior regresses. Verified locally: Layer 1 static passes and all 6 Layer-2 synthetic scenarios pass.

Hypotheses

Untestable on available data, not disproven. The taker_ratio supplementary stream appears to span only ~26 days (all daily returns confined to 2026-04-10..2026-05-06; annual_returns has a single 2026 key; data_days=13; funding_events_available=26), while the backtest ran 14,286 4H bars over 6.5 years. The strategy correctly stayed FLAT for the ~99.5% of bars with no taker data, so it produced 16 trades against a hypothesized 300-420 — a sample that cannot support the 3-phase optimization (3 walk-forward windows would see ~5 trades each and return empty/degenerate OOS folds). The 16 trades that did fire are net-negative (expectancy -$139.10, Sharpe -2.196, PF 0.741) but that is a coin-flip sample, NOT evidence against the mechanism. Not 'iterate': the blocker is data-history depth, which the developer cannot fix in strategy code — sending it back would thrash. Abandoning to stop the loop, with two findings the Research Lead and Data Engineer should record. (1) DATA CONSTRAINT: the `taker_ratio` stream carries only recent history (the Binance /futures/data/takerlongshortRatio endpoint is a rolling-window endpoint, ~30 days), so it CANNOT back a multi-year backtest. Recommend verifying the actual row-span of the taker_ratio table before any further hypothesis specifies it, and either (a) backfilling it from aggTrades (which Binance Vision does carry historically, and from which buy/sell aggressor volume is directly derivable), or (b) restricting taker_ratio strategies to short-horizon studies. The portfolio-gap argument that 'zero strategies use taker_ratio across 941 experiments' is likely explained by this constraint, not by oversight. (2) CODE BUG for whoever next touches this file: `_taker_ratio_at` forward-fills the final taker row forever (bisect_right-1, no staleness bound), so a stream that ends mid-backtest yields a frozen signal rather than a flat one — add a max-staleness guard (e.g. drop rows older than one bar interval). FAILURE PATTERN: a hypothesis explicitly designed for 'MAXIMUM DATA SAFETY' (one instrument, one bar type, one supplementary stream) still died on a data wall — because availability was checked but HISTORY DEPTH was not. Supplementary-stream selection must verify the stream's time-span covers the intended backtest horizon, not merely that the stream exists.

Implementation

Single-instrument long-short contrarian strategy on BTCUSDT.BINANCE USD-M perpetual futures (4H bars). Computes the per-bar taker buy fraction = buyVol/(buyVol+sellVol) from the taker_ratio supplementary stream, then maintains a rolling 24h (6-bar) average. Fades sentiment extremes: SHORT when the 24h average taker buy ratio exceeds 0.55 (peak FOMO buying → local top), LONG when it drops below 0.45 (peak panic selling → local bottom). Exits on reversion to neutral (0.50), a 6% take-profit, a 4% stop-loss, or a 12-bar (48h) time stop. Leverage 2.0 for ~1.2x-equity notional. Stays flat if taker_ratio data is unavailable — never falls back to a price proxy.

Verification Results

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['avg_window', 'exit_neutral', 'position_pct', 'max_hold_bars', 'stop_loss_pct', 'lower_threshold', 'take_profit_pct', 'upper_threshold'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: argument of type 'strategy' is not iterable (bar timestamp: 1735690740000) - steady_downtrend: TypeError: argument of type 'strategy' is not iterable (bar timestamp: 1735690740000) - flat_ranging: TypeError: argument of type 'strategy' is not iterable (bar timestamp: 1735690740000) - volatility_spike: TypeError: argument of type 'strategy' is not iterable (bar timestamp: 1735690740000) - zero_volume: TypeError: argument of type 'strategy' is not iterable (bar timestamp: 1735690740000) - price_gap: TypeError: argument of type 'strategy' is not iterable (bar timestamp: 1735690740000)

Backtest Review

The code is correct and disciplined: when taker_ratio is unavailable it stays FLAT rather than falling back to a price proxy, which is exactly right — a fallback would have manufactured a fake multi-year track record for a hypothesis the data cannot test.

Backtest Review

The trades that did fire match the hypothesized mechanism: 8 long / 8 short, genuinely contrarian and dollar-balanced, with beta -0.0017 and benchmark_correlation -0.0436 (market-neutral as designed).

Backtest Review

Field-robustness parsing (camelCase + snake_case, vols-then-ratio fallback) is sound, and entry_diag shows 16 signaled -> 16 submitted with zero drops, so the order plumbing works.

Backtest Review

THE STRATEGY IS BLIND FOR ~99.5% OF THE BACKTEST. bars_processed is 14,286 across 2019-12-31 to 2026-07-07, but every daily return lies in 2026-04-10..2026-05-06, annual_returns contains ONLY a 2026 key, and data_days=13. The taker_ratio supplementary stream appears to cover roughly 26 days, not 6 years. I am reporting the symptom — the exact coverage of the taker_ratio table was not independently queried — but the evidence is unambiguous that the signal was unavailable for essentially the whole run.

Backtest Review

SAMPLE IS UNUSABLE FOR OPTIMIZATION: 16 trades (vs the hypothesis's forecast of 300-420) inside a single ~26-day window. Splitting that across 3 walk-forward windows yields ~5 trades each; OOS folds will come back empty or degenerate. Two hours of optimization would fit 3 thresholds to noise.

Backtest Review

The 16 trades that exist LOSE money: expectancy -$139.10/trade, total_return -2.23%, sharpe_ratio -2.196, profit_factor 0.741, omega_ratio 0.657, negative alpha (-0.0016). sharpe_ci spans -11.19 to +6.68 — no information content whatsoever.

Backtest Review

FEE-FRAGILE BY CONSTRUCTION: commission_pct_of_gross is 29.39% on an avg_holding_period of 6h30m and turnover 725. Even with full data, a fade that holds ~1.6 bars hands ~30% of gross edge to fees before any edge is proven.

Backtest Review

SIZING IS AGGRESSIVE: avg_position_pct 120.4% (position_pct 0.60 x leverage 2.0). A contrarian fade running 1.2x notional into a trending move is the structurally dangerous side of this mechanism — largest_loss (-$3,708) is nearly 2x largest_win ($1,976).

Backtest Review

LATENT BUG: _taker_ratio_at forward-fills the last taker row indefinitely (bisect_right - 1 with no staleness bound). Once the stream ends, _ratio_history repeatedly appends one stale value until the whole avg_window is stale. It happened to sit near 0.50 here so nothing fired, but on other data this silently generates a permanent phantom signal.

Analysis

Do NOT optimize — the strategy only traded 16 times because the taker_ratio supplementary stream for BTCUSDT.BINANCE only has ~2 months of coverage (2026-04 to 2026-05), despite 14,261 bars (6 years) being processed. The strategy correctly stays flat when taker_ratio is missing, so the empty result is a DATA-COVERAGE problem, not a signal-logic problem. Required step before any optimization: (1) Verify taker_ratio history availability for BTCUSDT via the data pipeline (ensure_extras_available / supplementary collectors) and backfill the full 2019–2026 range if the source supports it. (2) Re-run the backtest and confirm the trade count reaches the ~300+ the hypothesis expected (>100 minimum). If taker_ratio genuinely only exists for ~2 recent months and cannot be backfilled, the hypothesis premise (6 years of taker_ratio coverage) is invalid and this should be escalated to the Research Lead to re-scope onto a supplementary stream with real historical depth (e.g. funding or OI, which show far more events), rather than optimized on a 16-trade sample.

Outcome Summary

This was the sixth iteration of a hypothesis deliberately engineered for 'maximum data safety' after two prior cross-venue ideas died on missing bar types: one instrument, one bar type, one supplementary stream (taker_ratio), fading extreme aggressor-flow readings long and short. The code was judged correct and disciplined — it stayed flat rather than falling back to a price proxy when taker_ratio was unavailable, and its 16 trades were genuinely market-neutral (beta -0.0017, benchmark correlation -0.0436) — but that discipline exposed the real problem: the taker_ratio stream carries only ~26 days of history against a 6.5-year backtest window. The 16 trades that fired were net-negative (Sharpe -2.196, expectancy -$139.10) yet the analyst explicitly labeled this a coin-flip sample, not evidence against the mechanism, and abandoned on the grounds that the blocker was data-history depth which no amount of developer iteration could fix. The run left two artifacts for the factory: a recommendation to backfill taker aggressor volume from Binance Vision aggTrades, and a latent bug report that `_taker_ratio_at` forward-fills the final taker row indefinitely with no staleness bound.

Outcome Summary

Verifying that a supplementary data stream *exists* is not the same as verifying its *history depth* covers the intended backtest horizon — check the stream's actual row-span before specifying it in a hypothesis.

Outcome Summary

The analyst issued an `abandon` verdict at the pre-optimization backtest-review gate: the taker_ratio supplementary stream covers only ~26 days of history (data_days=13, annual_returns has a single 2026 key), so the strategy correctly stayed flat for ~99.5% of bars and the 16-trade sample could not support 3-phase walk-forward optimization. Optimization, analyst post-opt review, and risk review were never reached.

Outcome Summary

Fade sentiment extremes on BTCUSDT.BINANCE 4H futures by shorting when the 24h-average taker buy/sell ratio exceeded 0.55 and going long when it fell below 0.45, on the thesis that peak aggressor-flow imbalance marks local tops and bottoms.

Outcome Summary

Across 14,286 4H bars nominally spanning 2019-12-31 to 2026-07-07, it produced only 16 trades (8 long / 8 short) against a hypothesized 300-420, all confined to a ~26-day window: total return -2.23%, Sharpe -2.196, profit factor 0.741, win rate 37.5%, expectancy -$139.10/trade, max drawdown 6.39%. Commissions consumed 29.39% of gross with an average holding period of 6h30m, and the Sharpe confidence interval (-11.19 to +6.68) carried no information.

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['avg_window', 'exit_neutral', 'min_notional', 'position_pct', 'max_hold_bars', 'stop_loss_pct', 'lower_threshold', 'take_profit_pct', 'upper_threshold', 'supplementary_data'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: BtcFourHourTakerRatioExtremeMeanReversionLongShort._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - steady_downtrend: TypeError: BtcFourHourTakerRatioExtremeMeanReversionLongShort._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - flat_ranging: TypeError: BtcFourHourTakerRatioExtremeMeanReversionLongShort._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - volatility_spike: TypeError: BtcFourHourTakerRatioExtremeMeanReversionLongShort._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - zero_volume: TypeError: BtcFourHourTakerRatioExtremeMeanReversionLongShort._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - price_gap: TypeError: BtcFourHourTakerRatioExtremeMeanReversionLongShort._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000)

Iteration History

Verification failed (Layer 3 — sandbox backtest): No trades produced Bar type used: BTCUSDT.BINANCE-4-HOUR-LAST-EXTERNAL, Bars processed: 14076 Diagnostics: should_enter() returned a side 0 times over 14057 evaluated bars -> your ENTRY CONDITION never triggered. Loosen the entry logic / thresholds. Ensure your strategy produces trades with the given data and parameters.
Strategy report

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