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:
2026-10-01 02:24:01 -07:00
parent 761725d2c8
commit 722cade974
45 changed files with 1561 additions and 1021 deletions

View File

@@ -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."""