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:
@@ -1,11 +1,12 @@
|
||||
"""
|
||||
Forward stepwise selection of equal-weight rank composites, scored on the
|
||||
DESIGN period only; the holdout is evaluated once for the final picks.
|
||||
DESIGN period only; historical validation is reported separately. Previously inspected history
|
||||
is not a fresh out-of-sample test.
|
||||
|
||||
PYTHONPATH=. uv run python -m research.lab.stepwise --market us --max-k 4
|
||||
|
||||
Every evaluated composite is appended to the trial ledger
|
||||
(data/research/results/trials_{market}.csv) so deflated Sharpe can count them.
|
||||
(data/research/results/trials_{market}.jsonl) so deflated Sharpe can count them.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -18,6 +19,7 @@ import pandas as pd
|
||||
|
||||
from research.lab.factor_scan import RESULTS, load, phase_avg
|
||||
from research.lab.factors import FACTORS, zrank
|
||||
from research.lab.trials import append_trials
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
@@ -55,7 +57,8 @@ def main() -> None:
|
||||
print(f"{len(names)} factor ranks in {time.time() - t:.0f}s", flush=True)
|
||||
a, b = periods["design"]
|
||||
ledger = []
|
||||
subs = SUBPERIODS[mk]
|
||||
subs = [(max(sa, a), min(sb, b)) for sa, sb in SUBPERIODS[mk]
|
||||
if max(sa, a) <= min(sb, b)]
|
||||
|
||||
def evaluate(combo: tuple[str, ...]) -> dict:
|
||||
s = sum(ranks[n] for n in combo) / len(combo)
|
||||
@@ -93,23 +96,25 @@ def main() -> None:
|
||||
best_overall += ranked[:args.beam]
|
||||
|
||||
if args.objective == "maximin":
|
||||
_save(ledger, args.market)
|
||||
_save(ledger, args.market, {"selection_start": a, "selection_end": b,
|
||||
"role": "development", "config": vars(args)})
|
||||
return
|
||||
# holdout, once, for the beam winners of each k
|
||||
print("=== holdout of design winners ===")
|
||||
print("=== historical validation of design winners (previously inspected data) ===")
|
||||
ha, hb = periods["holdout"]
|
||||
for c, st in best_overall:
|
||||
s = sum(ranks[n] for n in c) / len(c)
|
||||
hs = phase_avg(p, s, ha, hb, args.top_n, args.every)
|
||||
print(f" {'+'.join(c):55s} D sh {st['sharpe']:.2f} cagr {st['cagr']*100:5.1f}% | "
|
||||
f"H sh {hs['sharpe']:.2f} cagr {hs['cagr']*100:5.1f}% mdd {hs['mdd']*100:5.1f}%", flush=True)
|
||||
_save(ledger, args.market)
|
||||
_save(ledger, args.market, {"selection_start": a, "selection_end": b,
|
||||
"role": "development", "config": vars(args)})
|
||||
|
||||
|
||||
def _save(ledger: list[dict], market: str) -> None:
|
||||
def _save(ledger: list[dict], market: str, protocol: dict) -> None:
|
||||
os.makedirs(RESULTS, exist_ok=True)
|
||||
path = os.path.join(RESULTS, f"trials_{market}.csv")
|
||||
pd.DataFrame(ledger).to_csv(path, mode="a", header=not os.path.exists(path), index=False)
|
||||
path = os.path.join(RESULTS, f"trials_{market}.jsonl")
|
||||
append_trials(path, ledger, protocol)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user