Premchan369 commited on
Commit
84dc0c8
Β·
verified Β·
1 Parent(s): d5f6347

Upload pipeline.py

Browse files
Files changed (1) hide show
  1. pipeline.py +280 -0
pipeline.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unified Pipeline - Orchestrates all AlphaForge components end-to-end.
2
+
3
+ INPUT: Market data (OHLCV), news feed, macro data, options chain
4
+ OUTPUT: Portfolio weights, risk metrics, PnL, dashboards
5
+
6
+ This is the central brain β€” one class to rule them all.
7
+ """
8
+ import numpy as np; import pandas as pd; import torch; import json; import os
9
+ from typing import Dict, List, Optional, Tuple, Any
10
+ from datetime import datetime, timedelta
11
+ import warnings; warnings.filterwarnings('ignore')
12
+
13
+ class AlphaForgePipeline:
14
+ """Production-grade unified pipeline: data β†’ alpha β†’ risk β†’ weights β†’ backtest."""
15
+
16
+ def __init__(self, config: Optional[Dict] = None):
17
+ self.config = config or self.default_config()
18
+ self._init_components()
19
+ self.state = {'pnl': [], 'weights': [], 'alerts': [], 'signals': {}, 'regime': 'neutral'}
20
+
21
+ # ── default configuration ───────────────────────────────────────────
22
+ @staticmethod
23
+ def default_config() -> Dict:
24
+ return {
25
+ 'tickers': ['SPY','QQQ','AAPL','MSFT','GOOGL','AMZN','META','NVDA','TSLA','JPM','V','WMT','XLF','XLK','XLE'],
26
+ 'lookback': 60, 'horizon': 5,
27
+ 'rebalance_freq': 'W', # D=Daily, W=Weekly, M=Monthly
28
+ 'alpha': {'lstm_hidden':128,'lstm_layers':2,'trans_d_model':128,'trans_nhead':4,'xgb_depth':6,'xgb_lr':0.05,'xgb_estimators':200,
29
+ 'ensemble_weights':{'lstm':0.3,'transformer':0.3,'xgboost':0.4},'epochs':50,'device':'cpu'},
30
+ 'sentiment': {'enabled':True,'model':'ProsusAI/finbert','weight':0.3,'window':5},
31
+ 'volatility': {'garch_p':1,'garch_q':1,'garch_dist':'t','lstm_hidden':64},
32
+ 'portfolio': {'max_weight':0.25,'risk_aversion':2.0,'transaction_cost':0.0003,'target_return':None},
33
+ 'risk': {'var_conf':[0.95,0.99],'max_drawdown_threshold':-0.10,'scaling_factor':2.0},
34
+ 'online': {'enable_drift_detection':True,'adaptation_window':21,'drift_threshold':0.3},
35
+ 'advanced_features': True,
36
+ 'include_macro': True,
37
+ 'include_sentiment': True,
38
+ }
39
+
40
+ def _init_components(self):
41
+ """Lazy-init all model components."""
42
+ self._alpha_model = None
43
+ self._sentiment_model = None
44
+ self._vol_engine = None
45
+ self._optimizer = None
46
+ self._risk_engine = None
47
+ self._feature_engine = None
48
+
49
+ # ── data pipeline ───────────────────────────────────────────────────
50
+ def fetch_market_data(self, start: str, end: str) -> Dict[str, pd.DataFrame]:
51
+ """Fetch and preprocess market data."""
52
+ from market_data import MarketDataPipeline
53
+ pipeline = MarketDataPipeline(self.config['tickers'], start, end)
54
+ return pipeline.fetch_data()
55
+
56
+ def build_features(self, data: Dict[str, pd.DataFrame]) -> pd.DataFrame:
57
+ """Build advanced feature matrix (90+ cols)."""
58
+ if self.config['advanced_features']:
59
+ from advanced_features_part1 import MicrostructureFeatures, CrossSectionalFeatures
60
+ from macro_features import MacroFeatures
61
+ from regime_features import RegimeFeatures
62
+ from technical_indicators import AdvancedTechnical
63
+ all_features = []
64
+ for ticker, df in data.items():
65
+ close = np.array(df['Close']).flatten(); high = np.array(df['High']).flatten()
66
+ low = np.array(df['Low']).flatten(); vol = np.array(df['Volume']).flatten()
67
+ cs = pd.Series(close, index=df.index); hs = pd.Series(high, index=df.index)
68
+ ls = pd.Series(low, index=df.index); vs = pd.Series(vol, index=df.index)
69
+ f = pd.DataFrame(index=df.index)
70
+ f['ticker'] = ticker; f['close'] = close
71
+ for col_df in [
72
+ MicrostructureFeatures.compute_all(cs,hs,ls,vs),
73
+ RegimeFeatures.volatility_regime(cs.pct_change().fillna(0)),
74
+ RegimeFeatures.liquidity_regime(vs,cs),
75
+ RegimeFeatures.trend_regime(cs),
76
+ AdvancedTechnical.ichimoku(cs,hs,ls),
77
+ AdvancedTechnical.supertrend(cs,hs,ls),
78
+ AdvancedTechnical.keltner_channels(cs,hs,ls),
79
+ AdvancedTechnical.volume_profile(cs,vs,hs,ls),
80
+ ]:
81
+ for c in col_df.columns: f[c] = col_df[c].values
82
+ all_features.append(f)
83
+ features_df = pd.concat(all_features, axis=0)
84
+ if self.config['include_macro']:
85
+ macro = MacroFeatures._synthetic_macro(str(features_df.index[0])[:10], str(features_df.index[-1])[:10])
86
+ for c in macro.columns: features_df[f'macro_{c}'] = macro[c].reindex(features_df.index).ffill()
87
+ # z-score normalize
88
+ nc = [c for c in features_df.columns if c not in ['ticker','close']]
89
+ for ticker in features_df['ticker'].unique():
90
+ m = features_df['ticker'] == ticker
91
+ for col in nc:
92
+ s = features_df.loc[m, col]; rm = s.rolling(42).mean(); rs = s.rolling(42).std().replace(0,1)
93
+ features_df.loc[m, col] = (s - rm) / rs
94
+ return features_df.replace([np.inf, -np.inf], 0).fillna(0)
95
+ else:
96
+ from market_data import MarketDataPipeline
97
+ return MarketDataPipeline(self.config['tickers'], '', '').create_feature_matrix()
98
+
99
+ # ── model training ──────────────────────────────────────────────────
100
+ def train_alpha(self, X: np.ndarray, y: np.ndarray, X_val=None, y_val=None) -> Dict:
101
+ """Train the alpha model ensemble."""
102
+ from alpha_model import AlphaEnsemble
103
+ ac = self.config['alpha']
104
+ self._alpha_model = AlphaEnsemble(
105
+ input_size=X.shape[2], seq_len=X.shape[1],
106
+ lstm_hidden=ac['lstm_hidden'], lstm_layers=ac['lstm_layers'],
107
+ trans_d_model=ac['trans_d_model'], trans_nhead=ac['trans_nhead'],
108
+ xgb_depth=ac['xgb_depth'], xgb_lr=ac['xgb_lr'], xgb_estimators=ac['xgb_estimators'],
109
+ weights=ac['ensemble_weights'], device=ac['device']
110
+ )
111
+ return self._alpha_model.fit(X, y, X_val, y_val, epochs=ac['epochs'])
112
+
113
+ def predict_alpha(self, X: np.ndarray) -> np.ndarray:
114
+ """Generate alpha predictions."""
115
+ if self._alpha_model is None:
116
+ raise RuntimeError("Alpha model not trained")
117
+ return self._alpha_model.predict(X)
118
+
119
+ # ── portfolio optimization ──────────────────────────────────────────
120
+ def optimize_portfolio(self, mu: np.ndarray, Sigma: np.ndarray,
121
+ current_weights: Optional[np.ndarray] = None) -> Dict:
122
+ """Optimize portfolio weights."""
123
+ from portfolio_optimizer import PortfolioOptimizer
124
+ pc = self.config['portfolio']
125
+ opt = PortfolioOptimizer(
126
+ max_weight=pc['max_weight'], risk_aversion=pc['risk_aversion'],
127
+ transaction_cost=pc['transaction_cost']
128
+ )
129
+ return opt.optimize_max_sharpe(mu, Sigma, current_weights)
130
+
131
+ # ── risk analytics ──────────────────────────────────────────────────
132
+ def compute_risk_metrics(self, returns: np.ndarray, weights: np.ndarray,
133
+ returns_df: pd.DataFrame) -> Dict:
134
+ """Compute comprehensive risk metrics."""
135
+ from risk_engine import RiskEngine
136
+ rc = self.config['risk']
137
+ risk = RiskEngine(confidence_levels=rc['var_conf'])
138
+ port_ret = returns_df.dot(weights) if returns_df.shape[1] == len(weights) else np.dot(returns, weights)
139
+ return {
140
+ **risk.compute_all_var(port_ret.values if hasattr(port_ret,'values') else port_ret),
141
+ **risk.compute_tail_risk(port_ret.values if hasattr(port_ret,'values') else port_ret),
142
+ 'portfolio_var': risk.portfolio_var(weights, returns_df, 'parametric', 0.95)
143
+ }
144
+
145
+ # ── full pipeline execution ─────────────────────────────────────────
146
+ def run(self, start: str, end: str, mode: str = 'backtest') -> Dict[str, Any]:
147
+ """Run full pipeline end-to-end."""
148
+ print(f"πŸš€ AlphaForge Pipeline: {start} β†’ {end}")
149
+
150
+ # 1. Data
151
+ data = self.fetch_market_data(start, end)
152
+ features = self.build_features(data)
153
+
154
+ # 2. Sequences
155
+ from market_data import MarketDataPipeline
156
+ pipeline = MarketDataPipeline(self.config['tickers'], start, end)
157
+ X, y, tickers, dates = pipeline.create_sequences(features, self.config['lookback'], self.config['horizon'])
158
+ n = len(X)
159
+ X_train, y_train = X[:int(n*0.7)], y[:int(n*0.7)]
160
+ X_test, y_test = X[int(n*0.85):], y[int(n*0.85):]
161
+
162
+ # 3. Alpha
163
+ self.train_alpha(X_train, y_train)
164
+ alpha_pred = self.predict_alpha(X_test)
165
+ from backtest_engine import compute_information_coefficient
166
+ ic = compute_information_coefficient(pd.Series(alpha_pred), pd.Series(y_test), by_date=False)
167
+
168
+ # 4. Volatility
169
+ from volatility_model import VolatilityEngine
170
+ vc = self.config['volatility']
171
+ vol_engine = VolatilityEngine(garch_p=vc['garch_p'], garch_q=vc['garch_q'], garch_dist=vc['garch_dist'])
172
+ returns_dict = {}
173
+ for t in self.config['tickers']:
174
+ if t in data:
175
+ c = np.array(data[t]['Close']).flatten()
176
+ returns_dict[t] = pd.Series(np.log(c[1:]/c[:-1]), index=data[t].index[1:])
177
+ returns_df = pd.DataFrame(returns_dict).fillna(0)
178
+
179
+ # 5. Portfolio
180
+ pred_df = pd.DataFrame({'date': dates[int(n*0.85):], 'ticker': tickers[int(n*0.85):],
181
+ 'predicted_return': alpha_pred, 'actual_return': y_test})
182
+ test_dates = sorted(pd.to_datetime(pred_df['date'].unique()))
183
+ weights_history = []
184
+ for rd in test_dates[::5]: # Weekly rebalance
185
+ dp = pred_df[pred_df['date'] == rd]
186
+ if len(dp) < 3: continue
187
+ mu = dp.set_index('ticker')['predicted_return'].reindex(self.config['tickers']).fillna(0).values
188
+ try:
189
+ cov = vol_engine.build_covariance_matrix(returns_df, rd)
190
+ cov = cov.reindex(index=self.config['tickers'], columns=self.config['tickers']).fillna(0).values
191
+ except: cov = np.eye(len(self.config['tickers'])) * 0.04
192
+ result = self.optimize_portfolio(mu, cov)
193
+ weights_history.append(pd.Series(result['weights'], index=self.config['tickers'], name=rd))
194
+
195
+ if not weights_history:
196
+ return {'error': 'No valid rebalance dates'}
197
+
198
+ weights_df = pd.DataFrame(weights_history)
199
+
200
+ # 6. Backtest
201
+ from backtest_engine import BacktestEngine, RegimeDetector
202
+ bt = BacktestEngine(initial_capital=1_000_000)
203
+ bt_returns = returns_df.reindex(weights_df.index).fillna(0)
204
+ metrics = bt.run_backtest(bt_returns, weights_df, rebalance_dates=weights_df.index)
205
+
206
+ # 7. Risk
207
+ risk = self.compute_risk_metrics(np.array(bt.returns_history), weights_df.iloc[-1].values,
208
+ bt_returns)
209
+
210
+ # 8. Regime
211
+ if 'SPY' in returns_df.columns:
212
+ rdet = RegimeDetector()
213
+ spy_r = returns_df['SPY'].reindex(weights_df.index).fillna(0)
214
+ rdet.detect_regimes(spy_r)
215
+ regime_stats = rdet.get_regime_stats(spy_r)
216
+
217
+ return {
218
+ 'metrics': metrics,
219
+ 'ic': ic,
220
+ 'risk': risk,
221
+ 'regime_stats': regime_stats.to_dict() if 'regime_stats' in dir() else None,
222
+ 'weights': weights_df.tail(10).to_dict(),
223
+ 'n_signals': len(alpha_pred),
224
+ 'feature_count': X.shape[2],
225
+ }
226
+
227
+
228
+ # ── Hyperparameter Sweep Engine ─────────────────────────────────────────────
229
+
230
+ class HyperparameterSweeper:
231
+ """Grid search over alpha model hyperparameters."""
232
+
233
+ def __init__(self, config_grid: Dict[str, List]):
234
+ self.grid = config_grid
235
+ self.results = []
236
+
237
+ def run(self, X: np.ndarray, y: np.ndarray, n_splits: int = 3) -> pd.DataFrame:
238
+ from itertools import product
239
+ keys = list(self.grid.keys())
240
+ combos = list(product(*self.grid.values()))
241
+ print(f"🧹 Sweeping {len(combos)} hyperparameter combinations...")
242
+
243
+ for i, combo in enumerate(combos):
244
+ params = dict(zip(keys, combo))
245
+ print(f" [{i+1}/{len(combos)}] {params}")
246
+
247
+ # Walk-forward validation
248
+ from alpha_model import AlphaEnsemble
249
+ n = len(X)
250
+ fold_size = n // (n_splits + 1)
251
+ ics = []
252
+
253
+ for fold in range(n_splits):
254
+ train_end = (fold + 1) * fold_size
255
+ val_end = train_end + fold_size
256
+ X_f, y_f = X[:train_end], y[:train_end]
257
+ X_v, y_v = X[train_end:val_end], y[train_end:val_end]
258
+ if len(X_v) < 10: continue
259
+ model = AlphaEnsemble(
260
+ input_size=X.shape[2], seq_len=X.shape[1],
261
+ lstm_hidden=params.get('lstm_hidden',128),
262
+ lstm_layers=params.get('lstm_layers',2),
263
+ trans_d_model=params.get('trans_d_model',128),
264
+ xgb_depth=params.get('xgb_depth',6),
265
+ xgb_lr=params.get('xgb_lr',0.05),
266
+ xgb_estimators=params.get('xgb_estimators',200),
267
+ device=params.get('device','cpu')
268
+ )
269
+ model.fit(X_f, y_f, X_v, y_v, epochs=params.get('epochs',30))
270
+ from backtest_engine import compute_information_coefficient
271
+ pred = model.predict(X_v)
272
+ ic = compute_information_coefficient(pd.Series(pred), pd.Series(y_v), by_date=False)
273
+ ics.append(ic['mean_ic'])
274
+
275
+ result = {**params, 'mean_ic': np.mean(ics), 'std_ic': np.std(ics), 'fold_ics': ics}
276
+ self.results.append(result)
277
+
278
+ df = pd.DataFrame(self.results).sort_values('mean_ic', ascending=False)
279
+ print(f"\nβœ… Best IC: {df['mean_ic'].iloc[0]:.4f} with params: {dict(df.iloc[0][list(keys)])}")
280
+ return df