File size: 4,888 Bytes
8fdaeba | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #!/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() |