- 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
132 lines
4.2 KiB
Python
132 lines
4.2 KiB
Python
#!/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()
|