Godstrument/workers/world_crypto.py
type-two e69b7e2ad6 Accounts, socio-economic feeds, and hub fixes (track deployed work)
Captures backend work already running on godstrument.pro but never committed:
- auth.py: invite-only accounts + per-user presets (SQLite, scrypt, signed
  stateless sessions), mounted by hub.py at /api/*
- hub.py: /api static+API handler, readonly public mode, full route spec in hello
- socio-economic / market / fire / debt / crypto workers + normalize/transform
- .gitignore: never commit godstrument_users.db* or auth_secret
- test_fixes.py: framework-free self-check for the review fixes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:18:35 +10:00

103 lines
3.9 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
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