Add crypto signals pipeline + Polymarket arb scanner

- Signal parser for Telegram JSON exports
- Price fetcher using Binance US API
- Backtester with fee-aware simulation
- Polymarket 15-min arb scanner with orderbook checking
- Systemd timer every 2 min for arb alerts
- Paper trade tracking
- Investigation: polymarket-15min-arb.md
This commit is contained in:
2026-02-09 14:31:51 -06:00
parent b24d0e87de
commit be0315894e
8 changed files with 925 additions and 2 deletions

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""
Crypto Price Fetcher
Pulls historical OHLCV data from Binance public API (no key needed).
"""
import json
import time
import urllib.request
from datetime import datetime, timezone
# Binance intl is geo-blocked from US; use Binance US
BINANCE_KLINES = "https://api.binance.us/api/v3/klines"
BINANCE_TICKER = "https://api.binance.us/api/v3/ticker/price"
def get_price_at_time(symbol, timestamp_ms, interval='1m'):
"""Get the candle at a specific timestamp."""
url = f"{BINANCE_KLINES}?symbol={symbol}&interval={interval}&startTime={timestamp_ms}&limit=1"
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
try:
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
if data:
return {
'open_time': data[0][0],
'open': float(data[0][1]),
'high': float(data[0][2]),
'low': float(data[0][3]),
'close': float(data[0][4]),
'volume': float(data[0][5]),
}
except Exception as e:
print(f"Error fetching {symbol}: {e}")
return None
def get_klines(symbol, interval='1h', start_time_ms=None, end_time_ms=None, limit=1000):
"""Get historical klines/candlestick data."""
params = f"symbol={symbol}&interval={interval}&limit={limit}"
if start_time_ms:
params += f"&startTime={start_time_ms}"
if end_time_ms:
params += f"&endTime={end_time_ms}"
url = f"{BINANCE_KLINES}?{params}"
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
try:
resp = urllib.request.urlopen(req, timeout=15)
raw = json.loads(resp.read())
return [{
'open_time': k[0],
'open': float(k[1]),
'high': float(k[2]),
'low': float(k[3]),
'close': float(k[4]),
'volume': float(k[5]),
'close_time': k[6],
} for k in raw]
except Exception as e:
print(f"Error fetching klines for {symbol}: {e}")
return []
def get_all_klines(symbol, interval, start_time_ms, end_time_ms):
"""Paginate through all klines between two timestamps."""
all_klines = []
current_start = start_time_ms
while current_start < end_time_ms:
batch = get_klines(symbol, interval, current_start, end_time_ms)
if not batch:
break
all_klines.extend(batch)
current_start = batch[-1]['close_time'] + 1
time.sleep(0.1) # Rate limiting
return all_klines
def get_current_price(symbol):
"""Get current price."""
url = f"{BINANCE_TICKER}?symbol={symbol}"
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
try:
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
return float(data['price'])
except Exception as e:
print(f"Error fetching current price for {symbol}: {e}")
return None
def normalize_symbol(ticker):
"""Convert signal ticker to Binance symbol format."""
# Remove USDT suffix if present, then add it back
ticker = ticker.upper().replace('USDT', '').replace('/', '')
return f"{ticker}USDT"
def datetime_to_ms(dt_str):
"""Convert datetime string to milliseconds timestamp."""
# Handle various formats
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S', '%Y-%m-%d']:
try:
dt = datetime.strptime(dt_str, fmt).replace(tzinfo=timezone.utc)
return int(dt.timestamp() * 1000)
except ValueError:
continue
return None
if __name__ == '__main__':
# Test with current signals
for ticker in ['ASTERUSDT', 'HYPEUSDT']:
symbol = normalize_symbol(ticker)
price = get_current_price(symbol)
print(f"{symbol}: ${price}")
# Get last 24h of 1h candles
now_ms = int(time.time() * 1000)
day_ago = now_ms - (24 * 60 * 60 * 1000)
klines = get_klines(symbol, '1h', day_ago, now_ms)
if klines:
highs = [k['high'] for k in klines]
lows = [k['low'] for k in klines]
print(f" 24h range: ${min(lows):.4f} - ${max(highs):.4f}")
print(f" Candles: {len(klines)}")
print()