| """AlphaForge - Complete Quantitative Trading System |
| |
| Usage: |
| python main.py --mode train --tickers SPY QQQ AAPL MSFT |
| python main.py --mode backtest --start 2020-01-01 --end 2024-01-01 |
| python main.py --mode live --config config.yaml |
| """ |
| import argparse |
| import numpy as np |
| import pandas as pd |
| import torch |
| import warnings |
| warnings.filterwarnings('ignore') |
|
|
| from market_data import MarketDataPipeline |
| from alpha_model import AlphaEnsemble |
| from sentiment_model import SentimentAlphaModel |
| from volatility_model import VolatilityEngine |
| from portfolio_optimizer import PortfolioOptimizer |
| from options_pricer import MLOptionsPricer |
| from backtest_engine import BacktestEngine, compute_information_coefficient, RegimeDetector |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser(description='AlphaForge Quant System') |
| parser.add_argument('--mode', type=str, default='train', |
| choices=['train', 'backtest', 'live', 'options']) |
| parser.add_argument('--tickers', type=str, nargs='+', |
| default=['SPY','QQQ','AAPL','MSFT','GOOGL','AMZN','META','NVDA','TSLA','JPM']) |
| parser.add_argument('--start', type=str, default='2020-01-01') |
| parser.add_argument('--end', type=str, default='2024-01-01') |
| parser.add_argument('--lookback', type=int, default=60) |
| parser.add_argument('--horizon', type=int, default=5) |
| parser.add_argument('--epochs', type=int, default=50) |
| parser.add_argument('--device', type=str, default='cpu') |
| parser.add_argument('--initial_capital', type=float, default=1_000_000) |
| parser.add_argument('--output', type=str, default='results/') |
| return parser.parse_args() |
|
|
|
|
| def train_alpha_model(args): |
| """Train the multi-asset alpha model""" |
| print("=" * 60) |
| print("ALPHA FORGE - Multi-Asset Alpha Model Training") |
| print("=" * 60) |
| |
| |
| pipeline = MarketDataPipeline(args.tickers, args.start, args.end) |
| data = pipeline.fetch_data() |
| |
| |
| features_df = pipeline.create_feature_matrix() |
| X, y, tickers, dates = pipeline.create_sequences( |
| features_df, lookback=args.lookback, forecast_horizon=args.horizon |
| ) |
| |
| print(f"\nDataset: {len(X)} samples, {X.shape[2]} features, seq_len={args.lookback}") |
| |
| |
| n = len(X) |
| train_end = int(n * 0.7) |
| val_end = int(n * 0.85) |
| |
| X_train, y_train = X[:train_end], y[:train_end] |
| X_val, y_val = X[train_end:val_end], y[train_end:val_end] |
| X_test, y_test = X[val_end:], y[val_end:] |
| |
| print(f"Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}") |
| |
| |
| ensemble = AlphaEnsemble( |
| input_size=X.shape[2], |
| seq_len=args.lookback, |
| device=args.device |
| ) |
| |
| metrics = ensemble.fit( |
| X_train, y_train, |
| X_val, y_val, |
| epochs=args.epochs, |
| batch_size=64, |
| lr=1e-4 |
| ) |
| |
| |
| test_pred = ensemble.predict(X_test) |
| test_ic = compute_information_coefficient( |
| pd.Series(test_pred), |
| pd.Series(y_test), |
| by_date=False |
| ) |
| |
| print(f"\nTest IC: {test_ic['mean_ic']:.4f}") |
| print(f"LSTM final val IC: {metrics['lstm']['val_ic'][-1]:.4f}") |
| print(f"Transformer final val IC: {metrics['transformer']['val_ic'][-1]:.4f}") |
| |
| |
| torch.save(ensemble.lstm.state_dict(), f"{args.output}/lstm_model.pt") |
| torch.save(ensemble.transformer.state_dict(), f"{args.output}/transformer_model.pt") |
| |
| return ensemble, metrics, test_ic |
|
|
|
|
| def run_backtest(args): |
| """Run full pipeline backtest""" |
| print("=" * 60) |
| print("ALPHA FORGE - Full Pipeline Backtest") |
| print("=" * 60) |
| |
| |
| pipeline = MarketDataPipeline(args.tickers, args.start, args.end) |
| data = pipeline.fetch_data() |
| features_df = pipeline.create_feature_matrix() |
| |
| X, y, tickers_arr, dates = pipeline.create_sequences( |
| features_df, lookback=args.lookback, forecast_horizon=args.horizon |
| ) |
| |
| |
| n = len(X) |
| train_end = int(n * 0.7) |
| val_end = int(n * 0.85) |
| |
| X_train, y_train = X[:train_end], y[:train_end] |
| X_test, y_test = X[val_end:], y[val_end:] |
| dates_test = dates[val_end:] |
| tickers_test = tickers_arr[val_end:] |
| |
| |
| print("\n[1/4] Training Alpha Model...") |
| ensemble = AlphaEnsemble(input_size=X.shape[2], seq_len=args.lookback, device=args.device) |
| ensemble.fit(X_train, y_train, epochs=30, batch_size=64, lr=1e-4) |
| |
| |
| alpha_pred = ensemble.predict(X_test) |
| |
| |
| pred_df = pd.DataFrame({ |
| 'date': dates_test, |
| 'ticker': tickers_test, |
| 'predicted_return': alpha_pred, |
| 'actual_return': y_test |
| }) |
| |
| |
| ic_metrics = compute_information_coefficient( |
| pred_df['predicted_return'], |
| pred_df['actual_return'], |
| by_date=True |
| ) |
| print(f"Mean IC: {ic_metrics['mean_ic']:.4f} +/- {ic_metrics['ic_std']:.4f}") |
| print(f"IC IR: {ic_metrics['ic_ir']:.4f}") |
| |
| |
| print("\n[2/4] Training Volatility Model...") |
| vol_engine = VolatilityEngine() |
| |
| |
| returns_dict = {} |
| for ticker in args.tickers: |
| if ticker in data: |
| close = data[ticker]['Close'].values.flatten() |
| returns_dict[ticker] = pd.Series( |
| np.log(close[1:] / close[:-1]), |
| index=data[ticker].index[1:] |
| ) |
| returns_df = pd.DataFrame(returns_dict).fillna(0) |
| |
| |
| for ticker in args.tickers: |
| if ticker in returns_df.columns: |
| vol_engine.fit_garch(returns_df[ticker], ticker) |
| |
| |
| print("\n[3/4] Running Portfolio Optimization...") |
| |
| |
| test_dates = pd.to_datetime(pred_df['date'].unique()) |
| test_dates = sorted(test_dates) |
| |
| |
| rebalance_dates = test_dates[::5] |
| |
| optimizer = PortfolioOptimizer( |
| max_weight=0.25, |
| risk_aversion=2.0, |
| transaction_cost=0.0003, |
| turnover_penalty=0.001 |
| ) |
| |
| weights_history = [] |
| |
| for rebalance_date in rebalance_dates: |
| |
| day_preds = pred_df[pred_df['date'] == rebalance_date] |
| |
| if len(day_preds) < 3: |
| continue |
| |
| |
| mu = day_preds.set_index('ticker')['predicted_return'].reindex(args.tickers).fillna(0).values |
| |
| |
| try: |
| Sigma = vol_engine.build_covariance_matrix(returns_df, rebalance_date) |
| Sigma = Sigma.reindex(index=args.tickers, columns=args.tickers).fillna(0) |
| Sigma = Sigma.values |
| except: |
| Sigma = np.eye(len(args.tickers)) * 0.04 |
| |
| |
| result = optimizer.optimize_max_sharpe(mu, Sigma) |
| |
| weights_row = pd.Series(result['weights'], index=args.tickers) |
| weights_row.name = rebalance_date |
| weights_history.append(weights_row) |
| |
| weights_df = pd.DataFrame(weights_history) |
| |
| |
| backtest_returns = returns_df.reindex(weights_df.index).fillna(0) |
| |
| |
| print("\n[4/4] Running Backtest...") |
| engine = BacktestEngine( |
| initial_capital=args.initial_capital, |
| transaction_cost=0.0003, |
| slippage=0.0001 |
| ) |
| |
| metrics = engine.run_backtest( |
| backtest_returns, |
| weights_df, |
| rebalance_dates=weights_df.index |
| ) |
| |
| |
| if 'SPY' in returns_df.columns: |
| regime_detector = RegimeDetector() |
| spy_returns = returns_df['SPY'].reindex(weights_df.index).fillna(0) |
| regimes = regime_detector.detect_regimes(spy_returns) |
| regime_stats = regime_detector.get_regime_stats(spy_returns) |
| print("\nRegime Statistics:") |
| print(regime_stats.to_string()) |
| |
| |
| print("\n" + "=" * 60) |
| print("BACKTEST RESULTS") |
| print("=" * 60) |
| print(f"Total Return: {metrics['total_return']*100:.2f}%") |
| print(f"Annualized Return: {metrics['annualized_return']*100:.2f}%") |
| print(f"Volatility: {metrics['volatility']*100:.2f}%") |
| print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.3f}") |
| print(f"Sortino Ratio: {metrics['sortino_ratio']:.3f}") |
| print(f"Max Drawdown: {metrics['max_drawdown']*100:.2f}%") |
| print(f"Calmar Ratio: {metrics['calmar_ratio']:.3f}") |
| print(f"Win Rate: {metrics['win_rate']*100:.1f}%") |
| print(f"Alpha: {metrics['alpha']*100:.2f}%") |
| print(f"Beta: {metrics['beta']:.3f}") |
| print(f"Information Ratio: {metrics['information_ratio']:.3f}") |
| print(f"Avg Turnover: {metrics['avg_turnover']*100:.2f}%") |
| print(f"Total Costs: ${metrics['total_transaction_costs']:,.2f}") |
| print(f"Final Capital: ${metrics['final_capital']:,.2f}") |
| print(f"Trades: {metrics['n_trades']}") |
| |
| |
| import os |
| os.makedirs(args.output, exist_ok=True) |
| |
| results = { |
| 'metrics': metrics, |
| 'ic_metrics': ic_metrics, |
| 'equity_curve': engine.get_equity_curve().to_dict(), |
| 'weights': weights_df.to_dict() |
| } |
| |
| import json |
| with open(f"{args.output}/backtest_results.json", 'w') as f: |
| json.dump({k: v for k, v in results.items() if k != 'weights'}, f, indent=2, default=str) |
| |
| weights_df.to_csv(f"{args.output}/weights_history.csv") |
| |
| print(f"\nResults saved to {args.output}/") |
| |
| return metrics, engine |
|
|
|
|
| def train_options_model(args): |
| """Train ML options pricing model""" |
| print("=" * 60) |
| print("ALPHA FORGE - Options Pricing Model") |
| print("=" * 60) |
| |
| pricer = MLOptionsPricer(device=args.device) |
| |
| |
| print("Generating synthetic option data...") |
| train_df = pricer.generate_synthetic_options(n_samples=50000) |
| val_df = pricer.generate_synthetic_options(n_samples=10000) |
| |
| X_train = pricer.prepare_features(train_df) |
| y_train = train_df['price'].values |
| X_val = pricer.prepare_features(val_df) |
| y_val = val_df['price'].values |
| |
| print(f"Training samples: {len(X_train)}, Validation: {len(X_val)}") |
| |
| |
| metrics = pricer.fit(X_train, y_train, X_val, y_val, epochs=100, batch_size=256) |
| |
| |
| test_df = pricer.generate_synthetic_options(n_samples=5) |
| X_test = pricer.prepare_features(test_df) |
| |
| ml_prices = pricer.predict(X_test) |
| bs_prices = [] |
| for i in range(len(test_df)): |
| if test_df['option_type'].iloc[i] == 'call': |
| p = pricer.bs.call_price( |
| test_df['S'].iloc[i], test_df['K'].iloc[i], |
| test_df['T'].iloc[i], test_df['r'].iloc[i], |
| test_df['sigma_hist'].iloc[i] |
| ) |
| else: |
| p = pricer.bs.put_price( |
| test_df['S'].iloc[i], test_df['K'].iloc[i], |
| test_df['T'].iloc[i], test_df['r'].iloc[i], |
| test_df['sigma_hist'].iloc[i] |
| ) |
| bs_prices.append(p) |
| |
| print("\nSample Predictions:") |
| print(f"{'True':>10} {'ML':>10} {'BS':>10} {'ML Err%':>10} {'BS Err%':>10}") |
| for i in range(len(test_df)): |
| true_p = test_df['price'].iloc[i] |
| ml_err = abs(ml_prices[i] - true_p) / true_p * 100 |
| bs_err = abs(bs_prices[i] - true_p) / true_p * 100 |
| print(f"{true_p:>10.2f} {ml_prices[i]:>10.2f} {bs_prices[i]:>10.2f} {ml_err:>10.2f} {bs_err:>10.2f}") |
| |
| |
| import os |
| os.makedirs(args.output, exist_ok=True) |
| torch.save(pricer.model.state_dict(), f"{args.output}/options_model.pt") |
| |
| return pricer, metrics |
|
|
|
|
| def main(): |
| args = parse_args() |
| |
| if args.mode == 'train': |
| train_alpha_model(args) |
| elif args.mode == 'backtest': |
| run_backtest(args) |
| elif args.mode == 'options': |
| train_options_model(args) |
| else: |
| print("Live mode not implemented in this version") |
|
|
|
|
| if __name__ == '__main__': |
| main() |
|
|