40Hz commited on
Commit
8fdaeba
·
verified ·
1 Parent(s): fb405fd
Files changed (1) hide show
  1. quad_resolutions.csv +145 -0
quad_resolutions.csv ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """quad_resolve.py - resolve paper BUY_YES signals against real market outcomes.
3
+
4
+ Reads the append-only ledger (mode=paper, action=BUY_YES_15M_UP), fetches each
5
+ market from gamma-api, and writes quad_resolutions.csv with win/loss + ROI per
6
+ signal. This is what the OOS evaluation needs (ledger alone never resolves).
7
+
8
+ Math (standard Polymarket YES): entry price p, shares = notional/p.
9
+ win -> payout shares*$1, ROI = (1-p)/p
10
+ loss -> payout 0, ROI = -1.0
11
+ outcomes = ["Up","Down"]; outcomePrices[0] is Up (the side we buy).
12
+
13
+ Idempotent: skips markets already in the resolutions file.
14
+ """
15
+ import csv
16
+ import json
17
+ import os
18
+ import sys
19
+ import urllib.request
20
+ from datetime import datetime, timezone
21
+
22
+ LEDGER = os.environ.get("LEDGER_PATH", "/home/neo/quad_ledger.csv")
23
+ RESOLUTIONS = os.environ.get("RESOLUTIONS_PATH", "/home/neo/quad_resolutions.csv")
24
+ FIELDS = ["ts", "coin", "market_id", "question", "yes_price", "resolve_ts",
25
+ "outcome", "win", "roi"]
26
+
27
+
28
+ def now_iso():
29
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
30
+
31
+
32
+ def parse_yes_price(raw):
33
+ """Ledger stores yes_price as either a JSON array of both outcome prices
34
+ (autopilot) or a single YES price float (live-order paper mode)."""
35
+ if not raw:
36
+ return None
37
+ raw = raw.strip()
38
+ try:
39
+ arr = json.loads(raw)
40
+ if isinstance(arr, list) and arr:
41
+ return float(arr[0]) # index 0 == Up == our side
42
+ except Exception:
43
+ pass
44
+ try:
45
+ return float(raw)
46
+ except Exception:
47
+ return None
48
+
49
+
50
+ def fetch_market(mid):
51
+ url = f"https://gamma-api.polymarket.com/markets/{mid}"
52
+ req = urllib.request.Request(url, headers={"User-Agent": "quad-lab/0.1"})
53
+ with urllib.request.urlopen(req, timeout=30) as r:
54
+ return json.load(r)
55
+
56
+
57
+ def outcome_prices(m):
58
+ """gamma-api returns outcomePrices as a JSON *string* like '["1", "0"]'."""
59
+ raw = m.get("outcomePrices")
60
+ if isinstance(raw, list):
61
+ return [str(x) for x in raw]
62
+ if isinstance(raw, str):
63
+ try:
64
+ arr = json.loads(raw)
65
+ return [str(x) for x in arr]
66
+ except Exception:
67
+ return []
68
+ return []
69
+
70
+
71
+ def load_resolved():
72
+ ids = set()
73
+ if os.path.exists(RESOLUTIONS):
74
+ with open(RESOLUTIONS) as f:
75
+ for r in csv.DictReader(f):
76
+ ids.add(r["market_id"])
77
+ return ids
78
+
79
+
80
+ def append_resolution(row):
81
+ exists = os.path.exists(RESOLUTIONS)
82
+ with open(RESOLUTIONS, "a", newline="") as f:
83
+ w = csv.DictWriter(f, fieldnames=FIELDS)
84
+ if not exists:
85
+ w.writeheader()
86
+ w.writerow({k: row.get(k, "") for k in FIELDS})
87
+
88
+
89
+ def main():
90
+ if not os.path.exists(LEDGER):
91
+ sys.exit(f"ledger not found: {LEDGER}")
92
+ rows = []
93
+ with open(LEDGER) as f:
94
+ rows = [r for r in csv.DictReader(f)]
95
+
96
+ signals = [r for r in rows if r.get("action") == "BUY_YES_15M_UP"
97
+ and r.get("mode") == "paper" and r.get("market_id")]
98
+ done = load_resolved()
99
+ pending = [r for r in signals if r.get("market_id") not in done]
100
+ print(f"signals={len(signals)} already_resolved={len(signals) - len(pending)} pending={len(pending)}")
101
+
102
+ wins = losses = unresolved = 0
103
+ total_roi = 0.0
104
+ for r in pending:
105
+ mid = r["market_id"]
106
+ try:
107
+ m = fetch_market(mid)
108
+ except Exception as e:
109
+ print(f" {mid} {r['coin']}: fetch err {e}")
110
+ unresolved += 1
111
+ continue
112
+ status = m.get("umaResolutionStatus")
113
+ prices = outcome_prices(m)
114
+ if status != "resolved" or len(prices) < 2 or m.get("closed") is not True:
115
+ print(f" {mid} {r['coin']}: not resolved yet (status={status})")
116
+ unresolved += 1
117
+ continue
118
+ p = parse_yes_price(r.get("yes_price"))
119
+ if p is None:
120
+ print(f" {mid} {r['coin']}: unparsable yes_price={r.get('yes_price')!r}")
121
+ unresolved += 1
122
+ continue
123
+ up_won = prices[0] == "1"
124
+ roi = (1 - p) / p if up_won else -1.0
125
+ row = {"ts": now_iso(), "coin": r["coin"], "market_id": mid,
126
+ "question": r.get("question", ""), "yes_price": str(p),
127
+ "resolve_ts": r.get("resolve_ts", ""),
128
+ "outcome": "UP" if up_won else "DOWN",
129
+ "win": str(up_won), "roi": f"{roi:.4f}"}
130
+ append_resolution(row)
131
+ wins += 1 if up_won else 0
132
+ losses += 0 if up_won else 1
133
+ total_roi += roi
134
+ print(f" {mid} {r['coin']}: price={p} -> {'WIN' if up_won else 'LOSS'} roi={roi:+.3f}")
135
+
136
+ if wins + losses:
137
+ n = wins + losses
138
+ print(f"\nresolved now: {wins}W/{losses}L hit={wins/n:.3f} mean_roi={total_roi/n:+.3f}")
139
+ if unresolved:
140
+ print(f"unresolved still: {unresolved}")
141
+ print("done", now_iso())
142
+
143
+
144
+ if __name__ == "__main__":
145
+ main()