From e909c3a050e15cba2f1045773ab3e5f7180da06e Mon Sep 17 00:00:00 2001 From: jaxperro Date: Tue, 21 Jul 2026 11:49:45 -0400 Subject: [PATCH] copybot: _token_market falls back to gamma's closed=true view (#18 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gamma's default /markets listing HIDES closed markets — the resolved Odyssey market returns [] without &closed=true. Closed markets are exactly the population repair_market_meta exists for (resolved-but-stuck positions), so the gamma-only lookup left the paper book's own Odyssey copy (178.57sh, resolved won) and its France-WC position permanently cond-less; both verified resolving via the closed view. The fetch edge is factored into _gamma_markets so the variant order, caching, and error-then-fallback paths are unit-tested. Co-Authored-By: Claude Fable 5 --- copybot.py | 45 +++++++++++++++++++++++++-------------- tests/test_meta_repair.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/copybot.py b/copybot.py index 8f136dc3..a3a15bd2 100644 --- a/copybot.py +++ b/copybot.py @@ -142,28 +142,41 @@ _TOK_MKT = {} # token id -> (title, outcome, cond), immutable _TOK_LOCK = threading.Lock() +def _gamma_markets(qs): + """One gamma /markets fetch (separate so tests can stub the HTTP edge).""" + req = urllib.request.Request( + f"https://gamma-api.polymarket.com/markets?{qs}", + headers={"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req, timeout=12, context=SSL_CTX) as r: + return json.loads(r.read().decode()) or [] + + def _token_market(token): """token id -> (title, outcome, conditionId) via the gamma API, or None. A failed lookup means the caller drops the seed — the backstop poll still - copies the trade once the indexer catches up (honest, never guessy).""" + copies the trade once the indexer catches up (honest, never guessy). + Gamma's DEFAULT listing hides closed markets (#18 follow-up: the resolved + Odyssey market returned [] until &closed=true) — and closed markets are + exactly what repair_market_meta needs, so fall back to the closed view.""" with _TOK_LOCK: if token in _TOK_MKT: return _TOK_MKT[token] - try: - req = urllib.request.Request( - f"https://gamma-api.polymarket.com/markets?clob_token_ids={token}", - headers={"User-Agent": "Mozilla/5.0"}) - with urllib.request.urlopen(req, timeout=12, context=SSL_CTX) as r: - m = (json.loads(r.read().decode()) or [{}])[0] - toks = json.loads(m["clobTokenIds"]) - got = (m.get("question") or "", - json.loads(m["outcomes"])[toks.index(token)], - m.get("conditionId")) - except Exception: - return None - with _TOK_LOCK: - _TOK_MKT[token] = got - return got + for extra in ("", "&closed=true"): + try: + ms = _gamma_markets(f"clob_token_ids={token}{extra}") + if not ms: + continue + m = ms[0] + toks = json.loads(m["clobTokenIds"]) + got = (m.get("question") or "", + json.loads(m["outcomes"])[toks.index(token)], + m.get("conditionId")) + except Exception: + continue + with _TOK_LOCK: + _TOK_MKT[token] = got + return got + return None def fills_from_tx(tx, wallet): diff --git a/tests/test_meta_repair.py b/tests/test_meta_repair.py index f19e5779..aedf16a2 100644 --- a/tests/test_meta_repair.py +++ b/tests/test_meta_repair.py @@ -14,6 +14,8 @@ import time sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) import copybot # noqa: E402 +REAL_TOKEN_MARKET = copybot._token_market # section 6 tests the real one + TOK = "35358889366059131445607416132477521541386270328042995107702816540034073998102" MISS_TOK = "111" COND = "0xd2a97dd956e70c9c4abda30d2864588ece0c7213013eb3fa664b88509ff829b1" @@ -145,5 +147,40 @@ if b["status"] != "won" or b["pnl"] is None or TOK in st["my_pos"]: if abs(st["cash"] - 11.694914) > 1e-9: fails.append(f"settle cash wrong: {st['cash']}") +# 6) _token_market falls back to gamma's closed=true view (#18 follow-up: +# the default listing hides closed markets — the exact population repair +# cares about) and caches only success +import json as _json # noqa: E402 + +MKT = {"question": TITLE, "conditionId": COND, + "clobTokenIds": _json.dumps(["555", TOK]), + "outcomes": _json.dumps(["Yes", "No"])} +queries = [] + + +def gamma_closed_only(qs): + queries.append(qs) + if "closed=true" in qs: + return [MKT] + raise RuntimeError("transient") # default view: error, then empty + + +copybot._gamma_markets = gamma_closed_only +copybot._TOK_MKT.clear() +got = REAL_TOKEN_MARKET(TOK) +if got != (TITLE, "No", COND): + fails.append(f"closed-view fallback wrong: {got}") +if len(queries) != 2 or "closed=true" not in queries[1] or "closed=true" in queries[0]: + fails.append(f"variant order wrong: {queries}") +if copybot._TOK_MKT.get(TOK) != got: + fails.append("successful lookup not cached") +queries.clear() +if REAL_TOKEN_MARKET(TOK) != got or queries: + fails.append("cache miss on second call") +copybot._gamma_markets = lambda qs: [] +copybot._TOK_MKT.clear() +if REAL_TOKEN_MARKET("404") is not None or copybot._TOK_MKT: + fails.append("both-empty lookup should return None and cache nothing") + print("FAILURES:", fails or "none") sys.exit(1 if fails else 0)