# ============================================================================= # Author: Simon-Pierre Boucher # Contact: contact@spboucher.ai # ============================================================================= """Central configuration: paths, ticker universes, feature sets and constants. Every path is derived from the repository root and can be overridden through environment variables, so the pipeline runs from any checkout location: * ``WP7_RAW_DATA_DIR`` — directory holding the raw DuckDB stores (``options.duckdb``, ``stock_5min.duckdb``, ``etf_5min.duckdb``, ``index_5min.duckdb``). Defaults to ``/data/raw``. * ``WP7_RESULTS_DIR`` — output directory for result tables (defaults to ``/results``). """ import os from pathlib import Path # -------------------------------------------------------------------------- # Paths # -------------------------------------------------------------------------- PROJECT_ROOT = Path(__file__).resolve().parents[2] DATA_RAW = Path(os.environ.get("WP7_RAW_DATA_DIR", PROJECT_ROOT / "data" / "raw")) DATA_PROCESSED = PROJECT_ROOT / "data" / "processed" RESULTS_DIR = Path(os.environ.get("WP7_RESULTS_DIR", PROJECT_ROOT / "results")) FIGURES_DIR = PROJECT_ROOT / "figures" # Raw DuckDB stores (external — ~3.8B option records, 11.5B intraday bars) RAW_DB_FILES = { "options": "options.duckdb", "stocks_5min": "stock_5min.duckdb", "etfs_5min": "etf_5min.duckdb", "indices_5min": "index_5min.duckdb", } # Processed (derived) datasets MERGED_PARQUET = DATA_PROCESSED / "merged_options_rv.parquet" OPTIONS_FEATURES_PARQUET = DATA_PROCESSED / "options_features.parquet" REALIZED_VOL_PARQUET = DATA_PROCESSED / "realized_vol.parquet" CORRELATION_DIVERGENCE_PARQUET = DATA_PROCESSED / "correlation_divergence.parquet" PRICE_MAGNET_PARQUET = DATA_PROCESSED / "price_magnet_data.parquet" def ensure_output_dirs() -> None: """Create the output directories if they do not exist yet.""" for d in (DATA_PROCESSED, RESULTS_DIR, FIGURES_DIR): d.mkdir(parents=True, exist_ok=True) # -------------------------------------------------------------------------- # Ticker universes # -------------------------------------------------------------------------- MAJOR_TICKERS = [ 'AAPL', 'MSFT', 'AMZN', 'GOOG', 'GOOGL', 'TSLA', 'NVDA', 'META', 'JPM', 'BAC', 'WFC', 'GS', 'JNJ', 'UNH', 'PFE', 'XOM', 'CVX', 'HD', 'PG', 'KO', 'DIS', 'NFLX', 'INTC', 'AMD', 'CRM', 'COST', 'ABBV', 'MRK', 'TMO', 'ABT', 'V', 'MA', 'BRK_B', 'LLY', 'AVGO', 'ADBE', 'CSCO', 'PEP', 'WMT', 'MCD', 'TXN', 'QCOM', 'LOW', 'UPS', 'CAT', 'GE', 'BA', 'RTX', 'HON', 'LMT', ] ETF_TICKERS = [ 'SPY', 'QQQ', 'IWM', 'DIA', 'TLT', 'GLD', 'XLF', 'XLE', 'XLK', 'XLV', 'XLI', 'XLP', 'XLU', 'XLB', 'XLC', 'XLRE', 'XLY', ] INDEX_SYMBOLS = ['SPX', 'NDX', 'RUT', 'VIX', 'DJI'] INDEX_OPTION_TICKERS = ['SPX', 'NDX', 'RUT'] # Non-stock tickers (used to isolate the individual-stock cross-section) NON_STOCK_TICKERS = ETF_TICKERS + INDEX_SYMBOLS # 30 large S&P 500 constituents used for the implied-correlation index (RQ3) SPX_CONSTITUENTS = [ 'AAPL', 'MSFT', 'AMZN', 'GOOG', 'GOOGL', 'TSLA', 'NVDA', 'META', 'JPM', 'BAC', 'WFC', 'GS', 'JNJ', 'UNH', 'PFE', 'XOM', 'CVX', 'HD', 'PG', 'KO', 'DIS', 'NFLX', 'INTC', 'AMD', 'CRM', 'V', 'MA', 'LLY', 'AVGO', 'ADBE', ] # Liquid names used for the Greeks-decay / price-magnet analysis (RQ4) RQ4_TICKERS = ['AAPL', 'MSFT', 'AMZN', 'GOOG', 'TSLA', 'NVDA', 'META', 'JPM', 'SPY', 'QQQ'] # -------------------------------------------------------------------------- # Feature sets # -------------------------------------------------------------------------- # The ten option-implied predictors of the return-predictability regressions RQ1_FEATURES = [ 'iv_atm_30d', 'iv_term_slope', 'iv_skew_25d', 'implied_skewness', 'implied_kurtosis_proxy', 'pc_volume_ratio', 'pc_oi_ratio', 'net_gamma_exposure', 'rv_daily', 'rv_w', ] HAR_FEATURES = ['rv_lag1', 'rv_w', 'rv_m'] IV_FEATURES = ['iv_atm_30d', 'iv_term_slope', 'iv_skew_25d', 'implied_skewness', 'implied_kurtosis_proxy'] # Competing realized-variance forecasting models (RQ2) RQ2_MODELS = { 'HAR-RV': ['rv_lag1', 'rv_w', 'rv_m'], 'GARCH_proxy': ['rv_lag1', 'sq_return'], 'IV_only': ['iv_atm_30d', 'iv_atm_90d', 'iv_term_slope', 'iv_skew_25d'], 'IV_surface': ['iv_atm_30d', 'iv_atm_90d', 'iv_term_slope', 'iv_skew_25d', 'implied_skewness', 'implied_kurtosis_proxy'], 'HAR-RV + IV_surface': ['rv_lag1', 'rv_w', 'rv_m', 'iv_atm_30d', 'iv_atm_90d', 'iv_term_slope', 'iv_skew_25d', 'implied_skewness', 'implied_kurtosis_proxy'], } # Variables sorted on in the portfolio analysis SORT_VARIABLES = { 'iv_atm_30d': 'ATM IV (30d)', 'iv_skew_25d': 'Volatility Skew (25d)', 'implied_skewness': 'Implied Skewness', 'implied_kurtosis_proxy': 'Implied Kurtosis', 'pc_volume_ratio': 'Put-Call Volume Ratio', 'pc_oi_ratio': 'Put-Call OI Ratio', 'iv_term_slope': 'IV Term Structure Slope', } # -------------------------------------------------------------------------- # Sample partitions # -------------------------------------------------------------------------- SUBPERIODS = { 'Pre-GFC Recovery (2010-2012)': ('2010-01-01', '2012-12-31'), 'Bull Market (2013-2016)': ('2013-01-01', '2016-12-31'), 'Low Vol Era (2017-2018)': ('2017-01-01', '2018-12-31'), 'Pre-COVID (2019)': ('2019-01-01', '2019-12-31'), 'COVID Period (2020)': ('2020-01-01', '2020-12-31'), 'Post-COVID Bull (2021)': ('2021-01-01', '2021-12-31'), 'Rate Hiking (2022)': ('2022-01-01', '2022-12-31'), 'Recovery (2023-2024)': ('2023-01-01', '2024-12-31'), 'Recent (2025)': ('2025-01-01', '2025-12-31'), } CRISES = { 'Flash Crash (2010-05-06)': '2010-05-06', 'Euro Crisis (2011-08-05)': '2011-08-05', 'China Deval (2015-08-24)': '2015-08-24', 'Volmageddon (2018-02-05)': '2018-02-05', 'COVID Crash (2020-03-16)': '2020-03-16', 'Meme Stocks (2021-01-27)': '2021-01-27', 'Rate Shock (2022-06-13)': '2022-06-13', 'SVB Crisis (2023-03-10)': '2023-03-10', 'Aug VIX Spike (2024-08-05)': '2024-08-05', } VIX_REGIME_BINS = [0, 15, 20, 25, 35, 100] VIX_REGIME_LABELS = ['VeryLow', 'Low', 'Medium', 'High', 'Crisis'] VIX_REGIME_LABELS_VERBOSE = ['Very Low (<15)', 'Low (15-20)', 'Medium (20-25)', 'High (25-35)', 'Crisis (>35)']