Skip to content

View original

BtcAggressorBuyImbalanceMomentumLong

Hypotheses

BTC 4H Trade-Tick Aggressor Buy-Imbalance Momentum Long with Daily Bull Regime Filter

Hypotheses

A long-only single-instrument MICROSTRUCTURE ORDER-FLOW strategy on BTCUSDT perpetual futures using 4-hour bars + trade-tick supplementary data, with a daily-bar bull regime filter. This proposal targets a GENUINELY UNEXPLORED signal class — no current portfolio strategy uses trade-tick aggressor data. Order flow imbalance (buy-aggressor volume vs sell-aggressor volume) is a well-documented microstructure signal (Easley, Lopez de Prado, O'Hara 'The Volume Clock: Insights into the High-Frequency Paradigm' 2012; Cont, Kukanov, Stoikov 'The Price Impact of Order Book Events' 2014). The mechanism captures WHO is trading aggressively (takers vs makers, longs vs shorts) — a fundamentally different signal type than price oscillators (RSI/BB), volume aggregates (Vol Breakout), derivative-state signals (funding/OI/liquidations), or chart patterns. This is mechanistically novel and does NOT fall into any documented failure pattern: (1) NOT a mean-reversion/momentum-continuation transfer (it's a flow-direction signal, not a price-extreme signal), (2) NOT a sparse-trigger strategy (4H bars with aggressor imbalance fires 30-60×/year), (3) NOT a cross-asset replication (BTC is the only asset proposed), (4) NOT a symmetric inversion (this is a directly-observed flow signal, not an inversion of a failed thesis), (5) NOT options or multi-instrument (single-asset linear futures). The economic mechanism: when buy-aggressor volume materially dominates sell-aggressor volume during a 4H bar (e.g., buy-volume > 60% of total), this signals systematic institutional or retail buying pressure pushing through resting offers. In a bull regime, this aggressive buying typically continues over the next 4-8 hours as more buyers chase the breakout — momentum continuation. The signal is LEADING because it measures what's happening RIGHT NOW at the order level, not lagging price action. BTC is chosen because: (a) BTC has the deepest trade-tick data and most institutional flow (the aggressor split is most informative), (b) BTC's 4H data path is empirically the most-cached, (c) BTC's institutional ETF-driven flow makes aggressor imbalance economically meaningful (when ETF allocators buy aggressively, it's a documented continuation signal). Only 4 explicit parameters (aggressor-ratio threshold, minimum-volume filter, regime SMA period, stop pct).

Hypotheses

Iteration 7 fixes ONLY the Layer-2 synthetic failure: `_to_ns() takes 1 positional argument but 2 were given`. Root cause is the verifier's `_StrategyProxy`, which sweeps every callable off the instance and re-binds it via `types.MethodType(...)` onto a plain wrapper — a helper that the harness can bind differently than Python's normal descriptor protocol gets an extra `self` injected. The minimal, robust fix is to move the two pure, stateless helpers off the class entirely: `_to_ns`/`_norm_ns` → module-level `_ofb_ns(value)`, and `_fin` → module-level `_ofb_fin(value)`. Module-level functions are never touched by the proxy's attribute transfer, so the call signature is unambiguous under both the real engine and the verifier. Call sites in `_build_taker()` and `calculate_signal()` were updated accordingly. Nothing else changed: imports remain clean (Layer 1 stays green), the continuous signal (`buy_fraction - 0.5`, returned every bar) is byte-identical, entry/exit/sizing logic and the four-parameter surface are untouched. Verified locally against the real harness: Layer 1 static passes and all six Layer-2 scenarios (steady_uptrend, steady_downtrend, flat_ranging, volatility_spike, zero_volume, price_gap) pass. Venue is BINANCE futures because the hypothesis's aggressor-imbalance edge is derived from perpetual-futures taker flow, which is where the institutional/ETF-driven order flow the hypothesis describes actually prints; leverage stays 1.0 and is not referenced in sizing.

Hypotheses

max_iterations_after_review

Implementation

Long-only BTC 4H order-flow momentum strategy. Reads Binance taker buy/sell volume (taker_ratio supplementary data — the aggregated trade-tick aggressor split) and converts it to a causal buy-fraction per 4H bar. Enters long when buy-aggressor dominance exceeds aggressor_ratio_threshold while a daily SMA bull regime holds and bar volume clears a liquidity gate. Exits on the earliest of a ~8h time stop, a hard PnL stop, an aggressor flip to sell-dominant, or a regime flip to bearish. Four tunable parameters; position sizing is capital-relative at 20% of equity, unlevered.

Verification Results

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['stop_pct', 'min_volume_filter', 'regime_sma_period', 'aggressor_ratio_threshold'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: BtcAggressorBuyImbalanceMomentumLong._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - steady_downtrend: TypeError: BtcAggressorBuyImbalanceMomentumLong._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - flat_ranging: TypeError: BtcAggressorBuyImbalanceMomentumLong._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - volatility_spike: TypeError: BtcAggressorBuyImbalanceMomentumLong._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - zero_volume: TypeError: BtcAggressorBuyImbalanceMomentumLong._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - price_gap: TypeError: BtcAggressorBuyImbalanceMomentumLong._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000)

Backtest Review

Mechanism is genuinely novel for this portfolio and the trades faithfully implement the hypothesis: 16/16 LONG, all entered in a confirmed daily bull regime, 4-12h holds matching the stated 4-8h continuation horizon, leverage 0.2 == POSITION_PCT.

Backtest Review

No mechanism mismatch and no code-level trading bug: entry_diag shows entry_signaled 16 == entries_submitted 16, with dropped_size_zero, dropped_min_notional and dropped_cash_short all 0.

Backtest Review

The causal lookup is correct — _frac_at() uses bisect_right(ts)-1, taking the most-recent aggressor sample at-or-before the bar, so there is no lookahead.

Backtest Review

A full-history replacement source already exists and is already a registered loader key, so this is a small, well-scoped fix rather than a rewrite.

Backtest Review

FATAL for optimization: only 16 trades, ALL between 2026-04-05 and 2026-05-17, out of a nominal 2019-12-31 -> 2026-07-07 span. data_days = 16 against 14,286 bars processed.

Backtest Review

Root cause verified on disk: supplementary_data['taker_ratio'] for BTCUSDT is the Binance Futures REST feed and spans only 2026-03-23 -> 2026-05-18 (~56 days; 1h.parquet 864 rows, api_history.parquet 12,648 rows). For all earlier bars _frac_at() returns None, calculate_signal() returns 0.0, and should_enter() returns None — the strategy silently no-ops for ~6.3 of its 6.5 years.

Backtest Review

The fix is available: data/supplementary/metrics/BTCUSDT holds 336,371 rows spanning 2020-09-01 -> 2026-07-08 with column taker_long_short_vol_ratio, which IS the taker buy/sell volume ratio the strategy needs (frac = r/(1+r), the code's existing fallback math). agg_trades/BTCUSDT also exists for true tick-level aggressor flags.

Backtest Review

All headline metrics are therefore degenerate and must not be read as performance: total_return is 0.53% (a percent figure, not 52.6%), max_drawdown 0.29%, sharpe_ratio 2.45 but sharpe_ci_low -4.90 straddling zero, deflated_sharpe null, rolling_sharpe empty, cagr null, a single annual_returns entry (2026). 14 non-zero return days cannot support a Sharpe estimate.

Backtest Review

Pre-existing economic concern to re-check after the refit: mean net per-trade return is only ~0.165% against ~0.10% round-trip taker cost, commission_pct_of_gross is 24.53%, impact_cost_pct 13.4%, and capacity_usd just $5.57M. The edge is thin even where it does trade.

Backtest Review

Cosmetic inconsistency: min_bars_required() returns 20 while _sma() needs regime_sma_period * BARS_PER_DAY (300 bars at the default 50 days).

Backtest Review

enough to fit 4 params over ~225 trials

Analysis

Do NOT send this to optimization — the strategy only traded in a ~6-week window (2026-04-06 to 2026-05-17) out of a 6.5-year backtest, so a 225-trial sweep would fit 16 trades of noise and every walk-forward OOS window before 2026 would be empty. 1. MEASURE THE DATA SPAN FIRST. Do not change any threshold until you have. At the end of `_build_taker()`, log `len(self._tr_ts)`, and the min/max timestamps converted to dates. The report shows 14,267 bars evaluated but all 16 entries in April-May 2026, and `_frac_at()` returns None (blocking entry) for every bar before the first sample — so the parsed `taker_ratio` series almost certainly starts in 2026. Confirm this and report the actual first/last sample dates and the sample count. State the measured span; do not assert a cause you have not checked. 2. IF THE SPAN IS SHORT, SWITCH THE DATA SOURCE, NOT THE THRESHOLD. Per the project data table, `BinanceVisionCollector` archives **aggTrades** (and raw trades) to `data/supplementary/` from the Binance Vision CDN, which has full history and no rate limits. aggTrades carries the `isBuyerMaker` flag, which is the *actual* tick-level aggressor label the hypothesis calls for — strictly better than the pre-aggregated `taker_ratio` proxy. Rebuild the signal by bucketing aggTrades into 4H bins and computing `buy_fraction = buy_aggressor_volume / total_volume` per bin. Keep `_frac_at()`'s causal bisect semantics exactly as they are. - Check `supplementary_data` for an `agg_trades` / `aggTrades` key before writing new parsing. If the key is absent from the loader, say so explicitly in your response rather than silently falling back to `taker_ratio`. - Beware the cost of this: bucketing years of BTC aggTrades per bar will blow the 300s Layer-3 limit if done naively. Build the 4H buy-fraction series ONCE, lazily, into the same two parallel sorted arrays you already use — never rescan raw ticks per bar. 3. IF FULL-HISTORY AGGRESSOR DATA IS GENUINELY UNAVAILABLE, say so plainly and stop. Do not loosen `aggressor_ratio_threshold` to manufacture trades inside a 6-week window — that fits the handful of days that happen to have coverage. In that case this hypothesis is not testable as specified and should be reframed onto a data source with real history. 4. ADDRESS THE COST DRAG. Even in the usable window, commissions are 24.53% of gross PnL and market impact another 13.40% — ~38% of the edge. avg_win and avg_loss are within $1 of each other, so the strategy is a pure win-rate bet. The 2-bar (~8h) fixed time stop is what forces this churn. Once you have full history, report avg_trade_return and commission_pct_of_gross; if avg_trade_return stays near 0.165% (barely above the 0.15% USD-M bar), consider raising `MAX_HOLD_BARS` so winners run past the fee hurdle. Re-run and report: taker/aggressor series first sample date, last sample date, sample count; trades per calendar year; exposure_pct; commission_pct_of_gross. Target >= 100 trades spread across multiple years before this goes anywhere near the optimizer.

Outcome Summary

BtcAggressorBuyImbalanceMomentumLong proposed a genuinely unexplored signal class for the portfolio — taker aggressor buy/sell imbalance on BTC 4H bars, gated by a daily bull regime — and the implementation was clean: 16/16 signals filled, no lookahead in the causal aggressor lookup, holds matching the stated 4-8h continuation horizon. But the chosen feed, taker_ratio, spans only ~56 days of 2026, so across 14,286 bars the strategy traded on just 16 effective days and every headline metric (Sharpe 2.45, drawdown 0.29%) rested on 14 non-zero return days with a Sharpe confidence interval straddling zero. The reviewer declined to optimize, identified a full-history replacement already on disk, and returned 'iterate' — but after 7 iterations the run hit max_iterations_after_review and was abandoned before optimization or analyst review. Underneath the data problem sat a thin edge anyway: ~0.165% mean net per-trade return against a ~0.10% round-trip, with commissions at 24.53% of gross.

Outcome Summary

A signal is only as long as its data source: check the actual on-disk coverage of a supplementary feed before choosing it, especially when a full-history alternative (here, metrics/BTCUSDT's taker_long_short_vol_ratio, 336,371 rows from 2020-09-01) already exists as a registered loader key.

Outcome Summary

Backtest review returned 'iterate' rather than optimize, having verified on disk that supplementary_data['taker_ratio'] for BTCUSDT covers only 2026-03-23 to 2026-05-18 (~56 days), so _frac_at() returned None and the strategy silently no-opped for ~6.3 of its 6.5-year span. The strategy was then abandoned with reason 'max_iterations_after_review' after 7 iterations, without ever reaching optimization or analyst review.

Outcome Summary

Go long BTCUSDT on 4-hour bars when taker aggressor buy volume dominated sell volume (buy fraction >= 0.60) inside a daily bull regime, on the thesis that observed order-flow imbalance leads 4-8 hours of momentum continuation.

Outcome Summary

The backtest produced only 16 trades (all long, all between 2026-04-05 and 2026-05-17), with total_return 0.53%, Sharpe 2.45 but sharpe_ci_low -4.90, win rate 62.5%, profit factor 1.67, expectancy $33.04/trade, max drawdown 0.29%, and deflated_sharpe null. The reviewer explicitly ruled these metrics degenerate: 14,286 bars were processed but effective data_days was 16, avg net per-trade return was ~0.165% against ~0.10% round-trip cost, commissions were 24.53% of gross, and impact cost 13.4%.
Strategy report

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