fix: unify backtest execution and add forward validation candidates
Share point-in-time signals and a cash-funded order ledger across backtests and trading. Correct eligibility, missing-price fills, quality factors, regime inputs, warmup windows, and cash-injection accounting. Add reproducible registered-strategy comparisons and paired forward candidates with a 20% resize band. Validation: 125 Python tests, 23 web tests, TypeScript/Vite build, 87 strategy causality checks, and real-data target/order/equity parity for 10 strategies.
This commit is contained in:
@@ -173,14 +173,14 @@ def _cache_path() -> str:
|
||||
return os.path.join(CACHE_DIR, "sp500_history.json")
|
||||
|
||||
|
||||
def load_sp500_history(force_refresh: bool = False) -> dict[str, list[list[str | None]]]:
|
||||
def load_sp500_history(force_refresh: bool = False, *, refresh: bool = True) -> dict[str, list[list[str | None]]]:
|
||||
"""Load cached membership history, or rebuild if stale (>1 day old)."""
|
||||
path = _cache_path()
|
||||
if not force_refresh and os.path.exists(path):
|
||||
try:
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
if data.get("date") == str(date.today()):
|
||||
if not refresh or data.get("date") == str(date.today()):
|
||||
return data["intervals"]
|
||||
except Exception:
|
||||
pass
|
||||
@@ -285,13 +285,13 @@ def baostock_to_yahoo(code: str) -> str:
|
||||
return f"{num}.{'SS' if ex == 'sh' else 'SZ'}"
|
||||
|
||||
|
||||
def load_csi300_history(current: list[str] | None = None) -> dict[str, list[str]]:
|
||||
def load_csi300_history(current: list[str] | None = None, *, refresh: bool = True) -> dict[str, list[str]]:
|
||||
"""Monthly CSI 300 snapshots; appends ``current`` (or today's list) if it changed."""
|
||||
path = _csi300_path()
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
snaps = data["snapshots"]
|
||||
if current is None:
|
||||
if current is None and refresh:
|
||||
try:
|
||||
from universe import get_csi300
|
||||
current = get_csi300()
|
||||
@@ -309,6 +309,16 @@ def load_csi300_history(current: list[str] | None = None) -> dict[str, list[str]
|
||||
return snaps
|
||||
|
||||
|
||||
def pit_membership(market: str, dates: pd.DatetimeIndex, *, refresh: bool = False) -> pd.DataFrame:
|
||||
if market == "us":
|
||||
intervals = load_sp500_history(refresh=refresh)
|
||||
return membership_mask(dates, intervals, sorted(intervals))
|
||||
if market == "cn":
|
||||
snapshots = load_csi300_history(refresh=refresh)
|
||||
return csi300_membership_mask(dates, all_csi300_tickers_ever(snapshots), snapshots)
|
||||
raise ValueError(f"No PIT membership for {market}")
|
||||
|
||||
|
||||
def csi300_membership_mask(dates: pd.DatetimeIndex, tickers: list[str],
|
||||
snapshots: dict[str, list[str]] | None = None) -> pd.DataFrame:
|
||||
"""Boolean mask (dates x tickers): True while the ticker was in the CSI 300."""
|
||||
|
||||
Reference in New Issue
Block a user