copybot: _token_market falls back to gamma's closed=true view (#18 follow-up)

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 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-21 11:49:45 -04:00
parent 935200c901
commit e909c3a050
2 changed files with 66 additions and 16 deletions
+29 -16
View File
@@ -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):
+37
View File
@@ -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)