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()