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>
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""
|
|
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()
|