fix(backtest): settle delisted US holdings (engine v3), PIT membership before 2011, registry causality test
- execution/trader: a held US stock with no print for 5 sessions that has left the index is sold at its last print (execution.SETTLE_AFTER_SESSIONS), in both the batch replay and the live session ledger. Acquired names used to stay at their last mark forever: lowrisk_top20 ended 2017-12..2026-10 with 11 of 26 dead positions, and CAGR rises 1.7-5.5pp once they settle. ENGINE_VERSION cash-ledger-pit-v3; the API cache key follows. - universe_history: current members whose pre-2011 addition the Wikipedia change log missed now start at the list page's "Date added" (80 of 94 current members added 2001-2010 were open-start, e.g. ISRG, MA, BKNG). Membership from 2016 on is unchanged; the research panel cache now keys on the membership intervals. - research/lab/registered: pure-ETF strategies replay on the ETF panel (they ran as all-cash books or crashed); prepare_strategy refuses an ETF strategy that lacks its ETF universe. - research/lab/factors: trend_hzz uses a month's regression betas only from the session its forward returns end (short months read 1-2 sessions ahead). - tests: every registered strategy (US and CN) is truncation-invariant on the production signal path; batch and live ledgers settle a delisting identically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ Panels are cached in data/research/*.panel.pkl; delete them after refetching.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
@@ -103,11 +104,18 @@ def _tiingo_sig() -> list:
|
||||
return [len(files), round(mtime, 3)]
|
||||
|
||||
|
||||
def _membership_sig() -> str:
|
||||
with open(os.path.join("data", "sp500_history.json")) as fh:
|
||||
intervals = json.load(fh)["intervals"]
|
||||
return hashlib.sha256(json.dumps(intervals, sort_keys=True).encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def load_us() -> Panel:
|
||||
"""Cached US panel, rebuilt whenever the Tiingo overlay changes."""
|
||||
"""Cached US panel, rebuilt whenever the Tiingo overlay or the S&P 500
|
||||
membership intervals change."""
|
||||
path = os.path.join(RAW_DIR, "us.panel.pkl")
|
||||
key = os.path.join(RAW_DIR, "us.panel.key")
|
||||
sig = _tiingo_sig()
|
||||
sig = _tiingo_sig() + [_membership_sig()]
|
||||
if os.path.exists(path) and os.path.exists(key):
|
||||
with open(key) as fh:
|
||||
if json.load(fh) == sig:
|
||||
|
||||
Reference in New Issue
Block a user