Godstrument/workers/world_crypto.py
monsterrobotparty 8deb65b4fe Godstrument: a multi-modal sensor-fusion instrument
Everything — your hand, the room, a satellite, the markets, the sky — becomes an
OSC control signal fused through a modulation matrix. Includes:
- hub + normalize (One-Euro) + matrix (curves/gates/quantize) + transform (tweaks/groups)
- 20+ workers: sim sensors, 8 keyless world feeds, ephemeris/almanac/clock (computed),
  time-warp replay (quakes/weather/db), and the dealgod market warehouse feed
- planetary orbital LFOs, SQLite recorder, city targeting
- live browser console: mute, group macros, drag-to-patch, inspector, Web MIDI

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 02:13:05 +10:00

110 lines
4.1 KiB
Python

#!/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
rest_only = False
while True:
if rest_only:
# We could not open the socket at all; stay on REST but keep
# occasionally retrying the socket by resetting the flag on error.
try:
await poll_rest(client)
except Exception as e:
print(f"[{NAME}] REST loop error: {e}; retrying")
await asyncio.sleep(POLL_S)
continue
try:
await run_websocket(client)
# Clean disconnect (stream ended): reconnect quickly.
print(f"[{NAME}] websocket closed; reconnecting")
backoff = 1.0
except Exception as e:
print(f"[{NAME}] websocket error: {e}; backoff {backoff:g}s")
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 30.0)
# If we have never managed to connect, drop to REST fallback,
# but keep trying the socket periodically via a bounded retry.
if backoff >= 8.0 and not rest_only:
print(f"[{NAME}] websocket unreachable; falling back to REST polling")
rest_only = True
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass