#!/usr/bin/env python3 """End-to-end pipeline: HS300 universe -> momentum signal -> cross-sectional IC -> multi-stock backtest (AlphaStrategy + RankEqualWeightBuilder) -> reports.""" import logging import pandas as pd from analysis.report import generate_report from backtest.config import BacktestConfig from backtest.runner import BacktestRunner from data.downloader import download_batch from data.universe import SYMBOLS from eval.metrics import evaluate_cross_sectional from portfolio.builder import RankEqualWeightBuilder from signals.momentum import MomentumSignal from strategies.alpha_strategy import AlphaStrategy logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) def main(): symbols = SYMBOLS[:30] start, end = "2023-01-01", "2024-12-31" initial_cash = 1_000_000 # 1-2. Download daily data for the universe. data = download_batch(symbols, start, end) data = {s: df for s, df in data.items() if df is not None and not df.empty} logger.info(f"Downloaded {len(data)}/{len(symbols)} symbols") # 3. Compute the momentum signal per stock. signal = MomentumSignal(lookback=5) signal_series: dict[str, pd.Series] = {} forward_returns: dict[str, pd.Series] = {} for sym, df in data.items(): idx = pd.to_datetime(df["date"]) sig = signal.compute(df) sig.index = idx signal_series[sym] = sig fwd = df["close"].pct_change().shift(-1) # next-day return fwd.index = idx forward_returns[sym] = fwd # 4. Cross-sectional IC evaluation. signals_df = pd.DataFrame(signal_series) returns_df = pd.DataFrame(forward_returns) signal_eval = evaluate_cross_sectional(signals_df, returns_df) # 5. Attach the signal column to each DataFrame and build feeds. config = BacktestConfig( symbols=list(data.keys()), start_date=start, end_date=end, initial_cash=initial_cash, sizer_percent=0.95, ) runner = BacktestRunner(config) builder = RankEqualWeightBuilder(top_n=5) for sym, df in data.items(): df = df.copy() df["signal"] = signal.compute(df).values runner.add_signal_data(df, name=sym) # 6. Run the multi-stock backtest. results = runner.run_prepared(AlphaStrategy, {"builder": builder}) # 7. Reports. artifacts = generate_report( results, signal_eval, output_dir="reports/", initial_cash=initial_cash ) # 8. Print summary. print("\nSIGNAL IC") print("=" * 50) print(f"IC mean / std / IR: {signal_eval['ic_mean']:.4f} / " f"{signal_eval['ic_std']:.4f} / {signal_eval['ir']:.4f}") print(f"Rank IC mean / std / IR: {signal_eval['rank_ic_mean']:.4f} / " f"{signal_eval['rank_ic_std']:.4f} / {signal_eval['rank_ir']:.4f}") print(f"Hit rate: {signal_eval['hit_rate']:.2%}") print(f"Periods: {signal_eval['n_periods']}") print(f"\nReports written to: {artifacts}") if __name__ == "__main__": main()