spb/wp7_uqo Public
UQO Working Paper No. 7 — Options-implied information for cross-asset return and volatility prediction: evidence from 3.8B option contracts.
Python 66.5%
TeX 32.7%
Makefile 0.8%
1#!/usr/bin/env python32# =============================================================================3# Author: Simon-Pierre Boucher4# Contact: contact@spboucher.ai5# =============================================================================6"""Step 08 — Subperiod and regime analysis: stability across market7conditions.89Sections A (nine subperiods), C (252-day rolling R²) and D (pre/post COVID)10run from the processed panel. Section B (VIX-regime conditioning) needs the11raw VIX series and is skipped gracefully when the raw stores are absent.1213Inputs : data/processed/merged_options_rv.parquet14 (+ index_5min.duckdb for section B)15Outputs: results/subperiod_results.csv, results/regime_results.csv,16 results/rolling_r2.csv, results/pre_post_covid.csv17"""1819import warnings2021import pandas as pd2223import _bootstrap # noqa: F40124from wp7 import config25from wp7.data_io import RawDataUnavailableError, load_merged, load_vix_daily26from wp7.econometrics import pooled_regression_summary2728warnings.filterwarnings('ignore')2930FEATURES = config.RQ1_FEATURES31RV_MODELS = [('HAR-RV', config.HAR_FEATURES),32 ('HAR+IV', config.HAR_FEATURES + config.IV_FEATURES)]333435def main():36 print("=" * 70)37 print("SUBPERIOD & REGIME ANALYSIS")38 print("=" * 70)39 config.ensure_output_dirs()4041 df = load_merged()4243 # ── A. Subperiod analysis ──44 print("\n--- A. SUBPERIOD ANALYSIS ---")45 subperiod_results = []46 for name, (start, end) in config.SUBPERIODS.items():47 sub_data = df[(df['trade_date'] >= start) & (df['trade_date'] <= end)]48 for target, horizon in [('ret_1d', '1D'), ('ret_5d', '5D')]:49 res = pooled_regression_summary(sub_data, FEATURES, target, f"{name}|{horizon}")50 if res:51 res['subperiod'] = name52 res['horizon'] = horizon53 subperiod_results.append(res)54 print(f" {name} | {horizon}: R²={res['r2']:.6f}, "55 f"N={res['n_obs']:,}, Sig={res['n_significant']}")5657 print("\n RV Forecasting by subperiod:")58 for name, (start, end) in config.SUBPERIODS.items():59 sub_data = df[(df['trade_date'] >= start) & (df['trade_date'] <= end)]60 for model_name, feats in RV_MODELS:61 res = pooled_regression_summary(sub_data, feats, 'rv_fwd_1d',62 f"{name}|{model_name}")63 if res:64 res['subperiod'] = name65 res['model'] = model_name66 subperiod_results.append(res)67 print(f" {name} | {model_name}: R²={res['r2']:.6f}")6869 pd.DataFrame(subperiod_results).to_csv(70 config.RESULTS_DIR / "subperiod_results.csv", index=False)7172 # ── B. VIX regime analysis (needs raw VIX series) ──73 print("\n--- B. VIX REGIME ANALYSIS ---")74 try:75 vix = load_vix_daily()76 dfv = df.merge(vix, on='trade_date', how='left')77 dfv['vix_regime'] = pd.cut(dfv['vix_close'], bins=config.VIX_REGIME_BINS,78 labels=config.VIX_REGIME_LABELS)79 regime_results = []80 for regime in config.VIX_REGIME_LABELS:81 sub_data = dfv[dfv['vix_regime'] == regime]82 if len(sub_data) < 200:83 continue84 for target, horizon in [('ret_1d', '1D'), ('ret_5d', '5D'),85 ('rv_fwd_1d', 'RV_1D')]:86 feats = FEATURES if 'ret' in target else \87 config.HAR_FEATURES + config.IV_FEATURES88 res = pooled_regression_summary(sub_data, feats, target,89 f"VIX_{regime}|{horizon}")90 if res:91 res['regime'] = regime92 res['target'] = horizon93 regime_results.append(res)94 print(f" VIX {regime} | {horizon}: R²={res['r2']:.6f}, "95 f"N={res['n_obs']:,}")96 if regime_results:97 pd.DataFrame(regime_results).to_csv(98 config.RESULTS_DIR / "regime_results.csv", index=False)99 except RawDataUnavailableError as exc:100 print(f" [Section B skipped — raw stores unavailable; "101 f"existing regime_results.csv left untouched]\n {exc}")102103 # ── C. Rolling-window R² (252-day window, 63-day step) ──104 print("\n--- C. ROLLING WINDOW R² (252-day) ---")105 rolling_r2 = []106 dates_sorted = sorted(df['trade_date'].unique())107 for i in range(252, len(dates_sorted), 63):108 window_start = dates_sorted[max(0, i - 252)]109 window_end = dates_sorted[i]110 sub_data = df[(df['trade_date'] > window_start) & (df['trade_date'] <= window_end)]111112 for target, horizon in [('ret_5d', '5D_Return'), ('rv_fwd_1d', '1D_RV')]:113 feats = FEATURES if target == 'ret_5d' else \114 config.HAR_FEATURES + config.IV_FEATURES115 res = pooled_regression_summary(sub_data, feats, target, "Rolling")116 if res:117 rolling_r2.append({'date': window_end, 'target': horizon,118 'r2': res['r2'], 'n_obs': res['n_obs']})119120 rolling_df = pd.DataFrame(rolling_r2)121 rolling_df.to_csv(config.RESULTS_DIR / "rolling_r2.csv", index=False)122 print(f" {len(rolling_df)} rolling windows computed")123 for target in rolling_df['target'].unique():124 sub = rolling_df[rolling_df['target'] == target]125 print(f"\n {target}: mean R²={sub['r2'].mean():.6f}, "126 f"min={sub['r2'].min():.6f}, max={sub['r2'].max():.6f}, "127 f"std={sub['r2'].std():.6f}")128129 # ── D. Pre vs post COVID ──130 print("\n--- D. PRE vs POST COVID COMPARISON ---")131 pre_covid = df[df['trade_date'] < '2020-03-01']132 post_covid = df[df['trade_date'] >= '2020-03-01']133134 comparison_rows = []135 for period_name, period_data in [('Pre-COVID', pre_covid), ('Post-COVID', post_covid)]:136 for target, horizon in [('ret_1d', '1D'), ('ret_5d', '5D')]:137 res = pooled_regression_summary(period_data, FEATURES, target,138 f"{period_name}|{horizon}")139 if res:140 comparison_rows.append({**res, 'period': period_name, 'target': horizon})141 print(f" {period_name} | {horizon}: R²={res['r2']:.6f}")142 for model_name, feats in RV_MODELS:143 res = pooled_regression_summary(period_data, feats, 'rv_fwd_1d',144 f"{period_name}|{model_name}")145 if res:146 comparison_rows.append({**res, 'period': period_name, 'target': model_name})147 print(f" {period_name} | {model_name}: R²={res['r2']:.6f}")148149 pd.DataFrame(comparison_rows).to_csv(150 config.RESULTS_DIR / "pre_post_covid.csv", index=False)151152 print("\nSUBPERIOD & REGIME ANALYSIS COMPLETE.")153154155if __name__ == "__main__":156 main()157