#!/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
active market
ticker—
question—
closes in—
YES —¢NO —¢
account
balance—
open positions—
bets placed (session)—
AI decision stream — ollama local
—
MODE—
BTC—
24H—
FEAR/GREED—
OPEN BETS—
RESOLVED P&L—
WIN RATE—
AI CONF GATE—
NEXT MARKET—
"""
FLEET_HTML = r"""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"}
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
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]
import sys; sys.path.insert(0, str(HOME))
from bot import rsi as _rsi, momentum as _mom
c_rsi = _rsi(conn); c_mom, c_score = _mom(conn)
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)