BtcDeribitWeeklyDeltaHedgedAtmStraddleVrp
Hypotheses
Deribit BTC Weekly Variance-Risk-Premium Harvest — Delta-Hedged Short ATM Straddle, Conditional on Elevated IV Rank: Sell the New Weekly ATM Straddle Each Expiry Cycle ONLY When 1-Week IV Trades Rich to Trailing Realized Vol, Re-Hedge Delta Daily With the Perp, Hold to Expiry (DERIBIT Options + BINANCE Perp Hedge, Market-Neutral Vol, 2-Parameter)
Hypotheses
A DELTA-HEDGED, MARKET-NEUTRAL SHORT-VOLATILITY strategy that harvests the crypto VARIANCE RISK PREMIUM — the persistent, well-documented gap by which 1-week implied vol prints RICH to subsequently-realized vol — on the single most liquid crypto options market (Deribit BTC). This is a deliberate PIVOT OUT of the funding-carry family that keeps dying, and INTO the options bucket, which is the single largest structural gap in the book (BINANCE_OPTIONS 0.0%, combined options 5.5% vs the ≥15% quota). MECHANISM (schedule-driven, so it always trades and never dies at no-trades): at each Friday weekly expiry, IF the entry filter fires, SELL the newly-listed 1-week ATM straddle (short 1 ATM call + short 1 ATM put on the same strike/expiry — a single fixed instrument pair, so NO per-bar option-chain scanning and NO O(n) sandbox timeout), then RE-HEDGE the residual delta once daily by trading BTCUSDT.BINANCE perp back to net-zero delta, and hold the structure to expiry where it settles. The P&L is theta (time decay collected) minus realized gamma cost (daily hedge slippage) — positive in expectation precisely when IV was sold rich to realized. It is materially distinct from the factory's existing static short-vol positions: NOT a defined-risk iron condor (this is delta-hedged and re-hedged daily, harvesting gamma/theta rather than a static wing structure) and NOT an undefended monthly cash-secured put (this is delta-NEUTRAL at all times and short-dated weekly). ONLY 2 parameters: the IV-richness entry threshold and the daily delta re-hedge band.
Hypotheses
I traced the zero-trade run to a units double-conversion rather than a threshold problem, which is why iteration 2's gate loosening changed nothing. First I ruled out the headline diagnostic: 'calculate_signal() never evaluated (0 evaluated bars)' counts FactoryStrategy._process_primary, and RollingOptionsStrategy.on_bar deliberately never calls it — every rolling-options strategy in generated/ reports 0 evaluated bars and several trade fine. Routing bars through the base path would have made it worse, because the 1-DAY option legs are same-timeframe extras and the base's alignment barrier (_maybe_process_primary, base_template.py:404) would wait forever for a contemporaneous bar on a contract that exists for one week of a 2400-day window. The real cause: _straddle_iv built the straddle mark as `px_btc * spot` on the assumption that Deribit's coin quoting survives into the catalog. It does not — src/data/on_demand.py:1255 runs `_option_premium_to_usd` (premium_usd = premium_coin × underlying) on the default write path, so those bars are ALREADY USD. Multiplying by spot inflated the mark ~6e4×, so the very next line's solvability check (`straddle_px(hi) < total_usd`) tripped on every cycle, _straddle_iv returned None every time, the gate fell through to DVOL, and with a BINANCE primary instrument the sandbox has no deribit_dvol series to inject — landing on the `no_iv` branch, which correctly BLOCKS. Every weekly cycle was blocked by an arithmetic error. Fix, confined to the premium read: a new _leg_premium_usd() auto-detects the unit instead of assuming one (a BTC option's coin premium is always ≪1 and its USD premium always ≫1, so the conventions are unambiguous), making it correct under both the default and SF_NATIVE_OPTIONS paths. I also widened the bisection bracket 0.05–4.0 → 0.02–8.0 and now LOG the first unsolvable mark with its numbers — that failure was completely silent, which is why three iterations ran blind. Finally entry_window_hours 48 → 120: on 1-DAY bars 48h gives only two chances to enter, and _enter_leg legitimately defers on a bar where a leg's market isn't live yet, so one unlucky bar forfeited an entire cycle. I changed nothing else — the mechanism, the gate's logic and sign, the daily hedge, sizing, the roll schedule, the no-fallback BLOCK rule, and the 49-day lookback that keeps the per-bar-type catalog loader inside the 300s cap are all untouched, and imports are unchanged.
Hypotheses
verification_loop: Verification failed (Layer 4 — QA review) [class=code_defect]:
- [edge_concern] Beyond the sample-size block: sandbox avg_trade_return_pct is -11.35% and win metrics rest on 24 trades with a Sharpe CI of [-4.36, 6.24] (metrics_reliable flagged true but the CI spans zero widely). Per-notional return is not meaningful for option legs (premium notional << underlying), so the negative figure is not itself disqualifying, but with only ~7 cycles there is no way to tell theta-harvest edge from noise. If the loader capacity is fixed and the sample grows, re-check fee viability of the daily perp re-hedge (turnover 35.98, impact 1.2%) against the theta collected.
- [critical] The rolling-options chain expansion resolves the option schedule only over [now() - lookback_days, now()] (src/agents/backtest_agent.py:44-45: start = end - timedelta(days=lookback_days), end = now()). With rolling_options.lookback_days=49 the ENTIRE tradeable option chain in ANY backtest is confined to the ~49 days ending today = ~7 weekly straddle cycles / ~14 option legs, regardless of the 6.5-year catalog. The strategy sits flat for the other ~6.4 years and trades only at the tail. This is confirmed empirically by the sandbox: bars_processed=2397 (~6.5 yrs) but data_days=43 and total_trades=24 (of which ~14 are option legs, the rest perp hedges). The variance-risk-premium edge the hypothesis targets cannot be distinguished from noise on ~7 cycles / ~14 option trades — far below the ~30-100 trades needed for a measurable options sample (recurring: options strategies pass QA then die unmeasurable at ~1-9 trades). The developer's own docstring acknowledges the cap ('lookback_days is held at 49 (7 weekly cycles / 14 option legs) because the loader issues one catalog.bars() call PER bar type and each option leg costs ~9-10s'). This is a genuine catch-22: raising lookback to get a measurable sample multiplies the per-bar-type catalog loads and re-triggers the 300s sandbox SIGKILL that killed an earlier iteration. As configured the strategy can never produce a measurable full backtest. — fix: Do not pass on ~7 cycles. Resolve the loader-cost vs sample-size catch-22 before re-submitting: batch/parallelize the per-leg catalog.bars() loads (or cache the resolved weekly chain across iterations) so lookback_days can reach multiple years of weekly cycles (~200-300 cycles) without breaching the sandbox wall-clock, then set lookback_days to cover the full test window. If the loader cannot be made cheap enough to load a measurable weekly-options history within the time budget, this family is not testable in the current harness and should be redesigned (e.g. fewer, longer-dated cycles) rathe
Implementation
Delta-hedged, market-neutral short-volatility strategy harvesting the crypto variance risk premium on Deribit BTC weeklies. Each weekly expiry cycle the rolling-options framework resolves the newly-listed 1-week ATM straddle (short call + short put, same strike/expiry); the strategy sells it ONLY when the term-matched 1-week ATM implied vol — inverted by bisection from the straddle's own USD mark — exceeds trailing 10-day realized vol by vrp_ratio, and blocks the cycle entirely if no real implied reading is available (Deribit DVOL 30d is the only fallback; there is no ungated path and no underlying-only smoke trade). Both legs enter together or neither does. Residual Black-Scholes delta is re-hedged once per 1-DAY bar with the BTCUSDT.BINANCE perp whenever it exceeds hedge_band × per-leg size, and the hedge unwinds to flat when the structure closes 24h before European cash settlement. P&L is theta collected minus realized gamma cost. Exactly 2 tunables: vrp_ratio and hedge_band.
Verification Results
Verification failed (Layer 4 — QA review) [class=code_defect]:
- [edge_concern] Beyond the sample-size block: sandbox avg_trade_return_pct is -11.35% and win metrics rest on 24 trades with a Sharpe CI of [-4.36, 6.24] (metrics_reliable flagged true but the CI spans zero widely). Per-notional return is not meaningful for option legs (premium notional << underlying), so the negative figure is not itself disqualifying, but with only ~7 cycles there is no way to tell theta-harvest edge from noise. If the loader capacity is fixed and the sample grows, re-check fee viability of the daily perp re-hedge (turnover 35.98, impact 1.2%) against the theta collected.
- [critical] The rolling-options chain expansion resolves the option schedule only over [now() - lookback_days, now()] (src/agents/backtest_agent.py:44-45: start = end - timedelta(days=lookback_days), end = now()). With rolling_options.lookback_days=49 the ENTIRE tradeable option chain in ANY backtest is confined to the ~49 days ending today = ~7 weekly straddle cycles / ~14 option legs, regardless of the 6.5-year catalog. The strategy sits flat for the other ~6.4 years and trades only at the tail. This is confirmed empirically by the sandbox: bars_processed=2397 (~6.5 yrs) but data_days=43 and total_trades=24 (of which ~14 are option legs, the rest perp hedges). The variance-risk-premium edge the hypothesis targets cannot be distinguished from noise on ~7 cycles / ~14 option trades — far below the ~30-100 trades needed for a measurable options sample (recurring: options strategies pass QA then die unmeasurable at ~1-9 trades). The developer's own docstring acknowledges the cap ('lookback_days is held at 49 (7 weekly cycles / 14 option legs) because the loader issues one catalog.bars() call PER bar type and each option leg costs ~9-10s'). This is a genuine catch-22: raising lookback to get a measurable sample multiplies the per-bar-type catalog loads and re-triggers the 300s sandbox SIGKILL that killed an earlier iteration. As configured the strategy can never produce a measurable full backtest. — fix: Do not pass on ~7 cycles. Resolve the loader-cost vs sample-size catch-22 before re-submitting: batch/parallelize the per-leg catalog.bars() loads (or cache the resolved weekly chain across iterations) so lookback_days can reach multiple years of weekly cycles (~200-300 cycles) without breaching the sandbox wall-clock, then set lookback_days to cover the full test window. If the loader cannot be made cheap enough to load a measurable weekly-options history within the time budget, this family is not testable in the current harness and should be redesigned (e.g. fewer, longer-dated cycles) rathe
Iteration History
Verification failed (Layer 4 — QA review):
- Statistically unmeasurable short-vol sample, capped by the strategy's own architecture (L26). The sandbox covered only data_days=26 (~3-4 weekly Deribit expiries = 3-4 independent VRP observations); the 38 'trades' are dominated by daily perp re-hedges, not independent vol bets. The developer designed lookback_days=70 (~10 cycles) because the ~9s-per-catalog-query cost makes a full year '~105 queries and SIGKILLed', and concedes '~10 cycles cannot sample the tail that dominates a short-vol distribution' and avg_trade_return_pct 'is not meaningful for option legs'. A short-straddle's P&L lives in rare gamma-crash weeks; 3-10 benign cycles yield Sharpe 2.57 with CI -3.29..+8.68 (straddles zero) = selection noise. The design is architecturally incapable of ever sampling the ~50-100+ cycles a short-vol edge needs within the query-timeout ceiling, so the promised full-history backtest cannot deliver the statistics. Estimated meaningful trades are structurally under the ~30 floor.
- Hedge-instrument deviation: hypothesis names BTCUSDT.BINANCE as the hedge perp; code uses BTC-PERPETUAL.DERIBIT. Deliberately documented and economically sound (same margin account, same settlement index, zero extra queries); hedges the same BTC exposure with a functionally equivalent perp and does not change the edge. Defensible substitution, flagged for the record.
- IV source mismatch: hypothesis conditions on 1-WEEK IV, but the gate/hedge read Deribit DVOL (30-day forward IV). Using 30d IV as a proxy biases the richness test and BS hedge delta when weekly vs monthly vol terms diverge; and a missing DVOL sample falls through UNGATED (sells vol regardless), weakening the 'only when rich' conditioning.
Iteration History
Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit. 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).
Iteration History
Verification failed (Layer 3 — sandbox backtest): No trades produced
Bar type used: BTCUSDT.BINANCE-1-DAY-LAST-EXTERNAL, Bars processed: 2398
Diagnostics: calculate_signal() was never evaluated (0 evaluated bars) -> no primary bar reached the base template's signal path -> if you override on_bar(), call super().on_bar(bar); check bar_type matches the instrument's data. Do NOT loosen entry thresholds — the entry logic never ran.
Ensure your strategy produces trades with the given data and parameters.
Iteration History
Verification failed (Layer 1.5 — hypothesis/config consistency) [class=hypothesis_mismatch]:
- config.instrument_id is BTC-PERPETUAL.DERIBIT but the hypothesis declares BTCUSDT.BINANCE — same asset, DIFFERENT venue/contract type (fees, settlement and the mechanism's premise differ). Trade the declared instrument.
Abandon Reason
verification_loop: Verification failed (Layer 4 — QA review) [class=code_defect]:
- [edge_concern] Beyond the sample-size block: sandbox avg_trade_return_pct is -11.35% and win metrics rest on 24 trades with a Sharpe CI of [-4.36, 6.24] (metrics_reliable flagged true but the CI spans zero widely). Per-notional return is not meaningful for option legs (premium notional << underlying), so the negative figure is not itself disqualifying, but with only ~7 cycles there is no way to tell theta-harvest edge from noise. If the loader capacity is fixed and the sample grows, re-check fee viability of the daily perp re-hedge (turnover 35.98, impact 1.2%) against the theta collected.
- [critical] The rolling-options chain expansion resolves the option schedule only over [now() - lookback_days, now()] (src/agents/backtest_agent.py:44-45: start = end - timedelta(days=lookback_days), end = now()). With rolling_options.lookback_days=49 the ENTIRE tradeable option chain in ANY backtest is confined to the ~49 days ending today = ~7 weekly straddle cycles / ~14 option legs, regardless of the 6.5-year catalog. The strategy sits flat for the other ~6.4 years and trades only at the tail. This is confirmed empirically by the sandbox: bars_processed=2397 (~6.5 yrs) but data_days=43 and total_trades=24 (of which ~14 are option legs, the rest perp hedges). The variance-risk-premium edge the hypothesis targets cannot be distinguished from noise on ~7 cycles / ~14 option trades — far below the ~30-100 trades needed for a measurable options sample (recurring: options strategies pass QA then die unmeasurable at ~1-9 trades). The developer's own docstring acknowledges the cap ('lookback_days is held at 49 (7 weekly cycles / 14 option legs) because the loader issues one catalog.bars() call PER bar type and each option leg costs ~9-10s'). This is a genuine catch-22: raising lookback to get a measurable sample multiplies the per-bar-type catalog loads and re-triggers the 300s sandbox SIGKILL that killed an earlier iteration. As configured the strategy can never produce a measurable full backtest. — fix: Do not pass on ~7 cycles. Resolve the loader-cost vs sample-size catch-22 before re-submitting: batch/parallelize the per-leg catalog.bars() loads (or cache the resolved weekly chain across iterations) so lookback_days can reach multiple years of weekly cycles (~200-300 cycles) without breaching the sandbox wall-clock, then set lookback_days to cover the full test window. If the loader cannot be made cheap enough to load a measurable weekly-options history within the time budget, this family is not testable in the current harness and should be redesigned (e.g. fewer, longer-dated cycles) rathe
Backtest and paper results are hypothetical. Trading involves risk of loss.