book: scaffold + ch00 (execution trail as spine) — evidence exp 8-31, round 3
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Minimal OptimalStopControl threshold calibration — valid-window grid search.
|
||||
|
||||
This repo found OptimalStopControl thresholds overfit the valid window (valid
|
||||
+7.5% → test −17.7% on one calibration). This script runs a small grid over
|
||||
(entry_pct, exit_pct, max_hold_days) on the VALID window, reports per-config
|
||||
excess return + turnover, and warns when the best valid config is a spike.
|
||||
|
||||
Reference repo impl: tac-qlib/examples/run_optstop_compare.py.
|
||||
|
||||
python examples/run_optstop_compare.py --universe AAPL,MSFT,QQQ
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
GRID = {
|
||||
"entry_pct": [0.7, 0.85, 0.95],
|
||||
"exit_pct": [0.5, 0.7],
|
||||
"max_hold_days": [5, 10],
|
||||
}
|
||||
|
||||
|
||||
def evaluate_config(lake_root: str, universe: list[str], window: tuple, config: dict) -> dict:
|
||||
"""Simplified stand-in: train the RankIC model, backtest OptimalStopControl
|
||||
on `window`, return (ann_excess_return, turnover, max_drawdown).
|
||||
|
||||
The real repo impl calls qlib.backtest with the strategy and reads
|
||||
report_normal.csv + risk.csv. Keep the interface here so the grid loop is
|
||||
reusable.
|
||||
"""
|
||||
# placeholder — plug in the real backtest here
|
||||
return {"ann_excess": 0.0, "turnover": 0.0, "max_dd": 0.0}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--lake-root", default=os.environ.get("TAC_LAKE_DIR", ""))
|
||||
ap.add_argument("--universe", default="AAPL,MSFT,QQQ,IVV,SMH,TLT")
|
||||
args = ap.parse_args()
|
||||
universe = [s.strip().upper() for s in args.universe.split(",")]
|
||||
valid = ("2026-06-01", "2026-06-30")
|
||||
test = ("2026-07-01", "2026-08-06")
|
||||
|
||||
keys = list(GRID)
|
||||
results = []
|
||||
for combo in itertools.product(*[GRID[k] for k in keys]):
|
||||
config = dict(zip(keys, combo))
|
||||
v = evaluate_config(args.lake_root, universe, valid, config)
|
||||
t = evaluate_config(args.lake_root, universe, test, config)
|
||||
results.append({**config, "valid_excess": v["ann_excess"], "test_excess": t["ann_excess"]})
|
||||
|
||||
df = pd.DataFrame(results).sort_values("valid_excess", ascending=False)
|
||||
print(df.head(10).to_string(index=False))
|
||||
# Overfit check: how far is the best-valid config from the median test config?
|
||||
med = df["test_excess"].median()
|
||||
best = df.iloc[0]
|
||||
print(f"\nmedian test excess: {med:+.3f} | best-valid test excess: {best['test_excess']:+.3f}")
|
||||
if abs(best["test_excess"] - med) > 0.10:
|
||||
print("WARNING: best-valid config is an outlier on test — likely overfit, prefer robust defaults")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user