mirror of
https://github.com/cjudice-commits/prediction-market-arb.git
synced 2026-07-27 13:37:46 +00:00
Refresh stale Polymarket slugs (BTC dip markets rediscovered)
Polymarket appends random hash suffixes when re-publishing markets (e.g. will-bitcoin-dip-to-70k-in-may-2026 → ...-2026-438-356-919). Our static pairs.json had the unsuffixed slugs from the original spreadsheet snapshot, so every BTC 'below' pair silently fell through to NO POLY despite both venues having liquid markets. User flagged this looking at BTC <70k May. - scripts/refresh_pairs.py: walks each asset's 'what-price-will-X- hit-in-may' multi-outcome event on Gamma, rebuilds the (asset, direction, strike) → slug map, rewrites data/pairs.json. Probes HYPE strikes individually since there's no container event for that asset. Has --dry-run. - data/pairs.json: updated to current live slugs. Two BTC arbs that were previously hidden now surface (BTC <65k and <70k both ARB). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+7
-7
@@ -196,7 +196,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-7250000",
|
||||
"kalshi_strike": 72500.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-70k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-70k-in-may-2026-438-356-919",
|
||||
"poly_strike": 70000.0
|
||||
},
|
||||
{
|
||||
@@ -204,7 +204,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-7000000",
|
||||
"kalshi_strike": 70000.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-70k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-70k-in-may-2026-438-356-919",
|
||||
"poly_strike": 70000.0
|
||||
},
|
||||
{
|
||||
@@ -212,7 +212,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-6750000",
|
||||
"kalshi_strike": 67500.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-65k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-65k-in-may-2026-183-857-425",
|
||||
"poly_strike": 65000.0
|
||||
},
|
||||
{
|
||||
@@ -220,7 +220,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-6500000",
|
||||
"kalshi_strike": 65000.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-65k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-65k-in-may-2026-183-857-425",
|
||||
"poly_strike": 65000.0
|
||||
},
|
||||
{
|
||||
@@ -228,7 +228,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-6250000",
|
||||
"kalshi_strike": 62500.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-60k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-60k-in-may-2026-973-269",
|
||||
"poly_strike": 60000.0
|
||||
},
|
||||
{
|
||||
@@ -236,7 +236,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-6000000",
|
||||
"kalshi_strike": 60000.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-60k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-60k-in-may-2026-973-269",
|
||||
"poly_strike": 60000.0
|
||||
},
|
||||
{
|
||||
@@ -244,7 +244,7 @@
|
||||
"asset": "BTC",
|
||||
"kalshi_ticker": "KXBTCMINMON-BTC-26MAY31-5750000",
|
||||
"kalshi_strike": 57500.0,
|
||||
"poly_slug": "will-bitcoin-dip-to-55k-in-may-2026",
|
||||
"poly_slug": "will-bitcoin-dip-to-55k-in-may-2026-941",
|
||||
"poly_strike": 55000.0
|
||||
},
|
||||
{
|
||||
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Refresh data/pairs.json Polymarket slugs against the live Gamma API.
|
||||
|
||||
Polymarket periodically appends random hash suffixes to market slugs (e.g.
|
||||
will-bitcoin-dip-to-70k-in-may-2026 -> ...-2026-438-356-919). When that
|
||||
happens the scanner silently falls through to NO POLY for the affected
|
||||
pairs even though both venues have liquid markets.
|
||||
|
||||
Usage:
|
||||
python3 scripts/refresh_pairs.py [--dry-run]
|
||||
|
||||
Walks each asset's monthly multi-outcome event on Gamma, builds a current
|
||||
(asset, direction, strike) -> slug map, and rewrites pairs.json. Pairs whose
|
||||
strike has no current Polymarket counterpart are left as-is (the scanner
|
||||
will surface them as NO POLY so you can decide whether to remove them).
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
PAIRS = ROOT / "data" / "pairs.json"
|
||||
|
||||
ASSET_EVENTS = {
|
||||
"BTC": "what-price-will-bitcoin-hit-in-may-2026",
|
||||
"ETH": "what-price-will-ethereum-hit-in-may-2026",
|
||||
"SOL": "what-price-will-solana-hit-in-may-2026",
|
||||
"XRP": "what-price-will-xrp-hit-in-may-2026",
|
||||
"DOGE": "what-price-will-dogecoin-hit-in-may-2026",
|
||||
"BNB": "what-price-will-bnb-hit-in-may",
|
||||
}
|
||||
HYPE_STRIKES = [(d, s) for d in ("above", "below")
|
||||
for s in (52, 48, 44, 38, 32, 28, 24)]
|
||||
|
||||
|
||||
def gj(u):
|
||||
rq = urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"})
|
||||
try:
|
||||
with urllib.request.urlopen(rq, timeout=20) as r:
|
||||
return json.loads(r.read().decode())
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def normalize(asset, raw):
|
||||
raw = float(raw)
|
||||
return raw * 1000 if asset == "BTC" else raw
|
||||
|
||||
|
||||
def build_remap():
|
||||
"""Returns {(asset, direction, strike): slug}."""
|
||||
out = {}
|
||||
for asset, ev_slug in ASSET_EVENTS.items():
|
||||
e = gj("https://gamma-api.polymarket.com/events?slug=" + ev_slug)
|
||||
if not e:
|
||||
print(" WARN: event not found for %s (%s)" % (asset, ev_slug),
|
||||
file=sys.stderr)
|
||||
continue
|
||||
for mk in e[0].get("markets", []):
|
||||
if mk.get("closed"):
|
||||
continue
|
||||
sl = mk["slug"]
|
||||
m = re.search(r"-(dip-to|reach)-(\d+(?:pt\d+)?)k?-in-may", sl)
|
||||
if not m:
|
||||
continue
|
||||
direction = "below" if m.group(1) == "dip-to" else "above"
|
||||
strike = normalize(asset, float(m.group(2).replace("pt", ".")))
|
||||
out[(asset, direction, strike)] = sl
|
||||
|
||||
# HYPE doesn't have a single-event container; probe known strikes directly.
|
||||
for direc, strike in HYPE_STRIKES:
|
||||
kw = "reach" if direc == "above" else "dip-to"
|
||||
for variant in ("will-hyperliquid-%s-%s-in-may" % (kw, strike),
|
||||
"will-hyperliquid-%s-%s-in-may-2026" % (kw, strike)):
|
||||
a = gj("https://gamma-api.polymarket.com/markets?slug=" + variant)
|
||||
if a and not a[0].get("closed"):
|
||||
out[("HYPE", direc, float(strike))] = variant
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
dry = "--dry-run" in sys.argv
|
||||
remap = build_remap()
|
||||
print("Built remap with %d live (asset, direction, strike) entries." % len(remap))
|
||||
|
||||
pairs = json.loads(PAIRS.read_text())
|
||||
changed = []
|
||||
orphan = []
|
||||
for p in pairs:
|
||||
if not p.get("kalshi_ticker") or not p.get("poly_strike"):
|
||||
continue
|
||||
d = "below" if "MIN" in p["kalshi_ticker"] else "above"
|
||||
key = (p["asset"], d, float(p["poly_strike"]))
|
||||
new = remap.get(key)
|
||||
if new is None:
|
||||
if p.get("poly_slug"):
|
||||
orphan.append((p["asset"], d, p["poly_strike"]))
|
||||
continue
|
||||
if new != p.get("poly_slug"):
|
||||
changed.append((p["asset"], d, p["poly_strike"],
|
||||
p["poly_slug"], new))
|
||||
p["poly_slug"] = new
|
||||
|
||||
print(" %d pairs updated, %d pairs have no live Poly counterpart"
|
||||
% (len(changed), len(orphan)))
|
||||
for asset, d, s, old, new in changed[:8]:
|
||||
print(" %s %s $%s %s → %s"
|
||||
% (asset, d, s, (old or "")[:40], new[:55]))
|
||||
if len(changed) > 8:
|
||||
print(" ...(%d more)" % (len(changed) - 8))
|
||||
|
||||
if dry:
|
||||
print(" --dry-run; not writing.")
|
||||
return
|
||||
PAIRS.write_text(json.dumps(pairs, indent=2) + "\n")
|
||||
print(" wrote %s (%d pairs total)" % (PAIRS, len(pairs)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user