Files
quant-trading/strategies/base.py
Gahow Wang cc1366b2dc feat: robust-momentum strategies live; fix live universe, lots and UI backtests
Strategies
- BarStrategy protocol (close/open/high/low/volume bars; row d uses bars
  through d-1) and RobustMomentumStrategy: overnight_recovery_top10 (US),
  lowrisk_momentum_top15 and lowbeta_sharpe_mom_top20 (CN), top-N with
  rank hysteresis, 10-day rebalance, weights drift between rebalances.
- trader: strategy_fields/strategy_bars/bar_session_weights; one signal
  path (_signals_for_sessions) replaces generate_target_weights.

Live fixes
- CSI 300 constituents come from China Securities Index's file; the
  Wikipedia table was 122 names off the real index.
- S&P 500 change log moved to "Historical components of the S&P 500".
- data_manager: tickers missing from a partial Yahoo batch are retried
  alone before being marked unavailable (CEG, GEV, SNDK ... were dropped).
  update() takes fields=[...] and stores high/low/volume panels.
- Whole-lot orders go to the nearest lot within cash (CN books held ~62%
  cash); held names are resized only beyond RESIZE_BAND (10%), so books no
  longer pay a ticket fee to trade daily drift.

Web backtests
- backtest_runner.py replaces the duplicated bridge/precompute code: PIT
  membership in both markets (data/csi300_history.json), stock universe
  limited to index members (ETFs and benchmarks in the shared panel were
  pickable), and the book kept like the live trader (simulate_live_book:
  signal timing, drift, order floor, resize band, fees on orders placed).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 19:12:02 +08:00

50 lines
1.8 KiB
Python

from abc import ABC, abstractmethod
import pandas as pd
class Strategy(ABC):
"""
Abstract base class for all strategies.
"""
@abstractmethod
def generate_signals(self, data):
"""
Generates trading signals for each stock.
:param data: A pandas DataFrame of historical price data.
:return: A pandas Series with the same index as the input data,
containing the trading signals (e.g., 'buy', 'hold', 'sell').
"""
pass
class BarStrategy(Strategy):
"""Strategy on completed daily bars with several fields.
``fields`` names the panels it reads among close/open/high/low/volume.
``generate_signals(bars)`` takes ``{field: DataFrame}`` (a bare close
DataFrame is accepted when only "close" is needed) and returns weights
whose row t uses bars through t-1, like every Strategy. Backtests may add
``bars["eligible"]`` (bool, point-in-time index membership): prices stay
unmasked so signals see full histories, and only eligible names are held.
Timing differs from close-price strategies in the live trader: those read
the session's open as their latest price, while a BarStrategy only uses
completed bars, so the weights of row t trade at the close of t.
"""
fields: tuple[str, ...] = ("close",)
def generate_signals(self, data):
bars = data if isinstance(data, dict) else {"close": data}
missing = [f for f in self.fields if f not in bars]
if missing:
raise ValueError(f"{type(self).__name__} needs bar fields {missing}")
return self.generate_weights(bars)
@abstractmethod
def generate_weights(self, bars: dict[str, pd.DataFrame]) -> pd.DataFrame:
"""Weights (dates x assets), row t using bars through t-1."""