gaurv007 commited on
Commit
713381e
·
verified ·
1 Parent(s): c607dda

Upload alpha_factory/deterministic/regime_tagger.py with huggingface_hub

Browse files
alpha_factory/deterministic/regime_tagger.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Regime Tagger — deterministic regime classification for each historical year.
3
+ Tags years with vol/trend/rate/style regimes for the Performance Surgeon.
4
+ """
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class RegimeProfile:
10
+ """Regime classification for a single year."""
11
+ year: int
12
+ vol_regime: str # "low" (<15 VIX), "mid" (15-25), "high" (>25)
13
+ trend_regime: str # "bull" (SPY 12-1 mom > 0), "bear" (< 0)
14
+ rate_regime: str # "steepening", "flattening"
15
+ style_regime: str # "value" or "growth" leadership
16
+
17
+
18
+ # Historical regime data (2019-2024) — hardcoded from public market data
19
+ # In production, compute from VIX/SPY/10Y/Russell indices
20
+ HISTORICAL_REGIMES = {
21
+ 2019: RegimeProfile(2019, "low", "bull", "flattening", "growth"),
22
+ 2020: RegimeProfile(2020, "high", "bull", "flattening", "growth"),
23
+ 2021: RegimeProfile(2021, "low", "bull", "steepening", "growth"),
24
+ 2022: RegimeProfile(2022, "high", "bear", "steepening", "value"),
25
+ 2023: RegimeProfile(2023, "mid", "bull", "steepening", "growth"),
26
+ 2024: RegimeProfile(2024, "low", "bull", "flattening", "growth"),
27
+ }
28
+
29
+
30
+ def get_regime(year: int) -> RegimeProfile:
31
+ """Get regime profile for a year."""
32
+ return HISTORICAL_REGIMES.get(year, RegimeProfile(year, "mid", "bull", "flattening", "growth"))
33
+
34
+
35
+ def tag_yearly_regimes(yearly_sharpe: list[float], start_year: int = 2019) -> list[dict]:
36
+ """
37
+ Tag each year's Sharpe with its regime context.
38
+ Returns list of {year, sharpe, vol_regime, trend_regime, ...}
39
+ """
40
+ tagged = []
41
+ for i, sharpe in enumerate(yearly_sharpe):
42
+ year = start_year + i
43
+ regime = get_regime(year)
44
+ tagged.append({
45
+ "year": year,
46
+ "sharpe": sharpe,
47
+ "vol_regime": regime.vol_regime,
48
+ "trend_regime": regime.trend_regime,
49
+ "rate_regime": regime.rate_regime,
50
+ "style_regime": regime.style_regime,
51
+ })
52
+ return tagged
53
+
54
+
55
+ def detect_regime_dependency(yearly_sharpe: list[float], start_year: int = 2019) -> dict:
56
+ """
57
+ Detect if alpha performance is regime-dependent.
58
+ Returns analysis of which regimes it works/fails in.
59
+ """
60
+ tagged = tag_yearly_regimes(yearly_sharpe, start_year)
61
+
62
+ # Group by regimes
63
+ vol_performance = {"low": [], "mid": [], "high": []}
64
+ trend_performance = {"bull": [], "bear": []}
65
+ style_performance = {"value": [], "growth": []}
66
+
67
+ for t in tagged:
68
+ vol_performance[t["vol_regime"]].append(t["sharpe"])
69
+ trend_performance[t["trend_regime"]].append(t["sharpe"])
70
+ style_performance[t["style_regime"]].append(t["sharpe"])
71
+
72
+ def avg(lst):
73
+ return sum(lst) / len(lst) if lst else 0
74
+
75
+ analysis = {
76
+ "vol_sensitivity": {k: round(avg(v), 3) for k, v in vol_performance.items() if v},
77
+ "trend_sensitivity": {k: round(avg(v), 3) for k, v in trend_performance.items() if v},
78
+ "style_sensitivity": {k: round(avg(v), 3) for k, v in style_performance.items() if v},
79
+ }
80
+
81
+ # Detect dependency: if performance differs > 1.0 Sharpe between regimes
82
+ max_vol_diff = max(vol_performance.values(), key=avg, default=[0])
83
+ min_vol_diff = min(vol_performance.values(), key=avg, default=[0])
84
+ analysis["regime_dependent"] = (avg(max_vol_diff) - avg(min_vol_diff)) > 1.0
85
+
86
+ # Best/worst regime
87
+ all_regime_sharpes = [(f"{k}_{regime}", avg(v))
88
+ for category in [vol_performance, trend_performance, style_performance]
89
+ for regime, sharpes in category.items()
90
+ for k, v in [(regime, sharpes)] if v]
91
+ if all_regime_sharpes:
92
+ analysis["best_regime"] = max(all_regime_sharpes, key=lambda x: x[1])
93
+ analysis["worst_regime"] = min(all_regime_sharpes, key=lambda x: x[1])
94
+
95
+ return analysis