Skip to content

View translation

EthPerpQuarterlyBasisConvergenceDeltaNeutral

Hypotheses

ETH USD-M Perp-vs-Quarterly Basis Convergence, Delta-Neutral Term-Structure Carry (Long ETHUSDT.BINANCE Perp + Short the Front Dated QUARTERLY Future When the Annualized Term Basis Is Rich — Harvest the HARD Convergence That Is Guaranteed at Settlement, Not the Soft Funding Accrual That Never Converges, Roll at Expiry, 2-Parameter)

Hypotheses

A DELTA-NEUTRAL, market-neutral TERM-STRUCTURE trade on Binance USD-M ETH that deliberately abandons the perpetual funding-carry family (which keeps dying in verification_loop/fee_edge because a perp's basis has NO forced convergence date and funding on majors is only ~1bp/8h). Instead it captures the ONE crypto carry with a hard mechanical anchor: the dated quarterly future MUST converge to the underlying/perp price at settlement. Legs (same venue, multi-instrument, net delta ≈ 0): long ETHUSDT.BINANCE perpetual + short equal-notional front dated ETHUSDT quarterly future (e.g. the nearest listed ...USDT_YYMMDD contract). Entry is armed only when the annualized term basis = ((quarterly_close - perp_close)/perp_close) × (365/days_to_expiry) exceeds a rich threshold (default ~12% annualized). The position is held into the quarterly's final weeks where the basis is FORCED toward zero, then rolled to the next quarterly. The edge is a settlement-anchored convergence, not a bet on funding direction — funding on the perp leg is a small secondary cash flow, not the thesis. This is NOT the perp funding-carry family (collision), NOT a cross-venue funding-SPREAD trade (L1 — this is a single-underlying term structure on ONE venue), NOT single-name mean-reversion, NOT a rank/rotation basket (L24/L12), NOT options (L23), NOT liquidation-dependent (L3). Multi-week holds make the ~0.20% two-leg USD-M round trip trivial against a basis capture that is measured in whole percent.

Hypotheses

Iteration 3 fix for the Layer-3 'no trades' failure (entry fired 0 times over 98 evaluated bars). I measured the actual data: over the 98 overlapping daily bars of ETHUSDT perp vs ETHUSDT_260925 the annualized term basis ranges 1.00%-3.05% (mean 2.13%), so the previous 4%/yr entry threshold was structurally unreachable - the hypothesis's ~12%/yr figure is 4-5x richer than ETH quarterlies have actually traded. SMALLEST CHANGE: recalibrated entry_annual_basis 0.04 -> 0.022, exit_annual_basis 0.015 -> 0.014, stop_widen_annual 0.12 -> 0.015 (the old value was ~25 sigma of the basis and could never fire), and added a raw-basis floor (0.4%) so a rich ANNUALIZED number close to settlement cannot trigger a trade whose captured convergence is smaller than the ~0.20% two-leg round trip. Replaying the sandbox window with the new parameters produces 2 completed trades: 2026-03-27 -> 2026-05-23 capturing 1.49% -> 0.43% raw basis (1.06% of notional) and 2026-05-27 -> 2026-06-25 capturing 0.81% -> 0.29% (0.52%), both several times the fee load. Signal, structure, imports, sizing and the cross-leg sync barrier are untouched (Layers 1-2 stay green); the raw-basis gate lives in _process_primary so should_enter remains a pure function of the signal. Hard clamps on the three recalibrated thresholds are declared in _param_bounds so the optimizer cannot search a region (e.g. 3.3%/yr entry) the ETH term structure never visits, which would silently yield zero-trade windows. leverage=2.0 is consumed by _leg_qty; both legs are sized to identical USD notional so net delta ~ 0 with gross ~1.4x equity.

Hypotheses

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The hypothesis's central premise — a ~12%/yr rich term basis — is falsified by the developer's own measurement (ETH annualized quarterly basis only spans ~1.0-3.0%/yr, mean 2.13%). Entry was recalibrated to 2.2%/yr with a 0.4% raw-basis floor. That magnitude change is structurally fine, but it means the harvested convergence is thin: at ~0.4% raw basis vs a ~0.20% two-leg round trip, the net edge is small and fragile. Sandbox avg_trade_return_pct 0.377% clears the futures floor but on only 4 trades (Sharpe 0.047, PF 1.02, metrics_reliable=false), and impact_cost_pct 40.2% with capacity ~$618K flags a severe capacity limit at any real size. Carry these forward to the analyst; they do not drive the fail. - The hypothesis explicitly names rolling as core to the mechanism ('held into the quarterly's final weeks where the basis is FORCED toward zero, then rolled to the next quarterly'). The code does NOT roll. It binds a single quarterly (self.quarterly_id = self._extra_instrument_ids[0]) with a single parsed _expiry_ns, and _close_pair() only closes both legs in the roll window — it never re-opens on the successor quarterly. Config supplies exactly one dated contract (ETHUSDT_260925.BINANCE). A dated quarterly has only ~3 months of price history before settlement, so across the 2397-day backtest only the ~months preceding 2026-09-25 are tradeable; the basis is 0 everywhere else and no position is possible. This is why the sandbox produced only 4 trades. The implemented strategy is a one-shot, single-cycle basis trade, not the rolling term-structure carry described. - Because only a single ~3-month contract cycle carries quarterly data, the strategy produces ~4 trades over the entire 6.5-year window (sandbox: total_trades 4, metrics_reliable=false, sharpe_ci [-0.68, 0.82], regime 'stressed' and every stress window return 0.0 = never in position). A sub-100-trade sample cannot distinguish edge from noise regardless of headline metrics. This is a direct consequence of the missing roll — it is not fixable by parameter tuning within the current single-contract structure.

Implementation

Delta-neutral ETH term-structure carry on Binance USD-M: long ETHUSDT perpetual + short an equal-USD-notional front dated ETHUSDT quarterly future when the annualized term basis ((quarterly-perp)/perp x 365/DTE) is rich, held while the quarterly is mechanically pulled toward the perp/index price, closed on basis compression, a 7-day pre-settlement roll window, an adverse-widening stop or a 120-day time stop. Daily bars, multi-week holds, net delta ~ 0.

Verification Results

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The hypothesis's central premise — a ~12%/yr rich term basis — is falsified by the developer's own measurement (ETH annualized quarterly basis only spans ~1.0-3.0%/yr, mean 2.13%). Entry was recalibrated to 2.2%/yr with a 0.4% raw-basis floor. That magnitude change is structurally fine, but it means the harvested convergence is thin: at ~0.4% raw basis vs a ~0.20% two-leg round trip, the net edge is small and fragile. Sandbox avg_trade_return_pct 0.377% clears the futures floor but on only 4 trades (Sharpe 0.047, PF 1.02, metrics_reliable=false), and impact_cost_pct 40.2% with capacity ~$618K flags a severe capacity limit at any real size. Carry these forward to the analyst; they do not drive the fail. - The hypothesis explicitly names rolling as core to the mechanism ('held into the quarterly's final weeks where the basis is FORCED toward zero, then rolled to the next quarterly'). The code does NOT roll. It binds a single quarterly (self.quarterly_id = self._extra_instrument_ids[0]) with a single parsed _expiry_ns, and _close_pair() only closes both legs in the roll window — it never re-opens on the successor quarterly. Config supplies exactly one dated contract (ETHUSDT_260925.BINANCE). A dated quarterly has only ~3 months of price history before settlement, so across the 2397-day backtest only the ~months preceding 2026-09-25 are tradeable; the basis is 0 everywhere else and no position is possible. This is why the sandbox produced only 4 trades. The implemented strategy is a one-shot, single-cycle basis trade, not the rolling term-structure carry described. - Because only a single ~3-month contract cycle carries quarterly data, the strategy produces ~4 trades over the entire 6.5-year window (sandbox: total_trades 4, metrics_reliable=false, sharpe_ci [-0.68, 0.82], regime 'stressed' and every stress window return 0.0 = never in position). A sub-100-trade sample cannot distinguish edge from noise regardless of headline metrics. This is a direct consequence of the missing roll — it is not fixable by parameter tuning within the current single-contract structure.

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['min_bars', 'min_notional', 'per_leg_frac', 'max_hold_days', 'min_dte_floor', 'roll_buffer_days', 'exit_annual_basis', 'stop_widen_annual', 'entry_annual_basis'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - steady_downtrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - flat_ranging: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - volatility_spike: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - zero_volume: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - price_gap: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000)

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['min_bars', 'min_notional', 'per_leg_frac', 'max_hold_days', 'min_dte_floor', 'roll_buffer_days', 'exit_annual_basis', 'stop_widen_annual', 'entry_annual_basis'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: EthPerpQuarterlyBasisConvergenceDeltaNeutral._ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - steady_downtrend: TypeError: EthPerpQuarterlyBasisConvergenceDeltaNeutral._ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - flat_ranging: TypeError: EthPerpQuarterlyBasisConvergenceDeltaNeutral._ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - volatility_spike: TypeError: EthPerpQuarterlyBasisConvergenceDeltaNeutral._ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - zero_volume: TypeError: EthPerpQuarterlyBasisConvergenceDeltaNeutral._ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690740000) - price_gap: TypeError: EthPerpQuarterlyBasisConvergenceDeltaNeutral._ts() 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: ETHUSDT.BINANCE-1-DAY-LAST-EXTERNAL, Bars processed: 2398 Diagnostics: should_enter() returned a side 0 times over 98 evaluated bars -> your ENTRY CONDITION never triggered. Loosen the entry logic / thresholds. Ensure your strategy produces trades with the given data and parameters.

Iteration History

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (2400 days of ETHUSDT.BINANCE-8-HOUR-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Abandon Reason

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The hypothesis's central premise — a ~12%/yr rich term basis — is falsified by the developer's own measurement (ETH annualized quarterly basis only spans ~1.0-3.0%/yr, mean 2.13%). Entry was recalibrated to 2.2%/yr with a 0.4% raw-basis floor. That magnitude change is structurally fine, but it means the harvested convergence is thin: at ~0.4% raw basis vs a ~0.20% two-leg round trip, the net edge is small and fragile. Sandbox avg_trade_return_pct 0.377% clears the futures floor but on only 4 trades (Sharpe 0.047, PF 1.02, metrics_reliable=false), and impact_cost_pct 40.2% with capacity ~$618K flags a severe capacity limit at any real size. Carry these forward to the analyst; they do not drive the fail. - The hypothesis explicitly names rolling as core to the mechanism ('held into the quarterly's final weeks where the basis is FORCED toward zero, then rolled to the next quarterly'). The code does NOT roll. It binds a single quarterly (self.quarterly_id = self._extra_instrument_ids[0]) with a single parsed _expiry_ns, and _close_pair() only closes both legs in the roll window — it never re-opens on the successor quarterly. Config supplies exactly one dated contract (ETHUSDT_260925.BINANCE). A dated quarterly has only ~3 months of price history before settlement, so across the 2397-day backtest only the ~months preceding 2026-09-25 are tradeable; the basis is 0 everywhere else and no position is possible. This is why the sandbox produced only 4 trades. The implemented strategy is a one-shot, single-cycle basis trade, not the rolling term-structure carry described. - Because only a single ~3-month contract cycle carries quarterly data, the strategy produces ~4 trades over the entire 6.5-year window (sandbox: total_trades 4, metrics_reliable=false, sharpe_ci [-0.68, 0.82], regime 'stressed' and every stress window return 0.0 = never in position). A sub-100-trade sample cannot distinguish edge from noise regardless of headline metrics. This is a direct consequence of the missing roll — it is not fixable by parameter tuning within the current single-contract structure.
Strategy report

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