mirror of
https://github.com/cjudice-commits/prediction-market-arb.git
synced 2026-07-27 13:37:46 +00:00
05c0441053
- Monthly + hourly Kalshi/Polymarket arb scanner (stdlib-only Python). - Live positions tab w/ realized P&L history. - GitHub Actions cron workflow texts SMS via Apps Script webhook on newly-detected arbs. State persisted in alerts_state.json. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""Reference-price client for the hourly scanner.
|
|
|
|
Polymarket "Up or Down" resolves on the Binance {SYM}/USDT 1-hour candle
|
|
(Up if close >= open). So the *implied strike* for the hour == the Binance
|
|
candle OPEN. Kalshi hourly settles on CF Benchmarks (BRTI etc.), whose index
|
|
is built from Coinbase/Kraken/Bitstamp/LMAX. The Binance-vs-Coinbase spot gap
|
|
is therefore a live proxy for the unhedgeable settlement-feed basis risk.
|
|
|
|
data-api.binance.vision -> Binance public mirror (api.binance.com is 451
|
|
geo-blocked from here). Gives 1h-candle open +
|
|
spot for the implied strike & Binance side.
|
|
api.coinbase.com -> CF-Benchmarks-side spot proxy.
|
|
"""
|
|
from .net import get_json, parallel, FetchError
|
|
|
|
BINANCE = "https://data-api.binance.vision/api/v3"
|
|
COINBASE = "https://api.coinbase.com/v2/prices/%s-USD/spot"
|
|
|
|
# asset -> (binance symbol, coinbase code or None if not listed there)
|
|
ASSETS = {
|
|
"BTC": ("BTCUSDT", "BTC"),
|
|
"ETH": ("ETHUSDT", "ETH"),
|
|
"SOL": ("SOLUSDT", "SOL"),
|
|
"XRP": ("XRPUSDT", "XRP"),
|
|
"DOGE": ("DOGEUSDT", "DOGE"),
|
|
"BNB": ("BNBUSDT", None), # not on Coinbase -> no CF-side proxy
|
|
}
|
|
|
|
|
|
def _one(asset):
|
|
bsym, cb = ASSETS[asset]
|
|
k = get_json("%s/klines?symbol=%s&interval=1h&limit=1" % (BINANCE, bsym))
|
|
row = k[0]
|
|
open_px = float(row[1])
|
|
open_ms = int(row[0])
|
|
binance_spot = float(get_json(
|
|
"%s/ticker/price?symbol=%s" % (BINANCE, bsym))["price"])
|
|
cf_spot = None
|
|
if cb:
|
|
try:
|
|
cf_spot = float(get_json(COINBASE % cb)["data"]["amount"])
|
|
except (FetchError, KeyError, ValueError, TypeError):
|
|
cf_spot = None
|
|
div = None
|
|
if cf_spot:
|
|
div = (binance_spot - cf_spot) / cf_spot
|
|
return {
|
|
"asset": asset,
|
|
"hour_open": open_px, # implied Polymarket strike for the hour
|
|
"hour_open_ms": open_ms, # UTC ms of the candle open (window start)
|
|
"binance_spot": binance_spot,
|
|
"cf_spot": cf_spot,
|
|
"divergence": div, # (binance - coinbase) / coinbase
|
|
}
|
|
|
|
|
|
def fetch_refs(assets):
|
|
"""assets: iterable of symbols. Returns {asset: ref|None}."""
|
|
want = [a for a in assets if a in ASSETS]
|
|
res = parallel(_one, want, workers=8)
|
|
return {a: (None if isinstance(v, FetchError) else v)
|
|
for a, v in res.items()}
|