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>
135 lines
4.9 KiB
Python
135 lines
4.9 KiB
Python
"""
|
|
world_dealgod.py — your own market as an instrument. The dealgod feed.
|
|
|
|
Reads price history straight out of the basegod warehouse (dealgod.price_events,
|
|
~4M rows) on the M1 Ultra over the tailnet, bins it into an activity curve and an
|
|
average-price curve, and replays them compressed on a loop — your deal-flow as a
|
|
control source, warpable and jog-scrubbable like the other time-warp workers.
|
|
|
|
READ-ONLY: it issues a single SELECT (server-side GROUP BY does the work) over
|
|
SSH as johnking. No writes ever touch the master. Point --ssh at a host with
|
|
dashboard_ro if you want strict least-privilege networked access instead.
|
|
|
|
Emits:
|
|
/gs/market/velocity normalized market activity (price events per time bin)
|
|
/gs/market/turnover normalized average price of what's moving
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
from pythonosc.udp_client import SimpleUDPClient
|
|
from pythonosc.dispatcher import Dispatcher
|
|
from pythonosc.osc_server import ThreadingOSCUDPServer
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from timewarp import ReplayBuffer, Jog
|
|
|
|
PSQL = "/opt/homebrew/opt/postgresql@16/bin/psql"
|
|
|
|
# quote-free SQL (make_interval / to_timestamp avoid single quotes over ssh)
|
|
QUERY = (
|
|
"SELECT extract(epoch from bucket), cnt, avgp FROM ("
|
|
"SELECT date_bin(make_interval(mins=>{binmin}), scraped_at, to_timestamp(0)) AS bucket, "
|
|
"count(*) AS cnt, avg(price) FILTER (WHERE price BETWEEN 0 AND 100000) AS avgp "
|
|
"FROM price_events WHERE scraped_at > now() - make_interval(days => {days}) "
|
|
"GROUP BY bucket ORDER BY bucket) s"
|
|
)
|
|
|
|
|
|
def fetch(ssh_host, days, binmin):
|
|
sql = QUERY.format(days=days, binmin=binmin)
|
|
remote = f'{PSQL} -d dealgod -tA -F"|" -c "{sql}"'
|
|
r = subprocess.run(
|
|
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", ssh_host, remote],
|
|
capture_output=True, text=True, timeout=45)
|
|
rows = []
|
|
for line in r.stdout.splitlines():
|
|
p = line.split("|")
|
|
if len(p) >= 3 and p[0]:
|
|
try:
|
|
rows.append((float(p[0]), float(p[1]), float(p[2] or 0)))
|
|
except ValueError:
|
|
pass
|
|
return rows, r.stderr.strip()
|
|
|
|
|
|
def normalize(vals):
|
|
lo, hi = min(vals), max(vals)
|
|
return [((v - lo) / (hi - lo) if hi > lo else 0.5) for v in vals]
|
|
|
|
|
|
def build(rows, into):
|
|
times = [r[0] for r in rows]
|
|
vel = ReplayBuffer("continuous", into)
|
|
vel.load_continuous(times, normalize([r[1] for r in rows]))
|
|
turn = ReplayBuffer("continuous", into)
|
|
turn.load_continuous(times, normalize([r[2] for r in rows]))
|
|
return vel, turn
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--host", default="127.0.0.1")
|
|
ap.add_argument("--port", type=int, default=9000)
|
|
ap.add_argument("--jog-port", type=int, default=9104)
|
|
ap.add_argument("--ssh", default="johnking@100.91.239.7")
|
|
ap.add_argument("--days", type=int, default=7)
|
|
ap.add_argument("--binmin", type=int, default=15, help="minutes per bin")
|
|
ap.add_argument("--into", type=float, default=300.0, help="loop length (s)")
|
|
ap.add_argument("--refresh", type=float, default=900.0, help="re-query every Ns")
|
|
args = ap.parse_args()
|
|
|
|
client = SimpleUDPClient(args.host, args.port)
|
|
jog = Jog()
|
|
state = {"vel": None, "turn": None}
|
|
|
|
def load():
|
|
try:
|
|
rows, err = fetch(args.ssh, args.days, args.binmin)
|
|
except Exception as e: # SSH timeout, missing ssh binary, tailnet stall
|
|
print(f"[dealgod] query failed ({e}); retrying")
|
|
return
|
|
if len(rows) >= 2:
|
|
vel, turn = build(rows, args.into)
|
|
state["vel"], state["turn"] = vel, turn
|
|
span = (rows[-1][0] - rows[0][0]) / 86400
|
|
print(f"[dealgod] {len(rows)} bins over {span:.1f}d "
|
|
f"-> loop {args.into:g}s (jog udp/{args.jog_port})")
|
|
else:
|
|
print(f"[dealgod] no data ({err or 'empty'}); retrying")
|
|
|
|
load()
|
|
while state["vel"] is None:
|
|
time.sleep(10)
|
|
load()
|
|
|
|
disp = Dispatcher()
|
|
disp.map("/jog/rate", jog.on_rate)
|
|
disp.map("/jog/nudge", jog.on_nudge)
|
|
srv = ThreadingOSCUDPServer(("127.0.0.1", args.jog_port), disp)
|
|
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
|
|
|
last_refresh = time.time()
|
|
period = 1.0 / 30.0
|
|
while True:
|
|
v, t = state["vel"], state["turn"]
|
|
if v and t:
|
|
v.tick(period, jog.rate_mult, jog.nudge)
|
|
t.tick(period, jog.rate_mult, jog.nudge)
|
|
client.send_message("/gs/market/velocity", float(v.current()))
|
|
client.send_message("/gs/market/turnover", float(t.current()))
|
|
if time.time() - last_refresh > args.refresh:
|
|
last_refresh = time.time()
|
|
threading.Thread(target=load, daemon=True).start()
|
|
time.sleep(period)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|