186 lines
6.3 KiB
Python
186 lines
6.3 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""DOLPHIN live status — zero-dependency rolling display.
|
||
|
|
|
||
|
|
Polls HZ every 5s, prints a compact status block. No curses, no textual.
|
||
|
|
Run: source /home/dolphin/siloqy_env/bin/activate && python dolphin_status.py
|
||
|
|
"""
|
||
|
|
import json, os, time, sys
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
import hazelcast
|
||
|
|
|
||
|
|
CLEAR = "\033[2J\033[H"
|
||
|
|
BOLD = "\033[1m"; DIM = "\033[2m"; RST = "\033[0m"
|
||
|
|
GREEN = "\033[32m"; YELLOW = "\033[33m"; RED = "\033[31m"; CYAN = "\033[36m"
|
||
|
|
|
||
|
|
PC = {"APEX": GREEN, "STALKER": YELLOW, "TURTLE": "\033[38;5;208m", "HIBERNATE": RED}
|
||
|
|
SC = {"GREEN": GREEN, "DEGRADED": YELLOW, "CRITICAL": "\033[38;5;208m", "DEAD": RED}
|
||
|
|
|
||
|
|
START_CAP = None
|
||
|
|
CAP_PEAK = None
|
||
|
|
|
||
|
|
|
||
|
|
def _age(ts):
|
||
|
|
if not ts: return "?"
|
||
|
|
s = time.time() - ts
|
||
|
|
if s < 0: return "0s"
|
||
|
|
if s < 60: return f"{s:.0f}s"
|
||
|
|
if s < 3600: return f"{s/60:.0f}m"
|
||
|
|
return f"{s/3600:.1f}h"
|
||
|
|
|
||
|
|
|
||
|
|
def _bar(v, w=20):
|
||
|
|
v = max(0.0, min(1.0, v))
|
||
|
|
f = round(v * w)
|
||
|
|
return "█" * f + "░" * (w - f)
|
||
|
|
|
||
|
|
|
||
|
|
def _get(hz, map_name, key):
|
||
|
|
try:
|
||
|
|
raw = hz.get_map(map_name).blocking().get(key)
|
||
|
|
return json.loads(raw) if raw else {}
|
||
|
|
except Exception:
|
||
|
|
return {}
|
||
|
|
|
||
|
|
|
||
|
|
def render(hz):
|
||
|
|
global START_CAP, CAP_PEAK
|
||
|
|
|
||
|
|
eng = _get(hz, "DOLPHIN_STATE_BLUE", "engine_snapshot")
|
||
|
|
cap = _get(hz, "DOLPHIN_STATE_BLUE", "capital_checkpoint")
|
||
|
|
safe = _get(hz, "DOLPHIN_SAFETY", "latest")
|
||
|
|
hb = _get(hz, "DOLPHIN_HEARTBEAT", "nautilus_flow_heartbeat")
|
||
|
|
mh = _get(hz, "DOLPHIN_META_HEALTH", "latest")
|
||
|
|
acb = _get(hz, "DOLPHIN_FEATURES", "acb_boost")
|
||
|
|
|
||
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||
|
|
capital = float(eng.get("capital", 0) or cap.get("capital", 0))
|
||
|
|
posture = safe.get("posture") or eng.get("posture") or "?"
|
||
|
|
rm = float(safe.get("Rm", 0))
|
||
|
|
bd = safe.get("breakdown", {})
|
||
|
|
hb_ts = hb.get("ts")
|
||
|
|
hb_age = _age(hb_ts)
|
||
|
|
phase = hb.get("phase", "?")
|
||
|
|
trader_up = hb_ts and (time.time() - hb_ts) < 30
|
||
|
|
trades = eng.get("trades_executed", "—")
|
||
|
|
scans = eng.get("scans_processed", "—")
|
||
|
|
lev = float(eng.get("current_leverage", 0))
|
||
|
|
notional = float(eng.get("open_notional", 0))
|
||
|
|
mhs_st = mh.get("status", "?")
|
||
|
|
rm_meta = float(mh.get("rm_meta", 0))
|
||
|
|
boost = float(acb.get("boost", acb.get("cut", 0)))
|
||
|
|
vel_div = float(eng.get("last_vel_div", 0))
|
||
|
|
|
||
|
|
if capital > 0:
|
||
|
|
if START_CAP is None:
|
||
|
|
START_CAP = capital
|
||
|
|
if CAP_PEAK is None or capital > CAP_PEAK:
|
||
|
|
CAP_PEAK = capital
|
||
|
|
|
||
|
|
roi = ((capital - START_CAP) / START_CAP * 100) if START_CAP and capital else 0
|
||
|
|
dd = ((CAP_PEAK - capital) / CAP_PEAK * 100) if CAP_PEAK and capital < CAP_PEAK else 0
|
||
|
|
|
||
|
|
pc = PC.get(posture, DIM)
|
||
|
|
sc = SC.get(mhs_st, DIM)
|
||
|
|
tc = GREEN if trader_up else RED
|
||
|
|
roi_c = GREEN if roi >= 0 else RED
|
||
|
|
dd_c = RED if dd > 15 else (YELLOW if dd > 5 else GREEN)
|
||
|
|
|
||
|
|
svc = mh.get("service_status", {})
|
||
|
|
hz_ks = mh.get("hz_key_status", {})
|
||
|
|
|
||
|
|
lines = []
|
||
|
|
lines.append(f"{BOLD}{CYAN}🐬 DOLPHIN-NAUTILUS{RST} {DIM}{now}{RST}")
|
||
|
|
lines.append(f"{'─' * 60}")
|
||
|
|
|
||
|
|
# TRADER
|
||
|
|
lines.append(f"{BOLD}TRADER{RST} {tc}{'● LIVE' if trader_up else '● DOWN'}{RST}"
|
||
|
|
f" phase:{phase} hb:{hb_age}")
|
||
|
|
lines.append(f" vel_div:{vel_div:+.5f} scan:#{eng.get('last_scan_number', '?')}")
|
||
|
|
|
||
|
|
lines.append("")
|
||
|
|
|
||
|
|
# CAPITAL
|
||
|
|
lines.append(f"{BOLD}CAPITAL{RST} {CYAN}${capital:,.2f}{RST}")
|
||
|
|
lines.append(f" ROI: {roi_c}{roi:+.2f}%{RST} DD: {dd_c}{dd:.2f}%{RST}"
|
||
|
|
f" start: ${START_CAP:,.0f}" if START_CAP else "")
|
||
|
|
lines.append(f" trades:{trades} scans:{scans} bar:{eng.get('bar_idx', '?')}")
|
||
|
|
lines.append(f" lev:{lev:.2f}x notional:${notional:,.0f}")
|
||
|
|
|
||
|
|
# Open positions
|
||
|
|
positions = eng.get("open_positions", [])
|
||
|
|
if positions:
|
||
|
|
lines.append(f" {BOLD}OPEN POSITIONS:{RST}")
|
||
|
|
for p in positions:
|
||
|
|
side_c = GREEN if p.get("side") == "LONG" else RED
|
||
|
|
lines.append(f" {side_c}{p.get('asset','?')} {p.get('side','?')}{RST}"
|
||
|
|
f" qty:{p.get('quantity',0):.4f}"
|
||
|
|
f" entry:{p.get('entry_price',0):.2f}"
|
||
|
|
f" pnl:{p.get('unrealized_pnl',0):+.2f}")
|
||
|
|
else:
|
||
|
|
lines.append(f" {DIM}no open positions{RST}")
|
||
|
|
|
||
|
|
lines.append("")
|
||
|
|
|
||
|
|
# POSTURE
|
||
|
|
lines.append(f"{BOLD}POSTURE{RST} {pc}{posture}{RST} Rm:{pc}{_bar(rm, 20)}{RST} {rm:.4f}")
|
||
|
|
cats = " ".join(f"C{i}:{float(bd.get(f'Cat{i}', 0)):.2f}" for i in range(1, 6))
|
||
|
|
lines.append(f" {cats}")
|
||
|
|
lines.append(f" f_env:{float(bd.get('f_env', 0)):.3f} f_exe:{float(bd.get('f_exe', 0)):.3f}"
|
||
|
|
f" boost:{boost:.2f}")
|
||
|
|
|
||
|
|
lines.append("")
|
||
|
|
|
||
|
|
# SYS HEALTH
|
||
|
|
lines.append(f"{BOLD}SYS HEALTH{RST} {sc}{mhs_st}{RST} rm_meta:{rm_meta:.3f}")
|
||
|
|
for m in ("m1_data_infra", "m1_trader", "m2_heartbeat",
|
||
|
|
"m3_data_freshness", "m4_control_plane", "m5_coherence"):
|
||
|
|
v = float(mh.get(m, 0))
|
||
|
|
c = GREEN if v >= 0.9 else (YELLOW if v >= 0.5 else RED)
|
||
|
|
lines.append(f" {m}: {c}{v:.3f}{RST}")
|
||
|
|
|
||
|
|
# Services
|
||
|
|
lines.append(f" {DIM}services:{RST}")
|
||
|
|
for name, st in sorted(svc.items()):
|
||
|
|
dot = f"{GREEN}●{RST}" if st == "RUNNING" else f"{RED}●{RST}"
|
||
|
|
lines.append(f" {dot} {name}")
|
||
|
|
|
||
|
|
# HZ keys
|
||
|
|
lines.append(f" {DIM}hz keys:{RST}")
|
||
|
|
for name, info in sorted(hz_ks.items()):
|
||
|
|
score = float(info.get("score", 0))
|
||
|
|
c = GREEN if score >= 0.9 else (YELLOW if score >= 0.5 else RED)
|
||
|
|
lines.append(f" {c}●{RST} {name}: {info.get('status', '?')}")
|
||
|
|
|
||
|
|
lines.append(f"\n{'─' * 60}")
|
||
|
|
lines.append(f"{DIM}polling 5s • q to quit • {now}{RST}")
|
||
|
|
|
||
|
|
return "\n".join(lines)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
print("Connecting to HZ...")
|
||
|
|
hz = hazelcast.HazelcastClient(
|
||
|
|
cluster_name="dolphin", cluster_members=["localhost:5701"],
|
||
|
|
connection_timeout=5.0)
|
||
|
|
print("Connected. Starting status display...\n")
|
||
|
|
|
||
|
|
try:
|
||
|
|
while True:
|
||
|
|
try:
|
||
|
|
out = render(hz)
|
||
|
|
sys.stdout.write(CLEAR + out + "\n")
|
||
|
|
sys.stdout.flush()
|
||
|
|
except Exception as e:
|
||
|
|
sys.stdout.write(f"\n{RED}Render error: {e}{RST}\n")
|
||
|
|
sys.stdout.flush()
|
||
|
|
time.sleep(5)
|
||
|
|
except KeyboardInterrupt:
|
||
|
|
print(f"\n{DIM}Bye.{RST}")
|
||
|
|
finally:
|
||
|
|
hz.shutdown()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|