1"""Build a license-free synthetic cache (bars -> grids) and manifest so the whole pipeline runs without FinData.2Usage: make_synthetic.py [--cache data/synthetic/cache] [--n 4] [--start 2024-01-02 --end 2024-05-31]"""3from __future__ import annotations45import argparse6import hashlib7import json8import sys9from pathlib import Path1011sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))12from chronos_x.data.synthetic import synthetic_bars # noqa: E40213from chronos_x.data.features import build_grid, save_grid # noqa: E4021415ROOT = Path(__file__).resolve().parents[1]161718def main() -> None:19 ap = argparse.ArgumentParser()20 ap.add_argument("--cache", default="data/synthetic/cache"); ap.add_argument("--n", type=int, default=4)21 ap.add_argument("--start", default="2024-01-02"); ap.add_argument("--end", default="2024-05-31")22 a = ap.parse_args()23 cache = ROOT / a.cache24 inst = []25 for i in range(a.n):26 sym = f"SYN{i:02d}"; ac = "etf" if i % 4 == 3 else "stock"27 df, actions = synthetic_bars(sym, a.start, a.end, seed=100 + i, p_missing=0.02 + 0.03 * (i % 3), split=("2024-03-04", 2.0) if i == 1 else None,28 sigma=3e-4 * (1 + 0.5 * i), asset_class=ac)29 arrays, meta = build_grid(df, actions, a.start, a.end, sym, ac)30 save_grid(arrays, meta, cache / "grids" / f"{a.start}_{a.end}" / sym)31 inst.append({"symbol": sym, "asset_class": ac, "role": "heldout_instrument" if i == a.n - 1 else "pooled", "adv_usd": 1e7 * (i + 1),32 "adv_tercile": ["low", "mid", "high"][i % 3], "vol_tercile": ["low", "mid", "high"][(i + 1) % 3], "group": "synthetic",33 "delisted": False, "status": "active", "first_date": a.start, "last_date": a.end})34 man = {"config": {"synthetic": True}, "instruments": inst, "pilot_symbols": [r["symbol"] for r in inst if r["role"] == "pooled"],35 "feasibility_symbols": [r["symbol"] for r in inst if r["role"] == "pooled"], "n_selected": len(inst)}36 man["sha256"] = hashlib.sha256(json.dumps(man, sort_keys=True).encode()).hexdigest()37 (ROOT / "data" / "manifests").mkdir(parents=True, exist_ok=True)38 (ROOT / "data" / "manifests" / "universe_synthetic.json").write_text(json.dumps(man, indent=1))39 print(f"synthetic cache at {cache} ({a.n} instruments, {meta.counts['sessions']} sessions)")404142if __name__ == "__main__":43 main()44