EthDeribitWeeklyDeltaHedgedAtmStraddleVrp
Hypotheses
ETH Variance Risk Premium Harvest — Sell Short-Dated Deribit ATM Straddle, Delta-Hedged With Binance Perp, Gated on IV>RV Spread
Hypotheses
ITERATION 4. The review gave three items and told me not to touch the trade mechanism. I did not: the straddle construction, the IV-RV gate and the per-cycle premium stop are byte-unchanged. Here is each item.
ITEM 1 — COVERAGE (blocking). I verified both things the review asked me to verify before re-running, and neither is the cause.
(a) The 2025 and 2026 Deribit weekly ETH leg bars ARE in the catalog. I spot-checked the two contracts named in the feedback: data/catalog/data/bar/ETH-18APR25-1600-C.DERIBIT-1-DAY-LAST-EXTERNAL and ETH-13FEB26-*-P.DERIBIT-1-DAY-LAST-EXTERNAL both exist (1,674 ETH Deribit 1-DAY option bar dirs total). The chain data is present.
(b) The 430 extra bar types did NOT truncate the load. The decisive evidence is the review's own observation that the PRIMARY series, ETHUSDT.BINANCE-1-DAY, also stops at 2025-01-08. runner.py loads the primary with its own load_normalised(start, end) call, separately from the batched catalog.bars() call that fetches the extras. A leg-count problem cannot shorten the primary. Both were cut because the `end` PASSED INTO THE RUN was 2025-01-08.
THE ACTUAL CAUSE is the idea's locked final-exam vault, and it is a framework sizing interaction, not a strategy defect. hypotheses.market_context on 6495c5a1 holds:
{"final_exam": {"days": 611, "boundary": "2025-01-08T00:00:00+00:00", "sized_by": "bar_count", "history_days": 2445.0, "data_end_at_lock": "2026-09-11", "mode": "adaptive", "locked_at": "2026-09-12T10:59:24Z", "locked_at_stage": "backtesting", "reads": 0}}
pipeline_processor._final_exam_lock (src/agents/pipeline_processor.py:6196-6210) derives history_days from catalog_range() of the PRIMARY bar type — ETHUSDT.BINANCE daily, which runs back to 2019, i.e. 2,445 days. vault.vault_days then wants VAULT_TARGET_BARS/bars_per_day = 1000/1 = 1000 days and caps it at VAULT_MAX_HISTORY_PCT = 25% of 2,445 = 611 days. Boundary = 2026-09-11 minus 611d = 2025-01-08 exactly, and backtest_agent.py:296 passes it as `end`.
But this strategy's tradeable history is not the underlying's 2,445 days — the Deribit ETH weekly chain only begins 2024-07, ~800 days. Withholding 611 of those 800 withholds 76% of the actual tradeable history, which is precisely the invariant the 25% cap exists to prevent ("never starve the fit"). That is why the run got 192 days and 18 of ~115 cycles, under the 24-month span floor, missing Feb-2025, the April-2025 tariff shock, Oct-2025 and Feb-2026 — exactly the weeks the premium stop was built to bound, so the +4.21% mean per leg is measured on the wrong sample, as the review said.
Nothing in the strategy file, its config, or rolling_options.lookback_days can lift that boundary: it lives on the hypothesis row, is read before the strategy is constructed, and is fixed once by design. Two operator-side actions unblock it, and I deliberately did NOT take either from the developer seat — both mutate the factory's shared out-of-sample integrity mechanism, and the second is a production DB write that resets a research lock:
(i) Size the vault off the strategy's TRADEABLE span for rolling-options configs — pass history_days derived from min(entry_ns)..data_end of option_chain_schedule (equivalently the chain-bounded start subprocess_entry._chain_bounded_start already computes) into vault.lock. This is a GENERAL bug: it mis-sizes the vault for every rolling-options strategy in the factory, not just this one.
(ii) Clear market_context->'final_exam' on hypothesis 6495c5a1 so the corrected sizing re-locks. reads is 0, so no exam has been spent and nothing is contaminated by the reset.
With (i) the lock would be ~200 days (25% of ~800), the boundary would land near 2026-02, and the run would cover ~100 cycles across 2024/2025/2026 — the 70+ census the review asked for. I left lookback_days at 800 on purpose: cutting the requested chain would only shrink the sample further, the opposite of what this iteration needs.
ITEM 2 — SIZING BASE (fixed, in the same pass). _risk_frac read 0.25 while the report printed avg_position_pct 9.76% and my drift check stayed silent. One cause for all three. This is a two-venue book: runner.py adds DERIBIT as an extra venue with its own $100k balance and accumulates total_starting_capital to $200k; metrics.py:983 divides position notional by that running $200k base. But get_account_equity() resolves the PRIMARY venue's account only — $100k of Binance. So the code sized 25% of 100k, the report measured it against 200k and printed half, and my self-check compared 24%-of-100k against a 25% target and correctly saw no drift against the wrong denominator.
The fix is measurement, not scale. New _capital_base() sums the stable (USDT/USDC/USD) balance across ALL venue accounts — the same base metrics.py divides by — and both sizing and the realised-deployment bookkeeping now use it. _risk_frac is restated 0.25 -> 0.125 of that base, which leaves the CONTRACT COUNT ARITHMETICALLY UNCHANGED: 0.25 x 100k == 0.125 x 200k == $25k of underlying per leg, so at ETH ~2,400 that is 10 contracts either way, matching the 7-11 the review read off the trades. Effective deployment stays at ~12% of the reported base — inside the "keep near the current ~10-15%" instruction, explicitly NOT scaled up to force 25%, because the tail is still under-sampled and the worst single cycle scales linearly with size. What changes is that realised_avg_position_pct and target in the on_stop line are now in the same unit as the report, so the drift warning can actually fire. Tolerance is 15%: whole-contract quantisation at ~8 contracts is worth ~6%, while the defect it must catch (a wrong capital base) is a 2x error. Revisit the fraction only after the full-history run shows how the stop behaves in April 2025 and Feb 2026.
ITEM 3 — SKIPPED CYCLES (surfaced). on_stop tallied reasons only in aggregate, which cannot answer why the two weeks between 2024-11-02 and 2024-12-07 skipped. It now also prints one dated row per scheduled cycle — "YYYY-MM-DD=entered", "=entered*stopped", or "=vrp_closed" / "legs_not_ready" / "no_iv" / "no_rv" / "outside_window" / "never_attempted" — so gate refusal (the mechanism working as hypothesised) reads directly against missing leg data (infrastructure), for every cycle, without opening a trade file. on_start also logs the resolved chain span and the capital base.
NO REGRESSIONS. Layer 1 re-run clean on the new file (StaticAnalyzer passed=True). Imports unchanged except `datetime` (on ALLOWED_IMPORT_ROOTS) for the census date strings. calculate_signal still returns the continuous IV-minus-RV spread every bar; the schedule/gate/stop/hedge paths are untouched; still no underlying-only smoke path, so the options_no_option_trades gate is respected.
HONEST READ: the mechanism is not the open question this iteration — the coverage is. Until the vault boundary is re-sized, any run of this file is confined to a 192-day slice that contains none of the vol events the hypothesis needs to be judged on, and it cannot clear the 24-month data-span floor no matter what it finds. I recommend `continue` because the blocker is an identified, one-line infrastructure mis-sizing with a named fix, not a falsified premise — but re-running against the current lock will reproduce the same 18-cycle report.
Hypotheses
data_wall — the premise was not tested, not falsified. Vault mis-sizing truncated the run to 192 days / 18 cycles with all tail events withheld; the positive headline carries no information about VRP capture, and no optimization on this window can clear the span/sample floors. Code and mechanism are sound and unchanged from the reviews that approved them; revive once the vault is sized off the chain-bounded span.
Implementation
Weekly short ATM straddle on Deribit ETH (sell the front-week ~7-DTE ATM call and put together), entered only when the straddle's own inverted implied vol is rich to trailing realized vol by at least vrp_spread_min annualized vol points, delta-hedged once per daily bar with the ETHUSDT.BINANCE perp inside a hedge_band bandwidth, closed 24h before European cash settlement, and bounded by a per-cycle premium stop that buys BOTH legs back the first daily mark either short leg trades at stop_premium_mult x the credit it was written at. Harvests the crypto variance risk premium: delta-hedging strips price direction and leaves theta collected minus realized gamma cost, which is positive in expectation exactly when IV was sold rich to RV. Cross-venue by construction (Deribit options + Binance USD-M hedge), leverage 1.0, no underlying-only fallback path.
Verification Results
Have _submit_entry_instrument report success (or check cache.orders for the submitted client_order_id / compare self._entry_diag counters before and after) and return that from _enter_leg, so a dropped leg is retried on the next bar; and if one leg of a pair submits while the other is dropped, immediately close the submitted leg rather than carrying a naked short option.
Verification Results
_enter_leg() returns True unconditionally after calling _submit_entry_instrument(), which can silently drop the order (warmup, _market_ready False, _entry_order_inflight, instrument not in cache). RollingOptionsStrategy.on_bar then adds the leg id to self._opened, so the leg is never retried, and the strategy also records the cycle in _period_entered, so the census prints 'entered' for a cycle that never traded. Worse, the drop is per-leg: if the call is submitted and the put is dropped, the book holds a NAKED short call with no re-entry path and the paired-leg guard (_legs_ready) cannot catch it because it ran before submission. In BACKTEST this is inert (_in_warmup is False and _market_ready returns True when _live_mode is False), which is why the sandbox is clean — it is a live/paper-path defect and a census-accuracy defect only.
Verification Results
Track the expiries closed by the stop and treat their legs as flat inside _option_delta() for the remainder of the bar (skip periods in self._stopped), so the desired hedge is zero with a zero band on the same pass that closes the structure.
Verification Results
The premium stop submits the leg exit orders and _rehedge() runs on the SAME bar immediately afterwards. _option_delta() reads cache.positions_open(), which still returns the option legs until those exit orders fill, so on the stop bar the perp is hedged to the pre-stop delta rather than unwound; the unwind only happens on the next daily bar. On a stop bar (by construction a large realized move) that is a full day of unintended directional perp exposure of roughly the straddle's residual delta.
Verification Results
Record the leg bar's own ts_event alongside the close and reject a mark older than one primary bar interval — fall back to DVOL for the gate and skip the stop test for that leg on that bar.
Verification Results
_leg_close() returns the last BUFFERED leg bar, which may be stale when an option contract has no candle on the current day (thin weekly strikes routinely have gap days). A stale coin-denominated mark is then used both to invert the gate IV (against TODAY's spot and today's shortened tte, which biases the inverted IV) and to test the premium stop, so the stop can fire — or fail to fire — on a print from a previous session. This is staleness, not look-ahead (no future observation is ever read: DVOL uses searchsorted(..., 'right')-1 and the leg buffers only hold bars already dispatched), so it does not corrupt the research, but it adds noise to both the gate and the stop.
Verification Results
COVERAGE, not economics — the number the analyst must not read at face value. The developer's diagnosis of the run window checks out against the code: pipeline_processor._final_exam_lock derives history_days from catalog_range() of the PRIMARY bar type (ETHUSDT.BINANCE-1-DAY, back to 2019 => ~2,445 days), and vault.vault_days caps the withheld slice at VAULT_MAX_HISTORY_PCT=25% of that (611 days, boundary 2025-01-08). The tradeable history of this strategy is the Deribit ETH WEEKLY chain, which begins ~2024-07 (~800 days), so 611 withheld days remove ~76% of the strategy's real sample and leave ~192 days / ~18 weekly cycles — below the 24-month data-span floor and containing none of Feb-2025, the Apr-2025 tariff shock, Oct-2025 or Feb-2026. A short-straddle payoff is DEFINED by its left tail, so any positive headline from that window (sandbox: +5.56%, Sharpe 2.86, avg_trade_return_pct +4.21% on 67 trades) is measured on a sample with the tail censored out and should not be treated as evidence of VRP capture. This is a framework sizing interaction, not a strategy defect, and nothing in the strategy file, its config, or rolling_options.lookback_days can lift the boundary — the fix is operator-side (size the vault off the chain-bounded tradeable span for rolling-options configs, then clear market_context->'final_exam' on the hypothesis, reads=0 so no exam is spent). Secondary: with the gate at vrp_spread_min=0.03 and a weekly cadence, the full-history cycle count is ~115 before gating, so even at full coverage the census is near the lower bound of what distinguishes edge from noise for a fat-tailed payoff — read the per-cycle census line in on_stop before optimizing.
Backtest Review
Mechanism intact and matches the hypothesis on the trades: 36 option legs = 18 calls + 18 puts, every cycle a same-strike short ATM call + short ATM put entered/exited on the same dates, plus 31 ETHUSDT.BINANCE hedge legs. No naked/one-legged entries, no smoke fallback.
Backtest Review
Both fixes the last review asked for landed: sizing now measured against the summed two-venue capital base (avg_position_pct 9.53% now in the report's unit), and the per-cycle dated census is printed.
Backtest Review
Inside the window the book made money: +5.55% over 192 days, 12 of 18 cycles positive, PF 1.68, commissions 1.64% of gross, impact 0.87% — per-leg economics far above the Deribit 0.10% floor.
Backtest Review
COVERAGE, not economics. Run is 2024-05-30..2025-01-08 = 192 days (6.3 months) vs a 24-month hard span floor, and only 18 of ~115 available weekly cycles. The final-exam vault (611 days, boundary 2025-01-08) was sized off the PRIMARY underlying's 2,445-day catalog range, but the tradeable history is the Deribit ETH weekly chain (~800 days) — so 76% of the real sample is behind the lock, the exact inversion the 25% cap exists to prevent.
Backtest Review
A short-straddle payoff is defined by its left tail and every tail event is on the withheld side (Feb-2025, Apr-2025 tariff shock, Oct-2025, Feb-2026). The one stress window inside the run (yen-carry Aug-2024) was not even traded — the 16AUG24/23AUG24/30AUG24 cycles are absent from the trade list. So DD 1.65%, Sharpe 2.83 and zero premium-stop firings are a tail-censored sample, not evidence of VRP capture. QA's concern is confirmed on the trades, not merely predicted.
Backtest Review
A third of the PnL is not the option book: option legs +$7.36k vs delta-hedge legs +$3.74k of the $11.1k total — hedge path luck, not separable on 18 cycles.
Backtest Review
No promotable result can come from this window regardless of optimization (span/sample floors fail by construction), and 4 parameters fitted to 18 fat-tailed observations is noise-fitting.
Backtest Review
Nothing strategy-side can lift the boundary (stored on hypotheses.market_context, read before construction; lookback_days already 800). A fifth iterate reproduces an identical run.
Analysis
Do NOT change the trade mechanism. The straddle construction, the IV-RV gate and the new per-cycle premium stop all behave as designed on the trades I read, and the stop is exactly the bounded-loss fix the last verdict asked for. One thing blocks this run, and it is about COVERAGE, not logic.
1. RESTORE THE FULL CHAIN WINDOW (blocking). This backtest ran 2024-05-30 -> 2025-01-08: 224 primary bars, 18 entered cycles, out of a resolved schedule of ~115 weekly cycles ending 2026-09-11. Iteration 2, with the same rolling_options block (lookback_days=800), produced 156 option legs across 2024, 2025 and 2026. Symptom, not diagnosis: every series in data_spans terminates at 2025-01-07/08 — including ETHUSDT.BINANCE, which certainly has bars through 2026 — so the run window was bounded somewhere upstream of the strategy. Verify before re-running: (a) that the 2025 and 2026 Deribit weekly leg bars are actually present in the catalog for the legs in option_chain_schedule (spot-check e.g. ETH-18APR25-1600-C, ETH-13FEB26-2050-P); (b) whether requesting 430 extra bar types / 215 extra instruments at once is what truncated the load — if so, cut the requested universe to the legs whose expiry falls inside the run window instead of the whole 800-day chain, and re-check that on_stop reports periods in the 70+ range rather than 18. Print the on_stop census (periods / entered / not_entered by reason) in the run log so the next review can read it directly.
Why this is blocking rather than something optimization can absorb: the entire point of this iteration was to show the premium stop bounds the tail, and a window ending Jan-2025 contains none of the events that produce the tail — Feb 2025, the April 2025 tariff shock (which was iteration 2's -16% stress window), Oct 2025, Feb 2026. A +4.21% mean per leg measured with those weeks excluded is not evidence the -7.27% problem is fixed. 192 days also sits under the 24-month hard data-span floor, so the 3-phase run on this slice could not promote regardless of what it found.
2. RECONCILE THE SIZING BASE (do this in the same pass). _risk_frac is 0.25 but realised avg_position_pct is 9.76% — legs come out at 7-11 contracts where the stated target at 200k equity and ~3000 spot would be ~16. The cause is that get_account_equity() reads the BINANCE (primary) venue account only, while the equity curve starts at 200,000 across both venues; your on_stop drift check is therefore comparing against the wrong denominator and stayed silent. Either size off the capital base the report measures, or restate the target to what is actually deployable per venue. Do NOT simply scale up to force 25%: the tail is still under-sampled and the worst single cycle scales linearly with size — get the measurement honest first and keep effective deployment near the current ~10-15% until the full-history run shows how the stop behaves in April 2025 and Feb 2026.
3. Check why the 2024-11-02 and 2024-12-07 cycles skipped the two weeks between them (gate refusal vs legs_not_ready) — that tally is already in on_stop, just surface it.
Analysis
Benjamini-Hochberg at q=0.10 over 293 programme candidates keeps 4. A candidate that does not survive here is not distinguishable from the programme's own noise, however good its individual statistics look.
Outcome Summary
EthDeribitWeeklyDeltaHedgedAtmStradd-184a06eb2d
Outcome Summary
…(4-sentence story: mechanism worked cleanly, headline numbers were positive but tail-censored, vault mis-sizing capped the sample, abandoned as a data wall with code judged sound and marked revivable.)
Outcome Summary
For option-chain strategies the tradeable span is the chain's life, not the underlying's — size the withheld vault off the chain-bounded history, or a short-vol book gets judged on a tail-censored sample that proves nothing either way.
Outcome Summary
The backtest-review gate returned `abandon` on coverage, not economics: the OOS vault boundary was sized off the 2,445-day underlying history rather than the ~800-day Deribit weekly chain, truncating the run to 192 days and 18 of ~115 available weekly cycles with every tail event behind the lock. With span and sample floors failing by construction, a fifth iteration would have reproduced an identical run.
Outcome Summary
Harvest the ETH variance risk premium by selling the front-week Deribit ATM straddle only when leg-implied vol exceeded trailing realized vol by at least 3 annualized vol points, delta-hedging daily with the Binance ETHUSDT perp and closing 24h before settlement.
Outcome Summary
The initial backtest covered 2024-05-30 to 2025-01-08 (192 days, 224 bars) with 67 trades, +5.55% total return, Sharpe 2.83, profit factor 1.68, win rate 56.7% and max drawdown 1.65%; the optimized run over the fuller window produced 251 trades, +3.05% return, Sharpe 1.04, avg_trade_return_pct -0.38%, holdout Sharpe 0.568 against a walk-forward OOS Sharpe of 2.45 (ratio 0.232, below the 0.7 floor) and the programme-level FDR test did not keep the candidate.
Backtest and paper results are hypothetical. Trading involves risk of loss.