SPB Git

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%
6.8 KB · 157 lines python
Raw Blame History
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