| """Macro Features - FRED data, yield curve, VIX, credit spreads""" |
| import numpy as np |
| import pandas as pd |
| from typing import List |
|
|
| class MacroFeatures: |
| """Macroeconomic and sentiment overlay features""" |
| |
| @staticmethod |
| def fetch_fred_data(series_ids, start='2020-01-01', end='2024-01-01'): |
| """Fetch macro data from FRED. Series: DGS10, DGS2, T10Y2Y, VIXCLS, UNRATE, BAA10YM""" |
| try: |
| from fredapi import Fred |
| import os |
| fred = Fred(api_key=os.environ.get('FRED_API_KEY', '')) |
| data = {} |
| for sid in series_ids: |
| try: |
| data[sid] = fred.get_series(sid, observation_start=start, observation_end=end) |
| except Exception as e: |
| print(f"FRED {sid}: {e}") |
| return pd.DataFrame(data) |
| except ImportError: |
| return MacroFeatures._synthetic_macro(start, end) |
| |
| @staticmethod |
| def _synthetic_macro(start='2020-01-01', end='2024-01-01'): |
| dates = pd.bdate_range(start=start, end=end) |
| n = len(dates) |
| np.random.seed(42) |
| level = np.cumsum(np.random.normal(0, 0.01, n)) + 2.0 |
| return pd.DataFrame({ |
| 'DGS10': level + np.random.normal(0, 0.05, n), |
| 'DGS2': level * 0.6 + np.random.normal(0, 0.03, n), |
| 'T10Y2Y': level * 0.4 + np.random.normal(0, 0.02, n), |
| 'VIXCLS': 18 + np.random.normal(0, 3, n).clip(10, 45), |
| 'BAA10YM': 2.5 + np.random.normal(0, 0.2, n), |
| }, index=dates) |
| |
| @staticmethod |
| def yield_curve_features(treasury_10y, treasury_2y): |
| features = pd.DataFrame(index=treasury_10y.index) |
| features['yc_spread'] = treasury_10y - treasury_2y |
| features['yc_slope'] = features['yc_spread'].diff(21) |
| features['yc_inversion'] = (features['yc_spread'] < 0).astype(float) |
| features['yc_zscore'] = (features['yc_spread'] - features['yc_spread'].rolling(252).mean()) / features['yc_spread'].rolling(252).std().replace(0, 1) |
| return features |
| |
| @staticmethod |
| def vix_features(vix): |
| features = pd.DataFrame(index=vix.index) |
| features['vix_level'] = vix |
| features['vix_change'] = vix.pct_change() |
| features['vix_zscore'] = (vix - vix.rolling(63).mean()) / vix.rolling(63).std().replace(0, 1) |
| features['vix_term'] = vix.rolling(5).mean() - vix.rolling(21).mean() |
| return features |
| |
| @staticmethod |
| def credit_spread_features(spread): |
| features = pd.DataFrame(index=spread.index) |
| features['credit_spread'] = spread |
| features['credit_change'] = spread.diff(21) |
| features['credit_zscore'] = (spread - spread.rolling(252).mean()) / spread.rolling(252).std().replace(0, 1) |
| return features |