from __future__ import annotations import argparse import json from datetime import datetime, timezone from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_HISTORY = ROOT / "Temp" / "proposal_evaluation_history.json" DEFAULT_OUT = ROOT / "Temp" / "live_trade_outcome_ledger_v1.json" FORMULA_ID = "LIVE_TRADE_OUTCOME_LEDGER_V1" def _load(path: Path) -> dict[str, Any]: if not path.exists(): return {} try: obj = json.loads(path.read_text(encoding="utf-8")) except Exception: return {} return obj if isinstance(obj, dict) else {} def _f(v: Any, default: float | None = None) -> float | None: try: return float(v) except Exception: return default def _origin(rec: dict[str, Any]) -> str: proposal_id = str(rec.get("proposal_id") or "") if proposal_id.startswith("REPLAY:"): return "REPLAY" origin = str(rec.get("data_origin") or rec.get("validation_status") or "").upper() if origin in {"LIVE", "PAPER", "REPLAY"}: return origin if origin == "REPLAY_BACKFILL": return "REPLAY" return "LIVE" if origin.startswith("OPERATIONAL") or origin == "" else "PAPER" def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--history", default=str(DEFAULT_HISTORY)) ap.add_argument("--out", default=str(DEFAULT_OUT)) args = ap.parse_args() hist_path = Path(args.history) if not hist_path.is_absolute(): hist_path = ROOT / hist_path out_path = Path(args.out) if not out_path.is_absolute(): out_path = ROOT / out_path hist = _load(hist_path) records = hist.get("records") if isinstance(hist.get("records"), list) else [] rows: list[dict[str, Any]] = [] live_count = paper_count = replay_count = 0 operational_count = 0 for rec in records: if not isinstance(rec, dict): continue origin = _origin(rec) if origin == "LIVE": live_count += 1 operational_count += 1 elif origin == "PAPER": paper_count += 1 operational_count += 1 else: replay_count += 1 rows.append({ "proposal_id": rec.get("proposal_id"), "ticker": rec.get("ticker"), "name": rec.get("name"), "proposal_date": rec.get("proposal_date"), "action": rec.get("action") or rec.get("recommended_action"), "origin": origin, "is_replay": origin == "REPLAY", "t5_return_pct": _f(rec.get("t5_return_pct")), "t20_return_pct": _f(rec.get("t20_return_pct")), "decision_correct": rec.get("decision_correct"), "generated_at": rec.get("generated_at"), }) result = { "formula_id": FORMULA_ID, "generated_at": datetime.now(timezone.utc).isoformat(), "builder_version": "v1.live_trade_ledger", "live_sample_count": live_count, "paper_sample_count": paper_count, "replay_sample_count": replay_count, "t20_operational_sample": live_count + paper_count, "operational_sample_count": operational_count, "split_by_origin": { "LIVE": live_count, "PAPER": paper_count, "REPLAY": replay_count, }, "rows": rows[:2000], "note": "LIVE/PAPER sample rows are separated from REPLAY backfill; operational sample count excludes replay.", } out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())