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>
48 lines
1.5 KiB
Python
48 lines
1.5 KiB
Python
"""
|
|
Re-run the existing factor_combo champions under the research engine, with
|
|
the same rules every new candidate gets: next-open execution, PIT universe,
|
|
costs, and averaging over rebalance phases.
|
|
|
|
PYTHONPATH=. uv run python -m research.lab.baselines --market us
|
|
"""
|
|
|
|
import argparse
|
|
import warnings
|
|
|
|
import pandas as pd
|
|
|
|
from research.lab.factor_scan import load, phase_avg
|
|
from research.lab.report import fmt
|
|
from strategies.factor_combo import SIGNAL_REGISTRY
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
CHAMPIONS = {
|
|
"us": ["rec_mfilt+deep_upvol", "ma200+mom7m+rec126", "rec_mfilt+ma200", "mom7m+rec126"],
|
|
"cn": ["up_cap+quality_mom", "down_resil+qual_mom", "rec63+mom_gap", "up_cap+mom_gap"],
|
|
}
|
|
|
|
|
|
def baseline_scores(p, market: str) -> dict[str, pd.DataFrame]:
|
|
mk = "us" if market == "us" else "cn"
|
|
# factor_combo signals were built on prices masked to index membership
|
|
px = p.close.where(p.member).ffill(limit=5)
|
|
return {name: SIGNAL_REGISTRY[name](px) for name in CHAMPIONS[mk]}
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--market", default="us")
|
|
args = ap.parse_args()
|
|
p, periods = load(args.market)
|
|
scores = baseline_scores(p, args.market)
|
|
for name, s in scores.items():
|
|
for every, label in [(5, "weekly"), (21, "monthly")]:
|
|
for per, (a, b) in periods.items():
|
|
st = phase_avg(p, s, a, b, 10, every)
|
|
print(f"{name:22s} top10 {label:8s} {per:8s} {fmt(st)}", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|