#!/usr/bin/env python3 """quad_resolve.py - resolve paper BUY_YES signals against real market outcomes. Reads the append-only ledger (mode=paper, action=BUY_YES_15M_UP), fetches each market from gamma-api, and writes quad_resolutions.csv with win/loss + ROI per signal. This is what the OOS evaluation needs (ledger alone never resolves). Math (standard Polymarket YES): entry price p, shares = notional/p. win -> payout shares*$1, ROI = (1-p)/p loss -> payout 0, ROI = -1.0 outcomes = ["Up","Down"]; outcomePrices[0] is Up (the side we buy). Idempotent: skips markets already in the resolutions file. """ import csv import json import os import sys import urllib.request from datetime import datetime, timezone LEDGER = os.environ.get("LEDGER_PATH", "/home/neo/quad_ledger.csv") RESOLUTIONS = os.environ.get("RESOLUTIONS_PATH", "/home/neo/quad_resolutions.csv") FIELDS = ["ts", "coin", "market_id", "question", "yes_price", "resolve_ts", "outcome", "win", "roi"] def now_iso(): return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def parse_yes_price(raw): """Ledger stores yes_price as either a JSON array of both outcome prices (autopilot) or a single YES price float (live-order paper mode).""" if not raw: return None raw = raw.strip() try: arr = json.loads(raw) if isinstance(arr, list) and arr: return float(arr[0]) # index 0 == Up == our side except Exception: pass try: return float(raw) except Exception: return None def fetch_market(mid): url = f"https://gamma-api.polymarket.com/markets/{mid}" req = urllib.request.Request(url, headers={"User-Agent": "quad-lab/0.1"}) with urllib.request.urlopen(req, timeout=30) as r: return json.load(r) def outcome_prices(m): """gamma-api returns outcomePrices as a JSON *string* like '["1", "0"]'.""" raw = m.get("outcomePrices") if isinstance(raw, list): return [str(x) for x in raw] if isinstance(raw, str): try: arr = json.loads(raw) return [str(x) for x in arr] except Exception: return [] return [] def load_resolved(): ids = set() if os.path.exists(RESOLUTIONS): with open(RESOLUTIONS) as f: for r in csv.DictReader(f): ids.add(r["market_id"]) return ids def append_resolution(row): exists = os.path.exists(RESOLUTIONS) with open(RESOLUTIONS, "a", newline="") as f: w = csv.DictWriter(f, fieldnames=FIELDS) if not exists: w.writeheader() w.writerow({k: row.get(k, "") for k in FIELDS}) def main(): if not os.path.exists(LEDGER): sys.exit(f"ledger not found: {LEDGER}") rows = [] with open(LEDGER) as f: rows = [r for r in csv.DictReader(f)] signals = [r for r in rows if r.get("action") == "BUY_YES_15M_UP" and r.get("mode") == "paper" and r.get("market_id")] done = load_resolved() pending = [r for r in signals if r.get("market_id") not in done] print(f"signals={len(signals)} already_resolved={len(signals) - len(pending)} pending={len(pending)}") wins = losses = unresolved = 0 total_roi = 0.0 for r in pending: mid = r["market_id"] try: m = fetch_market(mid) except Exception as e: print(f" {mid} {r['coin']}: fetch err {e}") unresolved += 1 continue status = m.get("umaResolutionStatus") prices = outcome_prices(m) if status != "resolved" or len(prices) < 2 or m.get("closed") is not True: print(f" {mid} {r['coin']}: not resolved yet (status={status})") unresolved += 1 continue p = parse_yes_price(r.get("yes_price")) if p is None: print(f" {mid} {r['coin']}: unparsable yes_price={r.get('yes_price')!r}") unresolved += 1 continue up_won = prices[0] == "1" roi = (1 - p) / p if up_won else -1.0 row = {"ts": now_iso(), "coin": r["coin"], "market_id": mid, "question": r.get("question", ""), "yes_price": str(p), "resolve_ts": r.get("resolve_ts", ""), "outcome": "UP" if up_won else "DOWN", "win": str(up_won), "roi": f"{roi:.4f}"} append_resolution(row) wins += 1 if up_won else 0 losses += 0 if up_won else 1 total_roi += roi print(f" {mid} {r['coin']}: price={p} -> {'WIN' if up_won else 'LOSS'} roi={roi:+.3f}") if wins + losses: n = wins + losses print(f"\nresolved now: {wins}W/{losses}L hit={wins/n:.3f} mean_roi={total_roi/n:+.3f}") if unresolved: print(f"unresolved still: {unresolved}") print("done", now_iso()) if __name__ == "__main__": main()