Files
Casey Judice 05c0441053 Initial commit: prediction-market arb scanner + GHA alerts
- 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>
2026-05-20 19:48:50 -04:00

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