#!/usr/bin/env python3 """Phase 2 — ingest 1-minute and daily bars for the frozen universe into the private cache (resumable). usage: ingest_bars.py [--config configs/instruments.yaml] [--classes etf,stock,...] [--symbols SPY,QQQ] [--workers 3] Writes results/manifests/ingest_manifest.json (dataset versions, sha256, row counts) — no bar values.""" from __future__ import annotations import argparse import json import sys from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path import yaml ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) from wp46.findata_client import FinDataClient # noqa: E402 from wp46.findata_client.ingest import DAILY_FOR, DATASET_FOR, ingest_symbol # noqa: E402 def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--config", default=str(ROOT / "configs" / "instruments.yaml")) ap.add_argument("--classes", default="etf,stock,futures,fx,crypto") ap.add_argument("--symbols", default="") ap.add_argument("--start", default="2000-01-01") ap.add_argument("--end", default="2026-10-30") ap.add_argument("--workers", type=int, default=3) ap.add_argument("--no-daily", action="store_true") a = ap.parse_args() cfg = yaml.safe_load(open(a.config)) uni = cfg.get("universe") or cfg["candidates"] want = set(a.symbols.split(",")) if a.symbols else None jobs = [] for cls in a.classes.split(","): for s in uni.get(cls, []): if want and s not in want: continue jobs.append((cls, s)) fd = FinDataClient(log_path=ROOT / "results" / "logs" / "ingest_provenance.jsonl") man_path = ROOT / "results" / "manifests" / "ingest_manifest.json" manifest = json.loads(man_path.read_text()) if man_path.exists() else {} def work(cls, s): recs = {"minute": ingest_symbol(fd, DATASET_FOR[cls], s, a.start, a.end)} if not a.no_daily and cls in DAILY_FOR: recs["daily"] = ingest_symbol(fd, DAILY_FOR[cls], s, a.start, a.end) return cls, s, recs with ThreadPoolExecutor(a.workers) as ex: futs = [ex.submit(work, c, s) for c, s in jobs] for f in as_completed(futs): cls, s, recs = f.result() manifest[f"{cls}:{s}"] = recs n = sum(r.get("row_count") or 0 for r in recs["minute"]) print(f"{cls:8s} {s:8s} 1m rows={n:,} chunks={len(recs['minute'])} daily_chunks={len(recs.get('daily', []))}", flush=True) man_path.parent.mkdir(parents=True, exist_ok=True) man_path.write_text(json.dumps(manifest, indent=1)) # regime variables (index daily) for s in cfg.get("regime_variables", {}).get("index", []): manifest[f"index:{s}"] = {"daily": ingest_symbol(fd, DAILY_FOR["index"], s, a.start, a.end)} man_path.write_text(json.dumps(manifest, indent=1)) print("manifest", man_path) if __name__ == "__main__": main()