From 1bfc91dd6a1d7c1af72c19f0b09eb5f02aa98138 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Tue, 21 Jul 2026 15:29:12 -0400 Subject: [PATCH] surgebot: read-only /feed endpoint for the /surge dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CORS-open GET /feed serving the paper book (equity, counters, open, settled, skips, informed-set meta) — paper data only, nothing mutable. Co-Authored-By: Claude Fable 5 --- fly.surgebot.toml | 8 ++++++++ research/surgebot.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/fly.surgebot.toml b/fly.surgebot.toml index b4ec8ef1..ca9219ab 100644 --- a/fly.surgebot.toml +++ b/fly.surgebot.toml @@ -12,6 +12,14 @@ primary_region = "arn" source = "surge" destination = "/data" +# read-only paper feed for jaxperro.com/surge (GET /feed, CORS-open) +[http_service] + internal_port = 8080 + force_https = true + auto_stop_machines = false + auto_start_machines = true + min_machines_running = 1 + [[restart]] policy = "always" diff --git a/research/surgebot.py b/research/surgebot.py index c963b390..9f94c591 100644 --- a/research/surgebot.py +++ b/research/surgebot.py @@ -288,9 +288,49 @@ def run_conn(tag, bot): backoff = 2 +def serve_feed(bot, port=8080): + """Read-only public feed for the /surge dashboard (paper data only — + nothing here can place an order or mutate state). CORS-open.""" + from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + + class H(BaseHTTPRequestHandler): + def log_message(self, *a): + pass + + def do_GET(self): + if self.path.split("?")[0] != "/feed": + self.send_response(404) + self.end_headers() + return + with bot.lock: + st = bot.state + body = json.dumps({ + "mode": "paper", "updated": int(time.time()), + "bank": BANK, "equity": round(bot.equity(), 2), + "cash": round(st["cash"], 2), + "day": st["day"], "day_stake": st["day_stake"], + "stake_pct": STAKE_PCT, "event_cap": EVENT_CAP, + "flow_usd": FLOW_USD, "window_s": WINDOW_S, + "band": BAND, "counters": st["counters"], + "informed": bot.set_meta, + "open": [{**p, "asset": a} for a, p in st["open"].items()], + "settled": st["settled"][-200:], + "skips": st["skips"][-100:]}).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + ThreadingHTTPServer(("0.0.0.0", port), H).serve_forever() + + def main(): bot = Surge() bot.load_set() + threading.Thread(target=serve_feed, args=(bot,), daemon=True).start() for tag in ("a", "b"): threading.Thread(target=run_conn, args=(tag, bot), daemon=True).start() last_set = time.time()