#!/usr/bin/env python3 """K4LSH1_OPS — dashboard. Hacker console on :5053. Controls write config.json (bot hot-reloads).""" import json, sqlite3, time, threading from pathlib import Path from flask import Flask, jsonify, request HOME = Path(__file__).parent CFG_PATH = HOME / "config.json" DB_PATH = HOME / "state.db" app = Flask(__name__) HTML = r"""K4LSH1_OPS

K4LSH1_OPS

BTC/USD — kraken

$—

active market

ticker
question
closes in
YES —¢NO —¢

account

balance
open positions
bets placed (session)

AI decision stream — ollama local

system events

controls

STRATEGY MODE
MAX STAKE / BET: 50¢

order blotter

MODE
BTC
24H
FEAR/GREED
OPEN BETS
RESOLVED P&L
WIN RATE
AI CONF GATE
NEXT MARKET
""" FLEET_HTML = r"""K4LSH1 FLEET

K4LSH1_FLEET

""" def cfg(): return json.loads(CFG_PATH.read_text()) @app.route("/") def index(): return HTML @app.route("/api/state") def state(): c = cfg() conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row now = time.time() ticks = [r[0] for r in conn.execute("SELECT price FROM ticks WHERE ts > ? ORDER BY ts", (now-1800,)).fetchall()] last_price = ticks[-1] if ticks else None age = None row = conn.execute("SELECT MAX(ts) FROM ticks").fetchone() if row and row[0]: age = int(now - row[0]) dec = [{"t": time.strftime("%H:%M:%S", time.localtime(r["ts"])), "ticker": r["ticker"], "momentum": r["momentum"], "llm_vote": r["llm_vote"], "llm_conf": r["llm_conf"], "llm_why": r["llm_why"], "final": r["final"], "reason": r["reason"]} for r in conn.execute("SELECT * FROM decisions ORDER BY id DESC LIMIT 30")] orders = [{"t": time.strftime("%H:%M:%S", time.localtime(r["ts"])), "ticker": r["ticker"], "side": r["side"], "count": r["count"], "price_cents": r["price_cents"], "dry": r["dry"]} for r in conn.execute("SELECT * FROM orders ORDER BY id DESC LIMIT 20")] events = [{"t": time.strftime("%H:%M:%S", time.localtime(r[0])), "level": r[1], "msg": r[2]} for r in conn.execute("SELECT * FROM events ORDER BY ts DESC LIMIT 12")] bets = conn.execute("SELECT COUNT(*) FROM orders").fetchone()[0] perf_rows = conn.execute("SELECT status, COALESCE(pnl_cents,0) FROM orders WHERE status IN ('won','lost')").fetchall() won = sum(1 for s,p in perf_rows if s == 'won') pnl = sum(p for s,p in perf_rows) open_bets = conn.execute("SELECT COUNT(*) FROM orders WHERE status IN ('placed','posted')").fetchone()[0] kv = {r[0]: r[1] for r in conn.execute("SELECT k, v FROM kv")} conn.close() # live market + balance (proxy through bot's client without importing heavy code) market, balance, positions = {}, None, 0 try: import sys sys.path.insert(0, str(HOME)) from bot import Kalshi, btc_price, DEFAULT_CFG kx = Kalshi(c["api_key_id"], c["key_path"]) mkts = kx.markets(c.get("series", "KXBTC15M"), "open", 2) if mkts: m = sorted(mkts, key=lambda x: x["close_time"])[-1] from datetime import datetime close_ts = datetime.fromisoformat(m["close_time"].replace("Z","+00:00")).timestamp() book = kx.orderbook(m["ticker"]) market = {"ticker": m["ticker"], "title": m.get("title",""), "mins_left": (close_ts-time.time())/60, "yes_ask": (book.get("yes") or [[None]])[0][0], "no_ask": (book.get("no") or [[None]])[0][0]} b = kx.balance() balance = b.get("balance", 0) # available cash (cents) positions = len(kx.positions()) except Exception as e: market = {"error": str(e)[:80]} if last_price is None: try: from bot import btc_price last_price = btc_price() except Exception: pass try: from bot import intel as bot_intel ix = bot_intel() except Exception: ix = {} # momentum label mom = "FLAT" if len(ticks) > 10: r = (ticks[-1]-ticks[0])/ticks[0]*100 mom = f"{'UP' if r>0 else 'DOWN'} {abs(r):.3f}%/30m" return jsonify({"cfg": c, "btc": last_price, "price_age": age, "spark": ticks[-120:], "momentum": mom, "market": market, "balance": balance, "positions": positions, "bets_session": bets, "decisions": dec, "orders": orders, "events": events, "intel": ix, "perf": {"open": open_bets, "resolved": len(perf_rows), "won": won, "pnl": pnl, "min_conf": kv.get("min_conf", c.get("min_conf", 0.55))}}) @app.route("/api/control", methods=["POST"]) def control(): c = cfg() d = request.get_json(force=True) if d.get("mode") in ("AUTO","DOWN_SPAM","UP_SPAM","OFF"): c["mode"] = d["mode"] if isinstance(d.get("stake_cents"), int): c["stake_cents"] = max(1, min(99, d["stake_cents"])) c["kill"] = False CFG_PATH.write_text(json.dumps(c, indent=1)) return jsonify({"ok": True}) @app.route("/api/toggle-dry", methods=["POST"]) def toggle_dry(): c = cfg(); c["dry_run"] = not c["dry_run"]; c["kill"] = False CFG_PATH.write_text(json.dumps(c, indent=1)) return jsonify({"ok": True, "dry_run": c["dry_run"]}) @app.route("/api/fleet") def fleet_api(): """All active coins in one call — reads each coin's own DB.""" COINS = {"🐶DOGE": "#c2a633", "🔮SOL": "#9945FF", "💎ETH": "#627EEA", "🌊XRP": "#00AAE4", "₿BTC": "#f7931a"} result = {} for label, color in COINS.items(): coin = label[1:] # strip leading emoji fpath = HOME / f"{coin.lower()}.json" if not fpath.exists(): continue c = json.loads(fpath.read_text()) if c.get("mode","OFF") == "OFF": continue db_path = HOME / c.get("db_path", f"{coin.lower()}.db") if not db_path.exists(): continue conn = sqlite3.connect(str(db_path)) conn.row_factory = sqlite3.Row now = time.time() ticks = [r[0] for r in conn.execute("SELECT price FROM ticks WHERE ts > ? ORDER BY ts", (now-1800,)).fetchall()] last_price = ticks[-1] if ticks else None # enriched per-coin data — compute RSI + momentum directly (no cross-import) decs = [dict(r) for r in conn.execute("SELECT * FROM decisions ORDER BY id DESC LIMIT 3")] ords = [dict(r) for r in conn.execute("SELECT * FROM orders ORDER BY id DESC LIMIT 5")] live_bets = conn.execute("SELECT COUNT(*) FROM orders WHERE status IN ('placed','posted') AND dry=0").fetchone()[0] total_bets = conn.execute("SELECT COUNT(*) FROM orders WHERE dry=0").fetchone()[0] pnl = conn.execute("SELECT COALESCE(SUM(pnl_cents),0) FROM orders WHERE dry=0").fetchone()[0] won = conn.execute("SELECT COUNT(*) FROM orders WHERE status='won' AND dry=0").fetchone()[0] # RSI rows14 = conn.execute("SELECT price FROM ticks WHERE ts > ? ORDER BY ts DESC LIMIT 15", (now-900,)).fetchall() c_rsi = None if len(rows14) >= 15: px = [r[0] for r in rows14] gains = [max(0, b-a) for a, b in zip(px[:-1], px[1:])] losses = [max(0, a-b) for a, b in zip(px[:-1], px[1:])] ag = sum(gains)/14 if gains else 0; al = sum(losses)/14 if losses else 1e-9 c_rsi = 100 - 100/(1 + ag/al) if al else (100 if ag > 0 else 50) # Momentum rows_t = conn.execute("SELECT ts, price FROM ticks WHERE ts > ? ORDER BY ts", (now-1800,)).fetchall() c_mom, c_score = "FLAT", 0.0 if len(rows_t) >= 4: now_p = rows_t[-1][1] ref5 = min(rows_t, key=lambda r: abs(r[0]-(now-300)))[1] ref15 = min(rows_t, key=lambda r: abs(r[0]-(now-900)))[1] r5 = (now_p-ref5)/ref5*100 if ref5 else 0; r15 = (now_p-ref15)/ref15*100 if ref15 else 0 sc = r5*0.6 + r15*0.4 if sc > 0.008: c_mom, c_score = "UP", abs(sc) elif sc < -0.008: c_mom, c_score = "DOWN", abs(sc) else: c_mom, c_score = "FLAT", abs(sc) chg24 = c.get("chg24_cache", 0) result[coin] = {"label": label, "color": color, "series": c["series"], "price": last_price, "mode": c["mode"], "live_bets": live_bets, "total_bets": total_bets, "pnl": pnl, "won": won, "stake": c.get("stake_cents", 10), "swing": c.get("swing_threshold_pct", 0.005), "rsi": c_rsi, "mom": f"{c_mom} {c_score:.3f}%" if c_mom != "FLAT" else c_mom, "chg24": chg24, "decisions": decs, "orders": ords} conn.close() return jsonify(result) @app.route("/fleet") def fleet_page(): return FLEET_HTML def kill(): c = cfg(); c["kill"] = True CFG_PATH.write_text(json.dumps(c, indent=1)) return jsonify({"ok": True}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5053, debug=False)