feat: signal abstraction layer + sizer + HS300 universe + PnL/IC reports
This commit is contained in:
+74
-10
@@ -1,25 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""End-to-end smoke test: download data -> backtest 5-day reversal -> print results."""
|
||||
"""End-to-end pipeline: HS300 universe -> reversal signal -> cross-sectional IC
|
||||
-> multi-stock backtest (AlphaStrategy + PercentSizer) -> reports.
|
||||
|
||||
Note: this runs the first 30 HS300 constituents to keep runtime manageable.
|
||||
Downloading daily bars for the full ~300 names takes roughly 10 minutes.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from analysis.report import generate_report
|
||||
from backtest.config import BacktestConfig
|
||||
from backtest.runner import BacktestRunner
|
||||
from strategies.reversal import FiveDayReversal
|
||||
from analysis.report import print_results
|
||||
from data.downloader import download_batch
|
||||
from data.universe import SYMBOLS
|
||||
from eval.metrics import evaluate_cross_sectional
|
||||
from portfolio.builder import ThresholdBuilder
|
||||
from signals.reversal import ReversalSignal
|
||||
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 reversal signal per stock.
|
||||
signal = ReversalSignal(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=["sh600000", "sz000001", "sh600519"], # 浦发银行, 平安银行, 贵州茅台
|
||||
start_date="2023-01-01",
|
||||
end_date="2024-12-31",
|
||||
initial_cash=1_000_000,
|
||||
symbols=list(data.keys()),
|
||||
start_date=start,
|
||||
end_date=end,
|
||||
initial_cash=initial_cash,
|
||||
sizer_percent=0.95,
|
||||
)
|
||||
runner = BacktestRunner(config)
|
||||
builder = ThresholdBuilder(buy_threshold=0.02, sell_threshold=-0.02, size_pct=0.95)
|
||||
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
|
||||
)
|
||||
|
||||
runner = BacktestRunner(config)
|
||||
results = runner.run(FiveDayReversal)
|
||||
print_results(results, config.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__":
|
||||
|
||||
Reference in New Issue
Block a user