BtcWeeklyOtmPutVrpHarvestDeribit
Hypotheses
BTC Variance-Risk-Premium Harvest via Systematic Short-Dated OTM Put Selling (Single-Leg Deribit — Sell Weekly ~15-Delta BTC Puts to Collect the Structural IV>RV Premium, Delta-Capped, Roll at Expiry, 3-Parameter)
Hypotheses
A single-leg, systematic SHORT-VOLATILITY strategy on DERIBIT BTC options that harvests the VARIANCE RISK PREMIUM — the well-documented, persistent tendency for option-implied volatility to exceed subsequently-realized volatility in crypto (sellers of insurance are paid a premium). It is deliberately the SIMPLEST tradable option structure — a SINGLE short put leg, ONE position at a time, a FIXED-delta selection rule (no multi-leg spread, no full-chain optimization scan) — precisely because prior multi-leg option hypotheses died in development on chain-scan timeouts and whole-strategy swaps. This is genuinely ORTHOGONAL to the entire exhausted directional/momentum space that dominates the portfolio (the survivor and my 8 pending variants are all BTC momentum): the edge here is the vol-premium carry, not price direction. It fills the most under-represented venue bucket by far — BINANCE_OPTIONS 0.0% and DERIBIT 4.8%, both far below the ≥15% options quota — with the correct data source (Deribit is the most liquid crypto-options venue and carries greeks/IV/settlements in the catalog). The position is delta-capped so it is a vol/theta harvest, not a disguised directional short. Priority reflects that this is a novel, uncrowded family for the factory, tempered by candid option-execution/dev-fragility risk.
Hypotheses
ONE-LINE ROOT CAUSE, FOUND AND REPRODUCED, THEN A SIX-LINE FIX. The Layer-3 zero-trade failure was not the gate, the schedule or the chain: it was the DVOL key type. `_build_dvol()` filtered the supplementary series with `if not isinstance(k, int): continue`, relying on the documented dual int+str key convention. But the sandbox passes supplementary_data INLINE to its backtest subprocess through `json.dump` (subprocess_runner.py:192), and JSON stringifies every dict key - so in Layer 3 the deribit_dvol series arrives with STRING keys ONLY. The filter therefore discarded all 47,781 observations, `_iv_at` returned None on every bar, `_vrp` was unmeasurable, `_enter_leg` refused every weekly leg, and the run reported 'No trades produced'. That also explains why iteration 2's in-process verification (int keys preserved) showed puts being sold while the sandbox showed none. I reproduced BOTH sides on the real sandbox window (833 days of BTC-PERPETUAL 1-HOUR, 19,991 bars, the live-expanded 53-leg weekly put chain), feeding supp through the identical json.dumps/loads round trip: CURRENT code -> dvol=0 samples, unmeasurable=7632, sold=0, trades_count=0 (the exact failure); PATCHED code -> dvol=47,781, gate passed 278 / skipped-cheap 23, puts sold=29, trades_count=58. The change is confined to the key-parsing loop: accept int OR str keys via int(k) and collapse duplicates through a dict. Nothing else moved - the vrp_min gate, permanent-skip semantics, delta-capped sizing, roll delegation, the _ts_of proxy-safe accessor, the no-smoke-path rule, imports and the parameter surface are byte-identical to iteration 2, so the Layer-1 and Layer-2 passes are preserved. Per the feedback I did NOT loosen any threshold; the diagnostic's 'calculate_signal never evaluated' line is expected for a RollingOptionsStrategy (the base's schedule loop replaces the signal path) and is not the defect. Honest note for the analyst: over the sandbox window the strategy is roughly break-even-to-slightly-negative (-2.9% across 29 rolls), so the VRP edge here may not clear costs once optimized - but it now genuinely trades the pre-registered mechanism, which is what this iteration had to fix.
Hypotheses
Negative expectancy on the realized $ track record (the fee-comparable unit for options): profit_factor 0.66, expectancy -$122/trade, total_return -3.43%, Sharpe -0.45, Sortino -0.06 over 28 weekly rolls. This is the structural naked-short-put payoff — 25 small premium wins (avg $266) swamped by rare fat-tail losses (avg loss $3,355, ~12.6x; return_skew -3.27, kurtosis 12.1), with two BTC drawdowns into the strike (2026-02-05, 2026-06-04) erasing the whole premium stack. QA's edge concern is borne out: the developer's own DVOL study puts the raw VRP at only +0.065 vol pts (positive 77% of weeks), which is thin versus Deribit round-trip cost and the short-vol tail. The 3 tunables cannot fix this: delta_cap only scales the book (PF unchanged), and vrp_min filtering on an already-thin 28-trade sample cannot avoid crash weeks (IV is elevated going into crashes and realized still exceeds it). No parameter region turns a PF-0.66, tail-dominated naked-short-vol payoff into positive expectancy. abandon_class=negative_expectancy.
Implementation
Systematic short-volatility carry on DERIBIT BTC options: sells one weekly ~7%-OTM (approximately 15-delta) BTC put per roll cycle and holds it into expiry, harvesting the variance risk premium rather than taking a price view. BTC-PERPETUAL 1-hour bars are only the roll clock and the realized-volatility source and are never traded. Each weekly entry is gated on the premium actually being present: Deribit's DVOL implied index is compared against realized volatility computed from the underlying's own trailing closes, and the put is sold only when implied exceeds realized by at least vrp_min vol points; a week that fails the gate is skipped permanently rather than re-offered later in the option's life. Size is set so delta-equivalent exposure equals delta_cap of equity, using Black-Scholes delta on the schedule's own strike and expiry, floored at Deribit's 0.1 contract minimum and capped at max_notional_frac of equity. Roll, contract discovery and per-leg position handling are delegated entirely to RollingOptionsStrategy. If the option chain is unavailable the strategy trades nothing - there is no underlying/directional fallback.
Verification Results
Verification failed (Layer 3 — sandbox backtest): No trades produced
Bar type used: BTC-PERPETUAL.DERIBIT-1-HOUR-LAST-EXTERNAL, Bars processed: 19991
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.
Verification Results
Align the hypothesis text to describe weekly-tenor puts driven by an hourly roll clock, or add a note that bar_type is the underlying clock, not the option tenor. No code change needed.
Verification Results
Hypothesis text says 'Weekly bars' while config.bar_type is BTC-PERPETUAL.DERIBIT-1-HOUR. This is NOT a substantive timeframe mismatch: this is a RollingOptionsStrategy where 'weekly' is the roll/tenor cadence (rolling_options.roll='weekly', dte_target=7 — correctly configured) and the 1-HOUR bars are structurally required only as the roll clock and realized-vol source for the underlying (never traded). The strategy genuinely sells weekly puts. It is a wording inconsistency in the hypothesis, not an implementation defect.
Verification Results
Consider a tighter default max_notional_frac (e.g. 0.5) or let the Risk Officer/optimizer bound it; not a correctness defect.
Verification Results
Delta-capped sizing targets delta_cap of equity but max_notional_frac defaults to 1.0, so a low-delta (~0.15) short put can be sized to ~66-83% of equity in underlying notional (sandbox avg_position_pct 82.6%). This is the source of the fat-tailed losses; it is by design and delta-bounded on the delta axis, but the notional cap is generous for a naked short option.
Verification Results
Sandbox is negative and this is a naked short-put carry with a fat left tail: total_return -2.9% over 29 rolls, profit_factor 0.72, avg_loss ($2,526) ~8.7x avg_win ($290), return_skew -3.3, largest_loss -$5,352. The deeply negative avg_trade_return_pct (-65.5%) is partly an options-notional artifact but the mechanism is net-losing on this unoptimized draw. The developer's own DVOL study shows the raw premium (mean +0.065 vol pts, spread positive 77% of weeks) is thin versus Deribit round-trip cost and short-vol tail losses. The analyst should verify at the real backtest whether the VRP genuinely clears costs, and watch max_notional (avg_position_pct 82.6% — low delta means large notional per leg, so a fast BTC drawdown into the strike drives the outsized losses seen here). Advisory only; the code is a faithful implementation.
Backtest Review
Faithful, deliberately-simple single-leg implementation; delta-capped sizing keeps it a vol harvest rather than a disguised directional short
Backtest Review
Fills an under-represented options venue and is genuinely orthogonal to the momentum-dominated portfolio
Backtest Review
High raw win rate (0.89) confirms the small-premium-collection mechanic works most weeks
Backtest Review
Net-losing on realized terms: total_return -3.43%, expectancy -$122/trade, profit_factor 0.66, Sharpe -0.45, Sortino -0.06
Backtest Review
Classic naked-short-put tail signature: avg_loss $3,355 vs avg_win $266 (~12.6x), return_skew -3.27, kurtosis 12.1 — two crash days (2026-02-05 -4.48, 2026-06-04 -5.38) erased the entire premium stack
Backtest Review
Developer's own DVOL study shows the raw premium (+0.065 vol pts, positive 77% of weeks) is thin vs Deribit round-trip cost and short-vol tail losses — the VRP does not clear costs+tail
Backtest Review
Only 28 rolls (Deribit option history caps the window) — a thin sample where two tail events dominate the result
Iteration History
Verification failed (Layer 1 — static analysis):
- Required method not implemented: should_enter (line None)
- Required method not implemented: should_exit (line None)
- Required method not implemented: position_size (line None)
Iteration History
Verification failed (Layer 2 — synthetic scenarios):
Parameters used: ['rv_bars', 'vrp_min', 'delta_cap', 'leg_sides', 'fallback_iv', 'contract_step', 'min_contracts', 'min_rv_samples', 'rolling_options', 'max_notional_frac', 'roll_buffer_hours', 'dvol_max_lag_hours']
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)
Backtest and paper results are hypothetical. Trading involves risk of loss.