Files
2025-11-14 23:16:51 +00:00

83 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""
Compare paper vs. live fills to produce a lightweight TCA summary.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import pandas as pd
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Compare paper vs live fills.")
parser.add_argument("--paper", required=True, help="Path to paper fills CSV.")
parser.add_argument("--live", required=True, help="Path to live fills CSV.")
parser.add_argument("--out", default="results/execution/tca_summary.json", help="Output JSON path.")
return parser.parse_args()
def load(path: str) -> pd.DataFrame:
csv = Path(path)
if not csv.exists():
raise SystemExit(f"fills CSV not found: {csv}")
df = pd.read_csv(csv)
if df.empty:
raise SystemExit(f"{csv} is empty")
if "ts" in df.columns:
df["ts"] = pd.to_datetime(df["ts"])
return df
def summary(df: pd.DataFrame, label: str) -> dict:
pnl = df["pnl"] if "pnl" in df.columns else pd.Series(dtype=float)
latency = df["adapter_latency_ms"] if "adapter_latency_ms" in df.columns else pd.Series(dtype=float)
return {
f"{label}_trade_count": int(len(df)),
f"{label}_total_pnl": float(pnl.sum()) if not pnl.empty else None,
f"{label}_avg_pnl": float(pnl.mean()) if not pnl.empty else None,
f"{label}_avg_latency_ms": float(latency.mean()) if not latency.empty else None,
}
def pnl_gap(paper: pd.DataFrame, live: pd.DataFrame) -> dict:
cols = []
if "ts" in paper.columns and "ts" in live.columns:
merged = pd.merge(
paper[["ts", "pnl"]].rename(columns={"pnl": "paper_pnl"}),
live[["ts", "pnl"]].rename(columns={"pnl": "live_pnl"}),
on="ts",
how="outer",
)
else:
merged = pd.DataFrame({"paper_pnl": paper.get("pnl"), "live_pnl": live.get("pnl")})
merged = merged.fillna(0.0)
merged["pnl_diff"] = merged["live_pnl"] - merged["paper_pnl"]
return {
"pnl_diff_mean": float(merged["pnl_diff"].mean()),
"pnl_diff_std": float(merged["pnl_diff"].std(ddof=0)),
}
def main() -> None:
args = parse_args()
paper = load(args.paper)
live = load(args.live)
report = {"paper_path": args.paper, "live_path": args.live}
report.update(summary(paper, "paper"))
report.update(summary(live, "live"))
report.update(pnl_gap(paper, live))
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(report, indent=2, default=float), encoding="utf-8")
print(json.dumps(report, indent=2, default=float))
if __name__ == "__main__":
main()