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>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class Strategy(ABC):
|
||||
"""
|
||||
Abstract base class for all strategies.
|
||||
@@ -15,3 +18,32 @@ class Strategy(ABC):
|
||||
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."""
|
||||
|
||||
Reference in New Issue
Block a user