#!/usr/bin/env python3 """BTC/USD live trade ticks from Binance — the fastest, most chaotic source. Every sub-second trade fires the market's pulse; price-to-price velocity is raw volatility you can hear, driving rhythm and jitter in the modulation matrix. """ import argparse import asyncio import json import urllib.request from pythonosc.udp_client import SimpleUDPClient NAME = "world_crypto" WS_URL = "wss://stream.binance.com:9443/ws/btcusdt@trade" REST_URL = "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT" POLL_S = 2.0 # REST fallback poll interval USER_AGENT = "godstrument/1.0 (+world_crypto)" def emit(client, last_price, price): """Send price + tick-to-tick velocity. Returns the new last_price.""" client.send_message("/gs/crypto/price", float(price)) vel = 0.0 if last_price is None else abs(price - last_price) client.send_message("/gs/crypto/vel", float(vel)) return price async def run_websocket(client): """Stream trades over the websocket. Returns True if it ever connected (so the caller keeps favoring the socket), False if it never connected (so the caller can fall back to REST).""" import websockets # part of the required stack; import here to keep top clean ever_connected = False last_price = None async with websockets.connect(WS_URL, ping_interval=20, ping_timeout=20) as ws: ever_connected = True async for raw in ws: try: msg = json.loads(raw) price = float(msg["p"]) except (ValueError, KeyError, TypeError): continue # malformed / non-trade frame; skip last_price = emit(client, last_price, price) return ever_connected async def poll_rest(client): """Fallback: poll the REST ticker forever at POLL_S. Never returns.""" last_price = None loop = asyncio.get_running_loop() while True: try: def fetch(): req = urllib.request.Request(REST_URL, headers={"User-Agent": USER_AGENT}) with urllib.request.urlopen(req, timeout=10) as resp: return json.loads(resp.read().decode()) data = await loop.run_in_executor(None, fetch) price = float(data["price"]) last_price = emit(client, last_price, price) except Exception as e: # transient network / parse error — keep going print(f"[{NAME}] REST poll error: {e}; retrying") await asyncio.sleep(POLL_S) async def main(): parser = argparse.ArgumentParser(description="Binance BTCUSDT trade ticks -> OSC") parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=9000) args = parser.parse_args() client = SimpleUDPClient(args.host, args.port) print(f"[{NAME}] emitting /gs/crypto/price,/gs/crypto/vel every <1s (ws) / {POLL_S:g}s (rest)") backoff = 1.0 while True: try: await run_websocket(client) # Clean disconnect (stream ended): reconnect quickly. print(f"[{NAME}] websocket closed; reconnecting") backoff = 1.0 await asyncio.sleep(1.0) # avoid a hot loop on an instant re-close except Exception as e: # Bridge the gap on REST for the backoff window, then always retry # the socket — never latch onto REST for the rest of the session. print(f"[{NAME}] websocket error: {e}; REST bridge {backoff:g}s, then retry") try: await asyncio.wait_for(poll_rest(client), timeout=backoff) except asyncio.TimeoutError: pass except Exception as e2: print(f"[{NAME}] REST bridge error: {e2}") await asyncio.sleep(min(backoff, POLL_S)) backoff = min(backoff * 2, 30.0) if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: pass