Upload regime_features.py
Browse files- regime_features.py +54 -0
regime_features.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Regime Detection Features - Volatility and trend regimes"""
|
| 2 |
+
import numpy as np
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
class RegimeFeatures:
|
| 6 |
+
"""Market regime detection: bull/bear/high-vol"""
|
| 7 |
+
|
| 8 |
+
@staticmethod
|
| 9 |
+
def volatility_regime(returns, windows=[5, 21, 63]):
|
| 10 |
+
features = pd.DataFrame(index=returns.index)
|
| 11 |
+
for w in windows:
|
| 12 |
+
vol = returns.rolling(w).std() * np.sqrt(252)
|
| 13 |
+
features[f'vol_regime_{w}d'] = pd.cut(vol, bins=[0, 0.10, 0.20, 0.35, 1.0], labels=[0, 1, 2, 3]).astype(float)
|
| 14 |
+
features[f'vol_zscore_{w}d'] = (vol - vol.rolling(252).mean()) / vol.rolling(252).std().replace(0, 1)
|
| 15 |
+
return features
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def trend_regime(close):
|
| 19 |
+
features = pd.DataFrame(index=close.index)
|
| 20 |
+
for w in [21, 63, 126]:
|
| 21 |
+
ret = close.pct_change(w)
|
| 22 |
+
features[f'trend_{w}d'] = np.sign(ret)
|
| 23 |
+
features[f'trend_strength_{w}d'] = ret.abs()
|
| 24 |
+
# Composite regime
|
| 25 |
+
features['regime_composite'] = features['trend_21d'] + features['trend_63d']
|
| 26 |
+
return features
|
| 27 |
+
|
| 28 |
+
@staticmethod
|
| 29 |
+
def liquidity_regime(volume, close):
|
| 30 |
+
features = pd.DataFrame(index=volume.index)
|
| 31 |
+
dollar_vol = volume * close
|
| 32 |
+
features['dollar_vol_rank'] = dollar_vol.rolling(63).rank(pct=True)
|
| 33 |
+
features['volume_trend'] = volume.rolling(21).mean() / volume.rolling(63).mean() - 1
|
| 34 |
+
return features
|
| 35 |
+
|
| 36 |
+
@staticmethod
|
| 37 |
+
def detect_regime(returns, vol_window=21):
|
| 38 |
+
"""Simple regime: bull/bear/high-vol/neutral"""
|
| 39 |
+
trend = returns.rolling(63).mean()
|
| 40 |
+
vol = returns.rolling(vol_window).std() * np.sqrt(252)
|
| 41 |
+
vol_median = vol.median()
|
| 42 |
+
regimes = pd.Series(index=returns.index, dtype='object')
|
| 43 |
+
for i, date in enumerate(returns.index):
|
| 44 |
+
if pd.isna(trend.loc[date]) or pd.isna(vol.loc[date]):
|
| 45 |
+
regimes.loc[date] = 'unknown'
|
| 46 |
+
elif vol.loc[date] > vol_median * 1.5:
|
| 47 |
+
regimes.loc[date] = 'high_vol'
|
| 48 |
+
elif trend.loc[date] > 0.001:
|
| 49 |
+
regimes.loc[date] = 'bull'
|
| 50 |
+
elif trend.loc[date] < -0.001:
|
| 51 |
+
regimes.loc[date] = 'bear'
|
| 52 |
+
else:
|
| 53 |
+
regimes.loc[date] = 'neutral'
|
| 54 |
+
return regimes
|