from quantfinlab.options.fourier import direct_price, fft_grid, cos_prices, cos_density, tail_probability, model_cf
from quantfinlab.options.quote_cleaning import convert_quotes_to_usd_equivalent
from quantfinlab.options.bsm import bsm_price
from quantfinlab.dataio import load_option_chain, load_ohlcv
from quantfinlab.calibration.fft_cos import calibration_grid_quotes, calibration_weights, fit_daily_models, compare_fourier_models, family_winner, residual_by_bucket
from quantfinlab.calibration.jump_models import fixed_delta_hedge_candidates, tail_hedge_candidates, tail_hedge_schedule, hedge_score_components
from quantfinlab.backtest.overlays import run_overlay_backtest, overlay_summary, mark_book_for_schedules
from quantfinlab.plotting.options import cos_convergence, fft_fit, throughput, model_quality, daily_calibration_error, model_tournament, residual_smiles, tail_density, tail_probability_series, overlay_equity, hedge_selection, hedge_cost_payoff
btc_cache = cache_dir / "btc"
btc_cache.mkdir(parents=True, exist_ok=True)
btc_raw = load_option_chain(data_dir / "btc_options_chain.parquet", source="btc_deribit")
btc_raw = convert_quotes_to_usd_equivalent(btc_raw, contract_size=1.0)
btc_raw["date"] = pd.to_datetime(btc_raw["date"], errors="coerce").dt.normalize()
btc_raw["expiry"] = pd.to_datetime(btc_raw["expiry"], errors="coerce").dt.normalize()
btc_raw["dte_days"] = btc_raw["tau"] * 365.0
btc_raw["moneyness"] = btc_raw["strike"] / btc_raw["spot"]
btc_raw["log_moneyness"] = np.log(btc_raw["moneyness"])
btc_raw["relative_spread"] = btc_raw.get("rel_spread", (btc_raw["ask"] - btc_raw["bid"]) / btc_raw["mid"].replace(0, np.nan))
btc_raw["iv_mid"] = pd.to_numeric(btc_raw.get("iv", btc_raw.get("mark_iv")), errors="coerce")
btc_raw["rate"] = 0.04
btc_raw["dividend_yield"] = 0.0
btc_raw["discount_factor"] = np.exp(-btc_raw["rate"] * btc_raw["tau"])
btc_quotes = btc_raw[
(btc_raw["bid"] >= 0)
& (btc_raw["ask"] >= btc_raw["bid"])
& (btc_raw["mid"] > 0)
& btc_raw["dte_days"].between(3, 120)
& btc_raw["moneyness"].between(0.55, 1.60)
& btc_raw["relative_spread"].between(0, 0.50)
& btc_raw["iv_mid"].between(0.05, 3.00)
].copy()
btc_quotes["half_spread"] = 0.5 * (pd.to_numeric(btc_quotes["ask"], errors="coerce") - pd.to_numeric(btc_quotes["bid"], errors="coerce"))
btc_quotes["calib_scale_px"] = np.maximum.reduce([
btc_quotes["half_spread"].to_numpy(float),
0.001 * pd.to_numeric(btc_quotes["mid"], errors="coerce").to_numpy(float),
np.full(len(btc_quotes), 5.0),
])
btc_quotes["obs_weight"] = 1.0 / (btc_quotes["relative_spread"].clip(lower=0.01) ** 2 + 0.05 ** 2)
btc_quotes["obs_weight"] = btc_quotes["obs_weight"] / btc_quotes["obs_weight"].median()
btc_quotes["contract_key"] = btc_quotes["option_type"].astype(str) + "_" + btc_quotes["expiry"].dt.strftime("%Y-%m-%d") + "_" + btc_quotes["strike"].round(4).astype(str)
btc_spot = load_ohlcv(data_dir / "btc_usd_ohlcv.csv", fields=("close",))
btc_calib_path = btc_cache / "btc_calibration_grid_quotes.parquet"
btc_steps_path = btc_cache / "btc_calibration_grid_steps.parquet"
btc_calib_settings = {"version": 2, "rows": int(len(btc_quotes)), "min_quotes_per_date": 100, "max_quotes_per_date": 160}
if btc_calib_path.exists() and btc_steps_path.exists() and same_settings(btc_calib_path, btc_calib_settings):
btc_calibration = pd.read_parquet(btc_calib_path)
btc_selection_steps = pd.read_parquet(btc_steps_path)
else:
btc_calibration, btc_selection_steps = calibration_grid_quotes(
btc_quotes,
min_dte=7.0,
max_dte=120.0,
min_vega=0.0,
max_relative_spread=0.50,
max_abs_log_moneyness=0.45,
min_quotes_per_expiry=6,
min_expiries_per_date=4,
dte_targets=(7.0, 14.0, 21.0, 30.0, 45.0, 60.0, 75.0, 90.0, 120.0),
k_targets=(-0.40, -0.32, -0.25, -0.18, -0.12, -0.08, -0.04, 0.0, 0.04, 0.08, 0.12, 0.18, 0.25, 0.32, 0.40),
min_quotes_per_date=100,
max_quotes_per_date=160,
return_steps=True,
)
btc_calibration.to_parquet(btc_calib_path, index=False)
btc_selection_steps.to_parquet(btc_steps_path, index=False)
write_meta(btc_calib_path, btc_calib_settings, len(btc_calibration))
btc_dates = btc_calibration.groupby("date").size()
btc_fit_dates = btc_dates[btc_dates >= 80].index
btc_sv_dates = btc_fit_dates[::3]
btc_jump_path = btc_cache / "btc_jump_daily_fits_scale_v2.parquet"
btc_sv_path = btc_cache / "btc_sv_daily_fits_scale_v2.parquet"
if btc_jump_path.exists():
btc_jump_daily = pd.read_parquet(btc_jump_path)
btc_jump_fit = pd.read_parquet(btc_cache / "btc_jump_daily_fit_prices_scale_v2.parquet")
else:
out = fit_daily_models(btc_calibration, ["merton", "vg"], calibration_dates=btc_fit_dates, min_quotes=80, max_nfev=45, engine="cpp", n_terms=128, truncation_width=16.0)
btc_jump_daily, btc_jump_fit = out["params"], out["fit"]
btc_jump_daily.to_parquet(btc_jump_path, index=False)
btc_jump_fit.to_parquet(btc_cache / "btc_jump_daily_fit_prices_scale_v2.parquet", index=False)
if btc_sv_path.exists():
btc_sv_daily = pd.read_parquet(btc_sv_path)
btc_sv_fit = pd.read_parquet(btc_cache / "btc_sv_daily_fit_prices_scale_v2.parquet")
else:
out = fit_daily_models(btc_calibration, ["heston", "bates"], calibration_dates=btc_sv_dates, min_quotes=80, max_nfev=40, engine="cpp", n_terms=112, truncation_width=16.0)
btc_sv_daily, btc_sv_fit = out["params"], out["fit"]
btc_sv_daily.to_parquet(btc_sv_path, index=False)
btc_sv_fit.to_parquet(btc_cache / "btc_sv_daily_fit_prices_scale_v2.parquet", index=False)
btc_jump_summary = compare_fourier_models(daily=btc_jump_daily)
btc_sv_summary = compare_fourier_models(daily=btc_sv_daily)
btc_jump_winner = family_winner(btc_jump_summary)
btc_sv_winner = family_winner(btc_sv_summary)
btc_bsm_path = btc_cache / "btc_bsm_daily_fits_scale_v2.parquet"
if btc_bsm_path.exists():
btc_bsm_daily = pd.read_parquet(btc_bsm_path)
btc_bsm_fit = pd.read_parquet(btc_cache / "btc_bsm_daily_fit_prices_scale_v2.parquet")
else:
out = fit_daily_models(btc_calibration, ["bsm"], calibration_dates=btc_fit_dates, min_quotes=80, max_nfev=25, engine="cpp", n_terms=112, truncation_width=14.0)
btc_bsm_daily, btc_bsm_fit = out["params"], out["fit"]
btc_bsm_daily.to_parquet(btc_bsm_path, index=False)
btc_bsm_fit.to_parquet(btc_cache / "btc_bsm_daily_fit_prices_scale_v2.parquet", index=False)
btc_final_daily = pd.concat([btc_bsm_daily, btc_jump_daily[btc_jump_daily["model"].eq(btc_jump_winner)], btc_sv_daily[btc_sv_daily["model"].eq(btc_sv_winner)]], ignore_index=True)
btc_final_comparison = compare_fourier_models(daily=btc_final_daily)
btc_fit_prices = pd.concat([btc_bsm_fit, btc_jump_fit[btc_jump_fit["model"].eq(btc_jump_winner)], btc_sv_fit[btc_sv_fit["model"].eq(btc_sv_winner)]], ignore_index=True)
btc_residuals = residual_by_bucket(btc_fit_prices)
btc_sample = btc_quotes.iloc[:80].copy()
spot0 = float(btc_sample["spot"].median())
rate0 = float(btc_sample["rate"].median())
tau0 = float(btc_sample["tau"].median())
strikes = np.linspace(0.70, 1.30, 1000) * spot0
closed = np.asarray(bsm_price("call", spot0, strikes, tau0, float(btc_sample["iv_mid"].median()), rate0, 0.0), dtype=float)
btc_engine_settings = {"version": 2, "spot": round(spot0, 6), "tau": round(tau0, 8), "rows": int(len(btc_sample))}
btc_fft_path = btc_cache / "btc_fft_validation_grid.parquet"
btc_cos_path = btc_cache / "btc_cos_validation_grid.parquet"
btc_fft_fit_path = btc_cache / "btc_fft_fit_grid.parquet"
if btc_fft_path.exists() and btc_cos_path.exists() and btc_fft_fit_path.exists() and same_settings(btc_fft_path, btc_engine_settings):
btc_fft_validation = pd.read_parquet(btc_fft_path)
btc_cos_validation = pd.read_parquet(btc_cos_path)
btc_fft_fit = pd.read_parquet(btc_fft_fit_path)
else:
fft_rows = []
fft_fit_rows = []
for engine in ["numba", "cpp"]:
for n in [4096, 8192, 16384]:
t0 = time.perf_counter()
grid = fft_grid("bsm", [float(btc_sample["iv_mid"].median())], spot0, rate0, 0.0, tau0, n=n, eta=0.35, alpha=1.5, engine=engine)
runtime = time.perf_counter() - t0
interp = np.interp(strikes, grid["strike"], grid["price"])
fft_rows.append({"engine": engine, "n": n, "runtime_sec": runtime, "median_abs_error": float(np.median(np.abs(interp - closed))), "max_abs_error": float(np.max(np.abs(interp - closed)))})
if engine == "cpp" and n == 16384:
fft_fit_rows.extend([{"strike": float(k), "moneyness": float(k / spot0), "fft_price": float(px), "reference_price": float(ref), "abs_error": float(abs(px - ref))} for k, px, ref in zip(strikes, interp, closed)])
btc_fft_validation = pd.DataFrame(fft_rows)
cos_rows = []
for model, params, width in [("bsm", [float(btc_sample["iv_mid"].median())], 12.0), ("merton", [0.65, 0.60, -0.08, 0.35], 16.0), ("vg", [0.65, -0.10, 0.35], 30.0), ("heston", [0.45, 2.0, 0.45, 0.80, -0.45], 16.0), ("bates", [0.45, 2.0, 0.45, 0.80, -0.45, 0.60, -0.08, 0.35], 16.0)]:
ref = cos_prices(model, params, strikes, np.full_like(strikes, tau0), spot0, rate0, 0.0, n_terms=512, truncation_width=width, option_type="call", engine="numba")
for n_terms in [32, 64, 128, 256]:
num = cos_prices(model, params, strikes, np.full_like(strikes, tau0), spot0, rate0, 0.0, n_terms=n_terms, truncation_width=width, option_type="call", engine="numba")
t0 = time.perf_counter()
cpp = cos_prices(model, params, strikes, np.full_like(strikes, tau0), spot0, rate0, 0.0, n_terms=n_terms, truncation_width=width, option_type="call", engine="cpp")
runtime = time.perf_counter() - t0
cos_rows.append({"model": model, "n_terms": n_terms, "runtime_sec": runtime, "items": len(strikes), "max_abs_error": float(np.max(np.abs(num - ref))), "median_abs_error": float(np.median(np.abs(num - ref))), "cpp_numba_diff": float(np.max(np.abs(num - cpp)))})
btc_cos_validation = pd.DataFrame(cos_rows)
btc_fft_fit = pd.DataFrame(fft_fit_rows)
btc_fft_validation.to_parquet(btc_fft_path, index=False)
btc_cos_validation.to_parquet(btc_cos_path, index=False)
btc_fft_fit.to_parquet(btc_fft_fit_path, index=False)
write_meta(btc_fft_path, btc_engine_settings, len(btc_fft_validation))
btc_throughput = pd.concat([
btc_fft_validation.assign(label=btc_fft_validation["engine"] + " FFT", items=btc_fft_validation["n"]),
btc_cos_validation.assign(label="cpp COS", items=btc_cos_validation["items"]),
], ignore_index=True)
btc_model_quality = btc_final_comparison.copy()
btc_tail_path = btc_cache / "btc_tail_series_grid.parquet"
btc_density_path = btc_cache / "btc_density_grid.parquet"
btc_tail_settings = {"version": 2, "jump": btc_jump_winner, "sv": btc_sv_winner, "dates": int(btc_final_daily["date"].nunique())}
if btc_tail_path.exists() and btc_density_path.exists() and same_settings(btc_tail_path, btc_tail_settings):
btc_tail_series = pd.read_parquet(btc_tail_path)
btc_density = pd.read_parquet(btc_density_path)
else:
tail_rows = []
density_rows = []
for d in pd.DatetimeIndex(sorted(btc_final_daily["date"].drop_duplicates()))[::max(1, len(btc_final_daily["date"].drop_duplicates()) // 18)]:
day = btc_quotes[btc_quotes["date"].eq(pd.Timestamp(d).normalize())]
if day.empty:
continue
s = float(day["spot"].median())
r = float(day["rate"].median())
x = np.linspace(np.log(s) - 0.9, np.log(s) + 0.6, 520)
for model in ["bsm", btc_jump_winner, btc_sv_winner]:
source = btc_final_daily[btc_final_daily["model"].eq(model)]
source = source[source["date"].le(pd.Timestamp(d).normalize())].sort_values("date")
if source.empty:
continue
params = source.iloc[-1][[c for c in source.columns if c.startswith("p")]].dropna().to_numpy(float)
den = cos_density(model, params, x, s, r, 0.0, 45 / 365.0)
rel = np.exp(x) / s
probs = {}
for level in [0.90, 0.85, 0.80]:
mask = rel <= level
probs[level] = float(np.trapezoid(den[mask], x[mask])) if mask.any() else 0.0
tail_rows.append({"date": pd.Timestamp(d).normalize(), "model": model, "level": level, "left_tail_probability": probs[level], "tail_probability": probs[level], "tail_prob_80": probs[0.80] if level == 0.80 else np.nan})
if len(density_rows) < 5000:
density_rows.extend([{"date": pd.Timestamp(d).normalize(), "model": model, "x": float(xi), "density": float(yi)} for xi, yi in zip(rel, den)])
btc_tail_series = pd.DataFrame(tail_rows)
btc_density = pd.DataFrame(density_rows)
btc_tail_series.to_parquet(btc_tail_path, index=False)
btc_density.to_parquet(btc_density_path, index=False)
write_meta(btc_tail_path, btc_tail_settings, len(btc_tail_series))
btc_tail_wide = btc_tail_series.pivot_table(index=["date", "model"], columns="level", values="tail_probability").reset_index().rename(columns={0.9: "p90", 0.85: "p85", 0.8: "p80"})
btc_tail_plot = btc_tail_series[btc_tail_series["level"].eq(0.80)].copy()
hedge_input = btc_quotes[btc_quotes["option_type"].astype(str).str.lower().str.startswith("p") & btc_quotes["dte_days"].between(21, 120) & btc_quotes["moneyness"].between(0.70, 0.98) & btc_quotes["relative_spread"].le(0.40)].copy()
btc_hedge_prices_path = btc_cache / "btc_hedge_prices_grid.parquet"
btc_hedge_price_settings = {"version": 4, "rows": int(len(hedge_input)), "jump": btc_jump_winner, "sv": btc_sv_winner, "engine": "cpp date batches", "scale": "usd floor v2"}
if btc_hedge_prices_path.exists() and same_settings(btc_hedge_prices_path, btc_hedge_price_settings):
hedge_input = pd.read_parquet(btc_hedge_prices_path)
else:
hedge_input["jump_price"] = np.nan
hedge_input["sv_price"] = np.nan
for price_col, model, daily, width in [("jump_price", btc_jump_winner, btc_jump_daily, 30.0 if btc_jump_winner == "vg" else 16.0), ("sv_price", btc_sv_winner, btc_sv_daily, 16.0)]:
values = np.full(len(hedge_input), np.nan)
daily_model = daily[daily["model"].eq(model)].copy()
for d, idx in hedge_input.groupby("date", sort=True).groups.items():
source = daily_model[daily_model["date"].le(pd.Timestamp(d).normalize())].sort_values("date")
if source.empty:
continue
params = source.iloc[-1][[c for c in source.columns if c.startswith("p")]].dropna().to_numpy(float)
g = hedge_input.loc[idx]
values[hedge_input.index.get_indexer(idx)] = cos_prices(model, params, g["strike"].to_numpy(float), g["tau"].to_numpy(float), float(g["spot"].median()), float(g["rate"].median()), 0.0, option_type="put", n_terms=112, truncation_width=width, engine="cpp")
hedge_input[price_col] = values
hedge_input.to_parquet(btc_hedge_prices_path, index=False)
write_meta(btc_hedge_prices_path, btc_hedge_price_settings, len(hedge_input))
hedge_input = hedge_input.dropna(subset=["jump_price", "sv_price"]).copy()
hedge_input["model_uncertainty"] = (hedge_input["jump_price"] - hedge_input["sv_price"]).abs() / hedge_input["spot"].clip(lower=1e-8)
def btc_tail_for(model, d):
q = btc_tail_wide[btc_tail_wide["model"].eq(model)].copy()
q = q[q["date"].le(pd.Timestamp(d).normalize())].sort_values("date")
if q.empty:
return pd.Series({"p90": 0.12, "p85": 0.07, "p80": 0.04})
return q.iloc[-1][["p90", "p85", "p80"]].fillna({"p90": 0.12, "p85": 0.07, "p80": 0.04})
def btc_budget_for(model, d, edge_ratio=0.0, relative_spread=0.0, base_bps=35.0):
q = btc_tail_wide[btc_tail_wide["model"].eq(model)].copy()
q = q[q["date"].le(pd.Timestamp(d).normalize())].sort_values("date")
if len(q) < 8:
return base_bps
hist = q["p80"].tail(60).dropna()
if hist.empty:
return base_bps
p = float(hist.iloc[-1])
edge = float(edge_ratio) if np.isfinite(edge_ratio) else 0.0
spread = float(relative_spread) if np.isfinite(relative_spread) else 1.0
add = 0.0
if spread <= 0.30 and p > float(hist.quantile(0.70)) and edge > -0.05:
add += 25.0
if spread <= 0.30 and p > float(hist.quantile(0.85)) and edge > 0.0:
add += 40.0
if spread <= 0.30 and p > float(hist.quantile(0.95)) and edge > 0.0:
add += 60.0
return min(base_bps + add, 120.0)
def with_tail_budget(q, model, gated=False):
out = q.copy()
vals = out["date"].map(lambda d: btc_tail_for(model, d))
out["p90"] = [float(v["p90"]) for v in vals]
out["p85"] = [float(v["p85"]) for v in vals]
out["p80"] = [float(v["p80"]) for v in vals]
if gated:
edge = pd.to_numeric(out.get("model_edge", 0.0), errors="coerce").fillna(0.0) / pd.to_numeric(out.get("mid", 1.0), errors="coerce").replace(0.0, np.nan).fillna(1.0)
spread = pd.to_numeric(out.get("relative_spread", 1.0), errors="coerce").fillna(1.0)
out["budget_bps"] = [
btc_budget_for(model, d, edge_ratio=e, relative_spread=sp)
for d, e, sp in zip(out["date"], edge, spread)
]
else:
out["budget_bps"] = 35.0
return out
fixed = fixed_delta_hedge_candidates(with_tail_budget(hedge_input, btc_sv_winner, False), top_n=None, top_n_per_date=1)
jump_fixed = tail_hedge_candidates(with_tail_budget(hedge_input.assign(model_price=hedge_input["jump_price"], model_edge=hedge_input["jump_price"] - hedge_input["mid"]), btc_jump_winner, False), top_n=None, top_n_per_date=1, crash_level=0.85)
jump_gated = tail_hedge_candidates(with_tail_budget(hedge_input.assign(model_price=hedge_input["jump_price"], model_edge=hedge_input["jump_price"] - hedge_input["mid"]), btc_jump_winner, True), top_n=None, top_n_per_date=1, crash_level=0.85)
sv_fixed = tail_hedge_candidates(with_tail_budget(hedge_input.assign(model_price=hedge_input["sv_price"], model_edge=hedge_input["sv_price"] - hedge_input["mid"]), btc_sv_winner, False), top_n=None, top_n_per_date=1, crash_level=0.80)
sv_gated = tail_hedge_candidates(with_tail_budget(hedge_input.assign(model_price=hedge_input["sv_price"], model_edge=hedge_input["sv_price"] - hedge_input["mid"]), btc_sv_winner, True), top_n=None, top_n_per_date=1, crash_level=0.80)
risk_price = 0.5 * (hedge_input["jump_price"] + hedge_input["sv_price"])
risk_edge = risk_price - hedge_input["mid"] - 0.5 * (hedge_input["jump_price"] - hedge_input["sv_price"]).abs()
risk_frame = hedge_input[hedge_input["moneyness"].le(0.93)].copy()
risk_price = 0.5 * (risk_frame["jump_price"] + risk_frame["sv_price"])
risk_edge = risk_price - risk_frame["mid"] - 0.5 * (risk_frame["jump_price"] - risk_frame["sv_price"]).abs()
risk_frame = risk_frame.assign(model_price=risk_price, model_edge=risk_edge, calibration_penalty=0.0)
risk_fixed = tail_hedge_candidates(with_tail_budget(risk_frame, btc_sv_winner, False), top_n=None, top_n_per_date=1, crash_level=0.80)
risk_gated = tail_hedge_candidates(with_tail_budget(risk_frame, btc_sv_winner, True), top_n=None, top_n_per_date=1, crash_level=0.80)
btc_hedge_candidates = pd.concat([
fixed.assign(selector="fixed_delta_35bps"),
jump_fixed.assign(selector=f"{btc_jump_winner}_35bps"),
jump_gated.assign(selector=f"{btc_jump_winner}_tail_gated"),
sv_fixed.assign(selector=f"{btc_sv_winner}_35bps"),
sv_gated.assign(selector=f"{btc_sv_winner}_tail_gated"),
risk_fixed.assign(selector="model_risk_35bps"),
risk_gated.assign(selector="model_risk_tail_gated"),
], ignore_index=True)
btc_schedules = {}
for selector, group in btc_hedge_candidates.groupby("selector"):
sched = tail_hedge_schedule(group, spacing_days=21, budget_notional=1_000_000.0, premium_budget_bps=35.0, budget_col="budget_bps", contract_multiplier=1.0, label="tail_put")
btc_schedules[selector] = sched
btc_schedules["unhedged"] = pd.DataFrame(columns=["entry_date", "contract_key", "expiry", "strike", "option_type", "quantity", "label"])
btc_underlying = btc_spot["close"].reindex(pd.DatetimeIndex(sorted(btc_quotes["date"].unique()))).ffill()
btc_shares = 1_000_000.0 / float(btc_underlying.dropna().iloc[0])
btc_hedge_book = mark_book_for_schedules(btc_quotes, btc_schedules)
btc_hedge_nav_path = btc_cache / "btc_hedge_nav_grid.parquet"
btc_hedge_trades_path = btc_cache / "btc_hedge_trades_grid.parquet"
btc_hedge_settings = {"version": 8, "schedule_rows": int(sum(len(x) for x in btc_schedules.values())), "mark_rows": int(len(btc_hedge_book)), "base_budget_bps": 35.0, "tail_gated_budget": "price_aware", "risk_penalty": 0.0, "risk_moneyness_max": 0.93, "spacing_days": 21}
if btc_hedge_nav_path.exists() and btc_hedge_trades_path.exists() and same_settings(btc_hedge_nav_path, btc_hedge_settings):
btc_hedge_nav = pd.read_parquet(btc_hedge_nav_path).set_index("date")
btc_hedge_nav.index = pd.to_datetime(btc_hedge_nav.index, errors="coerce").normalize()
if not btc_hedge_nav.empty and float((btc_hedge_nav.iloc[0] - 1_000_000.0).abs().max()) > 1e-8:
btc_hedge_nav = pd.concat([pd.DataFrame({c: 1_000_000.0 for c in btc_hedge_nav.columns}, index=[btc_hedge_nav.index.min()]), btc_hedge_nav])
btc_hedge_trades = pd.read_parquet(btc_hedge_trades_path)
btc_hedge_results = {"nav": btc_hedge_nav, "drawdown": btc_hedge_nav / btc_hedge_nav.cummax() - 1.0, "trades": btc_hedge_trades}
else:
btc_hedge_results = run_overlay_backtest(btc_schedules, btc_hedge_book, btc_underlying, initial_nav=1_000_000.0, contract_multiplier=1.0, shares=btc_shares)
btc_hedge_results["nav"].reset_index(names="date").to_parquet(btc_hedge_nav_path, index=False)
btc_hedge_results["trades"].to_parquet(btc_hedge_trades_path, index=False)
write_meta(btc_hedge_nav_path, btc_hedge_settings, len(btc_hedge_results["nav"]))
btc_hedge_summary = overlay_summary(btc_hedge_results)
btc_trade_check = btc_hedge_results["trades"].copy()
if btc_trade_check.empty:
btc_hedge_check = pd.DataFrame([{"rows": 0, "open_trades": 0, "mark_rows": len(btc_hedge_book), "max_quantity": 0.0, "median_open_price": np.nan, "premium_spent": 0.0}])
else:
btc_open = btc_trade_check[btc_trade_check["event"].eq("open")].copy()
btc_hedge_check = btc_open.groupby("strategy", as_index=False).agg(open_trades=("event", "size"), max_quantity=("quantity", "max"), median_quantity=("quantity", "median"), median_open_price=("price", "median"), premium_spent=("cashflow", lambda x: -float(np.sum(x))), median_moneyness=("moneyness", "median"), median_dte=("dte_days", "median"))
btc_hedge_check["mark_rows"] = len(btc_hedge_book)
display(pd.DataFrame([{"btc_rows": len(btc_quotes), "calibration_rows": len(btc_calibration), "calibration_dates": btc_calibration["date"].nunique(), "avg_quotes_per_date": btc_dates.mean(), "jump_winner": btc_jump_winner, "sv_winner": btc_sv_winner}]).round(4))
display(btc_selection_steps)
display(btc_final_comparison.round(6))
display(btc_hedge_check.round(4))
display(btc_hedge_summary.round(6))
fig, axes = plt.subplots(3, 4, figsize=(22, 15))
axes = axes.ravel()
cos_convergence(axes[0], btc_cos_validation, "COS Convergence")
fft_fit(axes[1], btc_fft_fit, "FFT Fit")
throughput(axes[2], btc_throughput, "Throughput")
model_quality(axes[3], btc_model_quality, "Model Quality")
daily_calibration_error(axes[4], btc_jump_daily, "Jump RMSE")
daily_calibration_error(axes[5], btc_sv_daily, "SV RMSE")
model_tournament(axes[6], btc_final_comparison, "Model Tournament")
residual_smiles(axes[7], btc_residuals.assign(x=np.arange(len(btc_residuals))), "Residual Smile")
tail_density(axes[8], btc_density.rename(columns={"x": "x"}), "Tail Density")
tail_probability_series(axes[9], btc_tail_plot, "Tail Probability")
overlay_equity(axes[10], btc_hedge_results, "Hedge NAV")
hedge_cost_payoff(axes[11], btc_hedge_results["trades"], "Hedge Cashflow")
fig.tight_layout(pad=1.25, h_pad=1.0, w_pad=1.0)
plt.show()