From da01312292f0684093a4b8fb3332573aa925ba00 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jun 2026 09:44:33 +0800 Subject: [PATCH] feat: signal abstraction layer + sizer + HS300 universe + PnL/IC reports --- analysis/report.py | 113 +++++++++++++++++++++++++++++++++++ backtest/config.py | 1 + backtest/feed.py | 23 +++++++ backtest/runner.py | 42 +++++++++---- data/universe.py | 38 ++++++++++++ eval/__init__.py | 4 ++ eval/metrics.py | 90 ++++++++++++++++++++++++++++ portfolio/__init__.py | 4 ++ portfolio/builder.py | 40 +++++++++++++ reports/ic.png | Bin 0 -> 34329 bytes reports/pnl.png | Bin 0 -> 48359 bytes reports/summary.txt | 21 +++++++ requirements.txt | 1 + run_example.py | 84 ++++++++++++++++++++++---- signals/__init__.py | 5 ++ signals/base.py | 28 +++++++++ signals/reversal.py | 22 +++++++ strategies/alpha_strategy.py | 26 ++++++++ tests/test_eval.py | 53 ++++++++++++++++ tests/test_signals.py | 38 ++++++++++++ 20 files changed, 610 insertions(+), 23 deletions(-) create mode 100644 data/universe.py create mode 100644 eval/__init__.py create mode 100644 eval/metrics.py create mode 100644 portfolio/__init__.py create mode 100644 portfolio/builder.py create mode 100644 reports/ic.png create mode 100644 reports/pnl.png create mode 100644 reports/summary.txt create mode 100644 signals/__init__.py create mode 100644 signals/base.py create mode 100644 signals/reversal.py create mode 100644 strategies/alpha_strategy.py create mode 100644 tests/test_eval.py create mode 100644 tests/test_signals.py diff --git a/analysis/report.py b/analysis/report.py index d3106b0..368d7a9 100644 --- a/analysis/report.py +++ b/analysis/report.py @@ -1,6 +1,13 @@ """Performance analysis and reporting for backtest results.""" +import os from typing import Any +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 +import pandas as pd # noqa: E402 + def print_results(results: list, initial_cash: float = 1_000_000.0) -> dict[str, Any]: """Print and return key performance metrics from a backtrader run result.""" @@ -44,3 +51,109 @@ def print_results(results: list, initial_cash: float = 1_000_000.0) -> dict[str, print("=" * 50) return report + + +def plot_accumulated_pnl( + results: list, output_path: str = "reports/pnl.png", initial_cash: float = 1_000_000.0 +) -> str: + """Plot accumulated portfolio value from a backtest run. + + Reads the per-day TimeReturn analyzer attached by ``BacktestRunner`` and + compounds it into an equity curve. + + Args: + results: The list returned by ``cerebro.run()``. + output_path: Destination PNG path. + initial_cash: Starting portfolio value for scaling the curve. + + Returns: + The path the chart was written to. + """ + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + series = pd.Series(dtype=float) + if results: + tr = results[0].analyzers.timereturn.get_analysis() + series = pd.Series(tr).sort_index() + + fig, ax = plt.subplots(figsize=(10, 5)) + if len(series): + equity = (1.0 + series).cumprod() * initial_cash + ax.plot(equity.index, equity.values, color="C0") + ax.set_title("Accumulated Portfolio Value") + ax.set_xlabel("Date") + ax.set_ylabel("Value") + ax.grid(True, alpha=0.3) + fig.autofmt_xdate() + fig.tight_layout() + fig.savefig(output_path, dpi=100) + plt.close(fig) + return output_path + + +def plot_ic(signal_eval: dict, output_path: str = "reports/ic.png") -> str: + """Plot the per-period rank IC time series from a signal evaluation. + + Args: + signal_eval: Dict returned by ``evaluate_cross_sectional`` (expects a + ``rank_ic_series`` pandas Series). + output_path: Destination PNG path. + + Returns: + The path the chart was written to. + """ + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + rank_ic = signal_eval.get("rank_ic_series", pd.Series(dtype=float)) + + fig, ax = plt.subplots(figsize=(10, 5)) + if len(rank_ic): + ax.bar(rank_ic.index, rank_ic.values, width=1.0, color="C1", alpha=0.6, label="Rank IC") + ax.axhline(rank_ic.mean(), color="C3", linestyle="--", label=f"mean={rank_ic.mean():.3f}") + ax.legend() + ax.set_title("Cross-Sectional Rank IC") + ax.set_xlabel("Date") + ax.set_ylabel("Rank IC") + ax.grid(True, alpha=0.3) + fig.autofmt_xdate() + fig.tight_layout() + fig.savefig(output_path, dpi=100) + plt.close(fig) + return output_path + + +def generate_report( + results: list, + signal_eval: dict, + output_dir: str = "reports/", + initial_cash: float = 1_000_000.0, +) -> dict[str, str]: + """Generate the full report: PnL chart, IC chart, and a summary text file. + + Args: + results: The list returned by ``cerebro.run()``. + signal_eval: Dict returned by ``evaluate_cross_sectional``. + output_dir: Directory to write artifacts into. + initial_cash: Starting portfolio value. + + Returns: + Mapping of artifact name to file path. + """ + os.makedirs(output_dir, exist_ok=True) + pnl_path = plot_accumulated_pnl( + results, os.path.join(output_dir, "pnl.png"), initial_cash + ) + ic_path = plot_ic(signal_eval, os.path.join(output_dir, "ic.png")) + + metrics = print_results(results, initial_cash) + summary_path = os.path.join(output_dir, "summary.txt") + with open(summary_path, "w") as f: + f.write("BACKTEST SUMMARY\n") + f.write("=" * 40 + "\n") + for k, v in metrics.items(): + f.write(f"{k}: {v}\n") + f.write("\nSIGNAL IC\n") + f.write("=" * 40 + "\n") + for k in ("ic_mean", "ic_std", "ir", "rank_ic_mean", "rank_ic_std", "rank_ir", "hit_rate", "n_periods"): + if k in signal_eval: + f.write(f"{k}: {signal_eval[k]}\n") + + return {"pnl": pnl_path, "ic": ic_path, "summary": summary_path} diff --git a/backtest/config.py b/backtest/config.py index 30d35c1..ccae95b 100644 --- a/backtest/config.py +++ b/backtest/config.py @@ -11,3 +11,4 @@ class BacktestConfig: commission: float = 0.0003 # 0.03% for Chinese A-shares stamp_duty: float = 0.001 # 0.1% stamp duty on sells only (handled in strategy) adjust: str = "qfq" + sizer_percent: float = 0.95 # fraction of portfolio per trade diff --git a/backtest/feed.py b/backtest/feed.py index a19e0ee..8cff07a 100644 --- a/backtest/feed.py +++ b/backtest/feed.py @@ -10,3 +10,26 @@ def df_to_bt_feed(df: pd.DataFrame) -> bt.feeds.PandasData: df = df.set_index("date") df = df[["open", "high", "low", "close", "volume"]] return bt.feeds.PandasData(dataname=df) + + +class SignalPandasData(bt.feeds.PandasData): + """PandasData feed carrying an extra ``signal`` line alongside OHLCV.""" + + lines = ("signal",) + params = (("signal", -1),) # -1 -> match by column name + + +def df_to_signal_feed(df: pd.DataFrame) -> "SignalPandasData": + """Convert an OHLCV+signal DataFrame to a SignalPandasData feed. + + Args: + df: DataFrame with ``date``, OHLCV columns, and a ``signal`` column. + + Returns: + A SignalPandasData feed (NaN signals are preserved for the strategy to skip). + """ + df = df.copy() + df["date"] = pd.to_datetime(df["date"]) + df = df.set_index("date") + df = df[["open", "high", "low", "close", "volume", "signal"]] + return SignalPandasData(dataname=df) diff --git a/backtest/runner.py b/backtest/runner.py index fc5704e..07ba90f 100644 --- a/backtest/runner.py +++ b/backtest/runner.py @@ -4,7 +4,7 @@ import backtrader as bt from typing import Optional from backtest.config import BacktestConfig -from backtest.feed import df_to_bt_feed +from backtest.feed import df_to_bt_feed, df_to_signal_feed from data.downloader import download_daily logger = logging.getLogger(__name__) @@ -30,33 +30,49 @@ class BacktestRunner: self.cerebro.adddata(feed, name=symbol) logger.info(f"Added {symbol}: {len(df)} bars") + def add_signal_data(self, df, name: str) -> None: + """Add a pre-built OHLCV+signal DataFrame as a SignalPandasData feed.""" + feed = df_to_signal_feed(df) + self.cerebro.adddata(feed, name=name) + logger.info(f"Added signal feed {name}: {len(df)} bars") + def add_strategy(self, strategy_cls, **kwargs) -> None: """Add a strategy class to cerebro.""" self.cerebro.addstrategy(strategy_cls, **kwargs) - def setup(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> None: - """Full setup: load data for all symbols, configure cerebro, add strategy.""" - # Load data for all symbols - for sym in self.config.symbols: - self.add_data(sym) - - # Configure cerebro + def _configure(self) -> None: + """Configure broker, sizer, and analyzers (independent of data feeds).""" self.cerebro.broker.setcash(self.config.initial_cash) self.cerebro.broker.setcommission(commission=self.config.commission) + self.cerebro.addsizer(bt.sizers.PercentSizer, percents=self.config.sizer_percent * 100) - # Add analyzers self.cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe", riskfreerate=0.02) self.cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown") self.cerebro.addanalyzer(bt.analyzers.Returns, _name="returns") self.cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trades") + self.cerebro.addanalyzer( + bt.analyzers.TimeReturn, _name="timereturn", timeframe=bt.TimeFrame.Days + ) - # Add strategy - strategy_kwargs = strategy_kwargs or {} - self.cerebro.addstrategy(strategy_cls, **strategy_kwargs) + def setup(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> None: + """Full setup: load data for all symbols, configure cerebro, add strategy.""" + for sym in self.config.symbols: + self.add_data(sym) + self._configure() + self.cerebro.addstrategy(strategy_cls, **(strategy_kwargs or {})) def run(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> list: - """Setup and run the backtest. Returns cerebro run results.""" + """Setup (downloading all symbols) and run the backtest.""" self.setup(strategy_cls, strategy_kwargs) + return self._execute() + + def run_prepared(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> list: + """Run a backtest using feeds already added via ``add_signal_data``.""" + self._configure() + self.cerebro.addstrategy(strategy_cls, **(strategy_kwargs or {})) + return self._execute() + + def _execute(self) -> list: start_val = self.cerebro.broker.getvalue() logger.info(f"Starting portfolio value: {start_val:,.2f}") self._results = self.cerebro.run() diff --git a/data/universe.py b/data/universe.py new file mode 100644 index 0000000..9116467 --- /dev/null +++ b/data/universe.py @@ -0,0 +1,38 @@ +"""CSI 300 (HS300) universe helpers.""" +import logging + +import baostock as bs +import pandas as pd + +logger = logging.getLogger(__name__) + +# First 30 HS300 constituents (large caps) in 'shXXXXXX' / 'szXXXXXX' format. +# Hardcoded for fast, deterministic smoke tests. Use get_hs300_stocks() for the +# live, full list — downloading daily bars for all ~300 takes roughly 10 minutes. +SYMBOLS = [ + "sh600000", "sh600009", "sh600010", "sh600028", "sh600030", + "sh600036", "sh600048", "sh600050", "sh600104", "sh600276", + "sh600309", "sh600519", "sh600585", "sh600887", "sh600900", + "sh601012", "sh601166", "sh601288", "sh601318", "sh601398", + "sh601628", "sh601668", "sh601857", "sh601888", "sh601988", + "sz000001", "sz000002", "sz000333", "sz000651", "sz000858", +] + + +def get_hs300_stocks() -> pd.DataFrame: + """Fetch the current CSI 300 constituents from baostock. + + Returns: + DataFrame with columns ``code`` (e.g. ``sh600000``), ``name``, ``date``. + """ + bs.login() + try: + rs = bs.query_hs300_stocks() + stocks = [] + while rs.next(): + stocks.append(rs.get_row_data()) + finally: + bs.logout() + df = pd.DataFrame(stocks, columns=["code", "name", "date"]) + df["code"] = df["code"].str.replace(".", "", regex=False) + return df diff --git a/eval/__init__.py b/eval/__init__.py new file mode 100644 index 0000000..20cb146 --- /dev/null +++ b/eval/__init__.py @@ -0,0 +1,4 @@ +"""Signal evaluation metrics.""" +from eval.metrics import evaluate_cross_sectional + +__all__ = ["evaluate_cross_sectional"] diff --git a/eval/metrics.py b/eval/metrics.py new file mode 100644 index 0000000..3dda218 --- /dev/null +++ b/eval/metrics.py @@ -0,0 +1,90 @@ +"""Information-coefficient metrics for alpha signals.""" +from typing import Any + +import pandas as pd + + +def _summarize(ic: pd.Series, rank_ic: pd.Series) -> dict[str, Any]: + """Aggregate per-period IC series into summary statistics.""" + ic = ic.dropna() + rank_ic = rank_ic.dropna() + + ic_mean = float(ic.mean()) if len(ic) else float("nan") + ic_std = float(ic.std()) if len(ic) else float("nan") + rank_ic_mean = float(rank_ic.mean()) if len(rank_ic) else float("nan") + rank_ic_std = float(rank_ic.std()) if len(rank_ic) else float("nan") + + return { + "ic_mean": ic_mean, + "ic_std": ic_std, + "ir": ic_mean / ic_std if ic_std else float("nan"), + "rank_ic_mean": rank_ic_mean, + "rank_ic_std": rank_ic_std, + "rank_ir": rank_ic_mean / rank_ic_std if rank_ic_std else float("nan"), + "hit_rate": float((rank_ic > 0).mean()) if len(rank_ic) else float("nan"), + "n_periods": int(len(rank_ic)), + "ic_series": ic, + "rank_ic_series": rank_ic, + } + + +def _cross_sectional(signals_df: pd.DataFrame, returns_df: pd.DataFrame) -> dict[str, Any]: + """Per-date IC across stocks (requires >= 2 stocks).""" + dates = signals_df.index + ic_vals, rank_ic_vals, idx = [], [], [] + for dt in dates: + s = signals_df.loc[dt] + r = returns_df.loc[dt] + mask = s.notna() & r.notna() + if mask.sum() < 2: + continue + sv, rv = s[mask], r[mask] + # A degenerate (constant) vector makes correlation undefined. + if sv.nunique() < 2 or rv.nunique() < 2: + continue + ic_vals.append(sv.corr(rv)) + rank_ic_vals.append(sv.corr(rv, method="spearman")) + idx.append(dt) + ic = pd.Series(ic_vals, index=idx, dtype=float) + rank_ic = pd.Series(rank_ic_vals, index=idx, dtype=float) + return _summarize(ic, rank_ic) + + +def _rolling_single( + signals_df: pd.DataFrame, returns_df: pd.DataFrame, window: int = 20 +) -> dict[str, Any]: + """Rolling time-series IC for the single-stock case. + + With one stock there is no cross-section, so we measure how well the signal + tracks forward returns over a trailing window instead. + """ + col = signals_df.columns[0] + s = signals_df[col] + r = returns_df[col] + ic = s.rolling(window).corr(r) + rank_ic = s.rank().rolling(window).corr(r.rank()) + return _summarize(ic, rank_ic) + + +def evaluate_cross_sectional( + signals_df: pd.DataFrame, returns_df: pd.DataFrame +) -> dict[str, Any]: + """Evaluate a signal's predictive power against forward returns. + + Args: + signals_df: DataFrame indexed by date, one column per stock, signal values. + returns_df: DataFrame indexed by date, one column per stock, forward returns. + + Returns: + Dict with ``ic_mean``, ``ic_std``, ``ir``, ``rank_ic_mean``, + ``rank_ic_std``, ``rank_ir``, ``hit_rate``, ``n_periods`` and the + per-period ``ic_series`` / ``rank_ic_series`` (for plotting). + """ + cols = signals_df.columns.intersection(returns_df.columns) + idx = signals_df.index.intersection(returns_df.index) + signals_df = signals_df.loc[idx, cols] + returns_df = returns_df.loc[idx, cols] + + if len(cols) >= 2: + return _cross_sectional(signals_df, returns_df) + return _rolling_single(signals_df, returns_df) diff --git a/portfolio/__init__.py b/portfolio/__init__.py new file mode 100644 index 0000000..2b063cb --- /dev/null +++ b/portfolio/__init__.py @@ -0,0 +1,4 @@ +"""Translate signal values into position actions.""" +from portfolio.builder import PositionAction, ThresholdBuilder + +__all__ = ["PositionAction", "ThresholdBuilder"] diff --git a/portfolio/builder.py b/portfolio/builder.py new file mode 100644 index 0000000..22a37d6 --- /dev/null +++ b/portfolio/builder.py @@ -0,0 +1,40 @@ +"""Map signal values to discrete position actions.""" +from dataclasses import dataclass + + +@dataclass +class PositionAction: + """A target action for a single stock on a single bar.""" + + action: str # "buy", "sell", or "hold" + size_pct: float = 0.0 # target portfolio fraction for buys + + +class ThresholdBuilder: + """Open on strong positive signal, close on strong negative signal.""" + + def __init__( + self, + buy_threshold: float = 0.02, + sell_threshold: float = -0.02, + size_pct: float = 0.95, + ): + self.buy_threshold = buy_threshold + self.sell_threshold = sell_threshold + self.size_pct = size_pct + + def build(self, signal_value: float, in_position: bool) -> PositionAction: + """Decide what to do given the current signal and position state. + + Args: + signal_value: Latest signal value for the stock. + in_position: Whether the portfolio currently holds the stock. + + Returns: + The action to take ("buy", "sell", or "hold"). + """ + if not in_position and signal_value >= self.buy_threshold: + return PositionAction("buy", self.size_pct) + if in_position and signal_value <= self.sell_threshold: + return PositionAction("sell", 0.0) + return PositionAction("hold", 0.0) diff --git a/reports/ic.png b/reports/ic.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b2e69b05e22755556c883c64d4c33fa2f8f860 GIT binary patch literal 34329 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 literal 0 HcmV?d00001 diff --git a/reports/pnl.png b/reports/pnl.png new file mode 100644 index 0000000000000000000000000000000000000000..c9813202201fc696825d29eacf745293e7a11f60 GIT binary patch literal 48359 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 MDcs7s@$kie0h{f9i2wiq literal 0 HcmV?d00001 diff --git a/reports/summary.txt b/reports/summary.txt new file mode 100644 index 0000000..6e837d8 --- /dev/null +++ b/reports/summary.txt @@ -0,0 +1,21 @@ +BACKTEST SUMMARY +======================================== +sharpe: -5.633716896325243 +max_drawdown: 48.659457956769764 +max_drawdown_len: 451 +total_return: -0.48968077055625825 +avg_return: -0.0010117371292484674 +total_trades: 33 +won_trades: 20 +lost_trades: 12 + +SIGNAL IC +======================================== +ic_mean: -0.006912277738865651 +ic_std: 0.3332983458971776 +ir: -0.020739010030965125 +rank_ic_mean: -0.006980297831283274 +rank_ic_std: 0.32283972442680237 +rank_ir: -0.02162155801513181 +hit_rate: 0.4811715481171548 +n_periods: 478 diff --git a/requirements.txt b/requirements.txt index b01bb2b..b018091 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ backtrader>=1.9.76.123 akshare>=1.14.0 baostock>=0.8.8 pandas>=2.0.0 +matplotlib>=3.7.0 pytest>=7.0.0 diff --git a/run_example.py b/run_example.py index 2ff2ef8..ce11b48 100644 --- a/run_example.py +++ b/run_example.py @@ -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__": diff --git a/signals/__init__.py b/signals/__init__.py new file mode 100644 index 0000000..4fc8c97 --- /dev/null +++ b/signals/__init__.py @@ -0,0 +1,5 @@ +"""Alpha signal abstractions.""" +from signals.base import AlphaSignal +from signals.reversal import ReversalSignal + +__all__ = ["AlphaSignal", "ReversalSignal"] diff --git a/signals/base.py b/signals/base.py new file mode 100644 index 0000000..1beeab0 --- /dev/null +++ b/signals/base.py @@ -0,0 +1,28 @@ +"""Base class for cross-sectional alpha signals.""" +from abc import ABC, abstractmethod + +import pandas as pd + + +class AlphaSignal(ABC): + """A signal that maps a single stock's OHLCV history to a per-bar score. + + Higher scores indicate a stronger expected forward return. Implementations + operate on one stock at a time; cross-sectional ranking happens downstream. + """ + + @abstractmethod + def compute(self, df: pd.DataFrame) -> pd.Series: + """Compute the signal for one stock. + + Args: + df: OHLCV DataFrame with at least a ``close`` column, ordered by date. + + Returns: + Signal series aligned to ``df`` (NaN where undefined). + """ + + @property + @abstractmethod + def name(self) -> str: + """Human-readable signal identifier.""" diff --git a/signals/reversal.py b/signals/reversal.py new file mode 100644 index 0000000..b516a76 --- /dev/null +++ b/signals/reversal.py @@ -0,0 +1,22 @@ +"""Short-horizon reversal signal.""" +import pandas as pd + +from signals.base import AlphaSignal + + +class ReversalSignal(AlphaSignal): + """Negative trailing return: oversold stocks score high. + + The signal is ``-close.pct_change(lookback)``, so a stock that fell over the + lookback window gets a positive (bullish) score. + """ + + def __init__(self, lookback: int = 5): + self.lookback = lookback + + def compute(self, df: pd.DataFrame) -> pd.Series: + return -df["close"].pct_change(self.lookback) + + @property + def name(self) -> str: + return f"reversal_{self.lookback}d" diff --git a/strategies/alpha_strategy.py b/strategies/alpha_strategy.py new file mode 100644 index 0000000..dcceb29 --- /dev/null +++ b/strategies/alpha_strategy.py @@ -0,0 +1,26 @@ +"""Signal-driven multi-stock strategy.""" +import backtrader as bt +import pandas as pd + + +class AlphaStrategy(bt.Strategy): + """Trade each feed based on its precomputed ``signal`` line. + + The strategy delegates the buy/sell/hold decision to a portfolio builder + (e.g. ``ThresholdBuilder``) and sizes entries with ``order_target_percent``. + """ + + def __init__(self, builder): + self.builder = builder + + def next(self): + for data in self.datas: + sig = data.signal[0] + if pd.isna(sig): + continue + in_position = bool(self.getposition(data).size) + action = self.builder.build(float(sig), in_position) + if action.action == "buy": + self.order_target_percent(data=data, target=action.size_pct) + elif action.action == "sell": + self.close(data=data) diff --git a/tests/test_eval.py b/tests/test_eval.py new file mode 100644 index 0000000..df049ab --- /dev/null +++ b/tests/test_eval.py @@ -0,0 +1,53 @@ +"""Tests for cross-sectional IC evaluation.""" +import numpy as np +import pandas as pd + +from eval.metrics import evaluate_cross_sectional + + +def test_cross_sectional_keys_present(): + dates = pd.date_range("2024-01-01", periods=10) + cols = ["a", "b", "c"] + rng = np.random.default_rng(0) + signals = pd.DataFrame(rng.standard_normal((10, 3)), index=dates, columns=cols) + returns = pd.DataFrame(rng.standard_normal((10, 3)), index=dates, columns=cols) + res = evaluate_cross_sectional(signals, returns) + for key in ( + "ic_mean", "ic_std", "ir", "rank_ic_mean", "rank_ic_std", + "rank_ir", "hit_rate", "n_periods", + ): + assert key in res + + +def test_perfect_signal_has_positive_rank_ic(): + # When the signal equals next-period returns, rank IC should be ~1 each day. + dates = pd.date_range("2024-01-01", periods=8) + cols = ["a", "b", "c"] + rng = np.random.default_rng(42) + returns = pd.DataFrame(rng.standard_normal((8, 3)), index=dates, columns=cols) + signals = returns.copy() # perfect foresight + res = evaluate_cross_sectional(signals, returns) + assert res["rank_ic_mean"] > 0.99 + assert res["hit_rate"] == 1.0 + assert res["n_periods"] == 8 + + +def test_inverted_signal_has_negative_rank_ic(): + dates = pd.date_range("2024-01-01", periods=6) + cols = ["a", "b", "c"] + rng = np.random.default_rng(7) + returns = pd.DataFrame(rng.standard_normal((6, 3)), index=dates, columns=cols) + signals = -returns # perfectly wrong + res = evaluate_cross_sectional(signals, returns) + assert res["rank_ic_mean"] < -0.99 + + +def test_single_stock_falls_back_to_rolling(): + dates = pd.date_range("2024-01-01", periods=40) + rng = np.random.default_rng(1) + signals = pd.DataFrame({"a": rng.standard_normal(40)}, index=dates) + returns = pd.DataFrame({"a": rng.standard_normal(40)}, index=dates) + res = evaluate_cross_sectional(signals, returns) + # Rolling fallback still yields the standard metric keys. + assert "rank_ic_mean" in res + assert res["n_periods"] > 0 diff --git a/tests/test_signals.py b/tests/test_signals.py new file mode 100644 index 0000000..fe61aff --- /dev/null +++ b/tests/test_signals.py @@ -0,0 +1,38 @@ +"""Tests for alpha signal computation.""" +import pandas as pd + +from signals.reversal import ReversalSignal + + +def _make_df(closes): + return pd.DataFrame({"close": closes}) + + +def test_reversal_name(): + assert ReversalSignal(lookback=5).name == "reversal_5d" + assert ReversalSignal(lookback=10).name == "reversal_10d" + + +def test_reversal_is_negative_trailing_return(): + # Monotonically rising prices -> negative (bearish) reversal signal. + df = _make_df([10.0, 11.0, 12.0, 13.0, 14.0, 15.0]) + sig = ReversalSignal(lookback=5).compute(df) + # First 5 values are NaN (insufficient lookback). + assert sig.iloc[:5].isna().all() + # 15/10 - 1 = 0.5 return -> signal = -0.5 + assert abs(sig.iloc[5] - (-0.5)) < 1e-9 + + +def test_reversal_oversold_is_positive(): + # Falling prices -> positive (bullish) reversal signal. + df = _make_df([20.0, 18.0, 16.0, 14.0, 12.0, 10.0]) + sig = ReversalSignal(lookback=5).compute(df) + assert sig.iloc[5] > 0 + # 10/20 - 1 = -0.5 -> signal = +0.5 + assert abs(sig.iloc[5] - 0.5) < 1e-9 + + +def test_reversal_output_length_matches_input(): + df = _make_df([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]) + sig = ReversalSignal(lookback=3).compute(df) + assert len(sig) == len(df)