research: point-in-time factor research harness (research/lab)
Event-driven long-only simulator with next-open fills, CN tradability (halts, limit-up/down, stamp duty), PIT S&P 500 / CSI 300 membership, rebalance-phase averaging, factor IC scans, maximin stepwise composite search across 5-year blocks, walk-forward LightGBM/ridge combiners, overlays, and deflated Sharpe. Fetchers pull 20y US OHLCV from Yahoo and CSI 300/500 ever-members (membership snapshot from baostock, bars from Yahoo). Research data lives in data/research/ (ignored). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
research/lab/run_ml.py
Normal file
52
research/lab/run_ml.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Walk-forward ML combination experiment.
|
||||
|
||||
PYTHONPATH=. uv run --with lightgbm python -m research.lab.run_ml --market us
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pickle
|
||||
import time
|
||||
import warnings
|
||||
|
||||
|
||||
from research.lab import ml
|
||||
from research.lab.factor_scan import load, phase_avg
|
||||
from research.lab.factors import FACTORS
|
||||
from research.lab.report import fmt
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
OUT = os.path.join("data", "research", "results")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--market", default="us")
|
||||
ap.add_argument("--models", nargs="*", default=["lgbm", "ridge", "ic_weight"])
|
||||
ap.add_argument("--horizons", nargs="*", type=int, default=[5, 20])
|
||||
ap.add_argument("--first-test-year", type=int, default=2010)
|
||||
args = ap.parse_args()
|
||||
mk = "us" if args.market == "us" else "cn"
|
||||
p, periods = load(args.market)
|
||||
names = [n for n, f in FACTORS.items() if mk in f.markets]
|
||||
t = time.time()
|
||||
feats = ml.feature_ranks(p, names)
|
||||
print(f"{len(names)} features in {time.time() - t:.0f}s", flush=True)
|
||||
evalp = {"design": (f"{args.first_test_year}-01-01", periods["design"][1]), "holdout": periods["holdout"]}
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
for model in args.models:
|
||||
for h in args.horizons:
|
||||
t = time.time()
|
||||
score = ml.walk_forward(p, feats, model=model, horizon=h, first_test_year=args.first_test_year)
|
||||
with open(os.path.join(OUT, f"ml_{args.market}_{model}_h{h}.pkl"), "wb") as fh:
|
||||
pickle.dump(score, fh)
|
||||
for top_n in (10, 20):
|
||||
parts = []
|
||||
for per, (a, b) in evalp.items():
|
||||
parts.append(f"{per}: {fmt(phase_avg(p, score, a, b, top_n, 5))}")
|
||||
print(f"{model:9s} h{h:<3d} top{top_n:<3d}", " | ".join(parts), f"[{time.time() - t:.0f}s]", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user