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:
131
projects/crypto-signals/scripts/price_fetcher.py
Normal file
131
projects/crypto-signals/scripts/price_fetcher.py
Normal 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()
|
||||
Reference in New Issue
Block a user