Files
DOLPHIN/Observability/TUI/dolphin_tui_v3.py
hjnormey 01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
2026-04-21 16:58:38 +02:00

1054 lines
52 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
DOLPHIN TUI v3 — Live, event-driven observability
==================================================
All panels driven by real data. Zero additional load on origin systems:
• HZ maps → entry listeners (push model; callbacks fire on change only)
• Prefect → polled via SDK every 60 s
• Capital / meta-health → HZ listener + disk fallback
Run: python3 dolphin_tui_v3.py
Keys: q=quit r=force-refresh l=toggle log t=toggle test footer
HZ maps consumed:
DOLPHIN_FEATURES / latest_eigen_scan
DOLPHIN_FEATURES / exf_latest
DOLPHIN_FEATURES / acb_boost
DOLPHIN_FEATURES / obf_universe_latest
DOLPHIN_FEATURES / mc_forewarner_latest
DOLPHIN_META_HEALTH / latest
DOLPHIN_SAFETY / latest
DOLPHIN_STATE_BLUE / capital_checkpoint
DOLPHIN_HEARTBEAT / nautilus_flow_heartbeat
"""
# ── stdlib ────────────────────────────────────────────────────────────────────
import asyncio
import json
import math
import threading
import time
from collections import deque
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional
# ── third-party ───────────────────────────────────────────────────────────────
try:
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical
from textual.widgets import Digits, ProgressBar, Sparkline, Static
except ImportError as e:
raise SystemExit(f"textual not found — activate siloqy_env: {e}")
try:
import hazelcast
_HZ_OK = True
except ImportError:
_HZ_OK = False
# ── paths ─────────────────────────────────────────────────────────────────────
_META_JSON = Path("/mnt/dolphinng5_predict/run_logs/meta_health.json")
_CAPITAL_JSON = Path("/tmp/dolphin_capital_checkpoint.json")
_TEST_JSON = Path("/mnt/dolphinng5_predict/run_logs/test_results_latest.json")
_PREFECT_BASE = "http://localhost:4200"
# ── symbols watched in OBF panel ──────────────────────────────────────────────
_OBF_SYMS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]
# ── color maps ────────────────────────────────────────────────────────────────
_PC = {"APEX": "green", "STALKER": "yellow", "TURTLE": "dark_orange", "HIBERNATE": "red"}
_MC = {"GREEN": "green", "ORANGE": "dark_orange", "RED": "red"}
_SC = {"GREEN": "green", "DEGRADED": "yellow", "CRITICAL": "dark_orange", "DEAD": "red"}
# ═════════════════════════════════════════════════════════════════════════════
# Thread-safe state store
# ═════════════════════════════════════════════════════════════════════════════
class _State:
"""Lock-guarded dict shared between HZ listener threads and the Textual loop."""
def __init__(self):
self._l = threading.Lock()
self._d: Dict[str, Any] = {}
def put(self, k: str, v: Any):
with self._l: self._d[k] = v
def get(self, k: str, default=None):
with self._l: return self._d.get(k, default)
def update(self, mapping: dict):
with self._l: self._d.update(mapping)
_S = _State()
# ═════════════════════════════════════════════════════════════════════════════
# HZ entry-listener adapter (runs in a daemon thread, calls back to Textual)
# ═════════════════════════════════════════════════════════════════════════════
def _ingest(key: str, raw: Optional[str]):
"""Parse JSON payload and store in shared state."""
if not raw:
return
try:
_S.update({f"hz.{key}": json.loads(raw), f"hz.{key}._t": time.time()})
except Exception:
pass
def start_hz_listener(on_scan=None):
"""
Daemon thread: connect, initial-fetch all keys, attach entry listeners.
on_scan: callable invoked (from HZ thread) when latest_eigen_scan arrives.
Reconnects automatically on disconnect.
"""
if not _HZ_OK:
_S.put("hz_up", False)
return
def _run():
while True:
try:
_S.put("hz_up", False)
client = hazelcast.HazelcastClient(
cluster_name="dolphin",
cluster_members=["localhost:5701"],
connection_timeout=5.0,
)
_S.put("hz_up", True)
# ── DOLPHIN_FEATURES ─────────────────────────────────────
fm = client.get_map("DOLPHIN_FEATURES").blocking()
for k in ("latest_eigen_scan", "exf_latest", "acb_boost",
"obf_universe_latest", "mc_forewarner_latest"):
_ingest(k, fm.get(k))
def _f(e):
_ingest(e.key, e.value)
if e.key == "latest_eigen_scan" and on_scan:
try: on_scan()
except Exception: pass
fm.add_entry_listener(include_value=True, updated=_f, added=_f)
# ── DOLPHIN_META_HEALTH ──────────────────────────────────
mhm = client.get_map("DOLPHIN_META_HEALTH").blocking()
_ingest("meta_health", mhm.get("latest"))
def _mh(e):
if e.key == "latest": _ingest("meta_health", e.value)
mhm.add_entry_listener(include_value=True, updated=_mh, added=_mh)
# ── DOLPHIN_SAFETY ───────────────────────────────────────
sm = client.get_map("DOLPHIN_SAFETY").blocking()
_ingest("safety", sm.get("latest"))
def _sf(e):
if e.key == "latest": _ingest("safety", e.value)
sm.add_entry_listener(include_value=True, updated=_sf, added=_sf)
# ── DOLPHIN_STATE_BLUE ───────────────────────────────────
stm = client.get_map("DOLPHIN_STATE_BLUE").blocking()
_ingest("capital", stm.get("capital_checkpoint"))
_ingest("engine_snapshot", stm.get("engine_snapshot"))
def _cap(e):
if e.key == "capital_checkpoint": _ingest("capital", e.value)
elif e.key == "engine_snapshot": _ingest("engine_snapshot", e.value)
stm.add_entry_listener(include_value=True, updated=_cap, added=_cap)
# ── DOLPHIN_PNL_BLUE (live trade performance) ────────────
try:
pnl_m = client.get_map("DOLPHIN_PNL_BLUE").blocking()
_ingest("pnl_blue", pnl_m.get("session_perf"))
def _pnl(e):
if e.key == "session_perf": _ingest("pnl_blue", e.value)
pnl_m.add_entry_listener(include_value=True, updated=_pnl, added=_pnl)
except Exception:
pass # map may not exist yet — silent until event trader wires it
# ── DOLPHIN_HEARTBEAT ────────────────────────────────────
hbm = client.get_map("DOLPHIN_HEARTBEAT").blocking()
_ingest("heartbeat", hbm.get("nautilus_flow_heartbeat"))
def _hb(e):
if e.key == "nautilus_flow_heartbeat": _ingest("heartbeat", e.value)
hbm.add_entry_listener(include_value=True, updated=_hb, added=_hb)
# Block until disconnected
while True:
time.sleep(5)
if not getattr(client.lifecycle_service, "is_running",
lambda: True)():
break
except Exception as e:
_S.put("hz_up", False)
_S.put("hz_err", str(e))
time.sleep(10)
threading.Thread(target=_run, daemon=True, name="hz-listener").start()
# ═════════════════════════════════════════════════════════════════════════════
# Prefect poll (asyncio task, 60s interval)
# ═════════════════════════════════════════════════════════════════════════════
async def prefect_poll_loop():
"""Poll Prefect SDK for latest flow-run status per flow. 60s interval."""
while True:
try:
from prefect.client.orchestration import get_client
from prefect.client.schemas.sorting import FlowRunSort
async with get_client() as pc:
runs = await pc.read_flow_runs(
limit=20, sort=FlowRunSort.START_TIME_DESC)
seen: Dict[str, Any] = {}
fid_to_name: Dict[str, str] = {}
for r in runs:
fid = str(r.flow_id)
if fid not in seen:
seen[fid] = r
rows = []
for fid, r in seen.items():
if fid not in fid_to_name:
try:
f = await pc.read_flow(r.flow_id)
fid_to_name[fid] = f.name
except Exception:
fid_to_name[fid] = fid[:8]
rows.append({
"name": fid_to_name[fid],
"state": r.state_name or "?",
"ts": r.start_time.strftime("%m-%d %H:%M")
if r.start_time else "--",
})
_S.put("prefect_flows", rows[:8])
_S.put("prefect_ok", True)
except Exception as e:
_S.put("prefect_ok", False)
_S.put("prefect_err", str(e)[:60])
await asyncio.sleep(60)
# ═════════════════════════════════════════════════════════════════════════════
# Helper utilities
# ═════════════════════════════════════════════════════════════════════════════
def _age(ts: float) -> str:
"""Render age of a Unix timestamp as compact string."""
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 _age_col(ts: float, warn=15, dead=60) -> str:
s = time.time() - ts if ts else 9999
if s > dead: return "red"
if s > warn: return "yellow"
return "green"
def _bar(v: float, width=12) -> str:
"""ASCII fill bar 0-1."""
v = max(0.0, min(1.0, v))
f = round(v * width)
return "" * f + "" * (width - f)
def _fmt_vel(v) -> str:
if v is None: return "---"
return f"{float(v):+.5f}"
def _dot(state: str) -> str:
s = (state or "").upper()
if s == "COMPLETED": return "[green]●[/green]"
if s == "RUNNING": return "[cyan]●[/cyan]"
if s in ("FAILED","CRASHED","TIMEDOUT"): return "[red]●[/red]"
if s == "CANCELLED": return "[dim]●[/dim]"
if s == "PENDING": return "[yellow]●[/yellow]"
return "[dim]◌[/dim]"
def _posture_markup(p: str) -> str:
c = _PC.get(p, "dim")
return f"[{c}]{p}[/{c}]"
def _col(v, c): return f"[{c}]{v}[/{c}]"
def _eigen_from_scan(scan: dict):
"""Extract normalised eigen fields from both NG7 (nested result) and NG8 (flat) HZ formats."""
if not scan: return {}
# NG7 wraps in "result"
r = scan.get("result", scan)
mwr_raw = r.get("multi_window_results", {})
# Keys may be ints or strings
def _td(w):
return (mwr_raw.get(w) or mwr_raw.get(str(w)) or {}).get("tracking_data", {})
def _rs(w):
return (mwr_raw.get(w) or mwr_raw.get(str(w)) or {}).get("regime_signals", {})
# NG8 flat format also stores w50_velocity etc.
v50 = _td(50).get("lambda_max_velocity") or scan.get("w50_velocity", 0.0)
v150 = _td(150).get("lambda_max_velocity") or scan.get("w150_velocity", 0.0)
v300 = _td(300).get("lambda_max_velocity") or scan.get("w300_velocity", 0.0)
v750 = _td(750).get("lambda_max_velocity") or scan.get("w750_velocity", 0.0)
vel_div = scan.get("vel_div", float(v50 or 0) - float(v150 or 0))
inst_50 = _rs(50).get("instability_score", 0.0)
inst_avg = sum(_rs(w).get("instability_score", 0.0) for w in (50,150,300,750)) / 4
bt_price = (r.get("pricing_data", {}) or {}).get("current_prices", {}).get("BTCUSDT")
return {
"scan_number": scan.get("scan_number", 0),
"timestamp": scan.get("timestamp", 0),
"vel_div": float(vel_div or 0),
"v50": float(v50 or 0), "v150": float(v150 or 0),
"v300": float(v300 or 0), "v750": float(v750 or 0),
"inst50": float(inst_50 or 0),
"inst_avg": float(inst_avg or 0),
"btc_price": float(bt_price) if bt_price else None,
"regime": r.get("regime", r.get("sentiment", "?")),
"version": scan.get("version", "?"),
}
# ═════════════════════════════════════════════════════════════════════════════
# CSS
# ═════════════════════════════════════════════════════════════════════════════
_CSS = """
Screen { background: #0a0a0a; color: #d4d4d4; }
#header { height: 2; background: #111; border-bottom: solid #333; padding: 0 1; }
#trader_row { height: 5; }
#top_row { height: 9; }
#mid_row { height: 9; }
#bot_row { height: 7; }
#log_row { height: 5; display: none; }
#mc_outer { height: 16; border: solid #224; background: #060616; }
#mc_title { height: 1; padding: 0 1; }
#mc_body { height: 15; }
#mc_left { width: 18; padding: 0 1; }
#mc_center { width: 1fr; padding: 0 1; }
#mc_right { width: 30; padding: 0 1; }
/* Tier 1: envelope assessment */
#mc_prob_label { height: 1; }
#mc_prob_bar { height: 1; }
#mc_env_label { height: 1; }
#mc_env_bar { height: 1; }
#mc_champ_label { height: 1; }
#mc_champ_bar { height: 1; }
/* Tier 2: live perf (ROI/DD/WR + MAE rows + leverage slider) */
#mc_live { height: 8; }
/* Right column */
#mc_spark_lbl { height: 1; }
#mc_spark { height: 2; }
#mc_mae_spark { height: 2; }
#mc_mae_lbl { height: 1; }
#mc_digits { height: 3; }
#mc_status { height: 3; }
#mc_legend { height: 6; }
#test_footer { height: 3; background: #101010; border-top: solid #2a2a2a; padding: 0 1; }
Static.panel { border: solid #333; padding: 0 1; height: 100%; }
#p_trader { width: 1fr; border: solid #006650; }
#p_health { width: 1fr; }
#p_alpha { width: 1fr; }
#p_scan { width: 1fr; }
#p_extf { width: 1fr; }
#p_obf { width: 1fr; }
#p_capital { width: 1fr; }
#p_prefect { width: 1fr; }
#p_acb { width: 1fr; }
#p_log { width: 1fr; border: solid #333; padding: 0 1; }
ProgressBar > .bar--bar { color: $success; }
ProgressBar > .bar--complete { color: $success; }
ProgressBar.-danger > .bar--bar { color: $error; }
ProgressBar.-warning > .bar--bar { color: $warning; }
"""
# ═════════════════════════════════════════════════════════════════════════════
# Textual App
# ═════════════════════════════════════════════════════════════════════════════
class DolphinTUI(App):
CSS = _CSS
BINDINGS = [
("q", "quit", "Quit"),
("r", "force_refresh","Refresh"),
("l", "toggle_log", "Log"),
("t", "toggle_tests", "Tests"),
]
_log_vis = False
_test_vis = True
_prob_hist: deque
# ── Layout ───────────────────────────────────────────────────────────────
def compose(self) -> ComposeResult:
yield Static("", id="header")
with Horizontal(id="trader_row"):
yield Static("", classes="panel", id="p_trader")
with Horizontal(id="top_row"):
yield Static("", classes="panel", id="p_health")
yield Static("", classes="panel", id="p_alpha")
yield Static("", classes="panel", id="p_scan")
with Horizontal(id="mid_row"):
yield Static("", classes="panel", id="p_extf")
yield Static("", classes="panel", id="p_obf")
yield Static("", classes="panel", id="p_capital")
with Horizontal(id="bot_row"):
yield Static("", classes="panel", id="p_prefect")
yield Static("", classes="panel", id="p_acb")
with Vertical(id="mc_outer"):
yield Static("", id="mc_title")
with Horizontal(id="mc_body"):
with Vertical(id="mc_left"):
yield Digits("0.000", id="mc_digits")
yield Static("", id="mc_status")
with Vertical(id="mc_center"):
# ── Tier 1: envelope assessment (4h) ──
yield Static("", id="mc_prob_label")
yield ProgressBar(total=100, show_eta=False,
show_percentage=False, id="mc_prob_bar")
yield Static("", id="mc_env_label")
yield ProgressBar(total=100, show_eta=False,
show_percentage=False, id="mc_env_bar")
yield Static("", id="mc_champ_label")
yield ProgressBar(total=100, show_eta=False,
show_percentage=False, id="mc_champ_bar")
# ── Tier 2: live performance vs MC bounds ──
yield Static("", id="mc_live")
with Vertical(id="mc_right"):
yield Static("", id="mc_spark_lbl")
yield Sparkline([], id="mc_spark")
yield Static("", id="mc_mae_lbl")
yield Sparkline([], id="mc_mae_spark")
yield Static("", id="mc_legend")
yield Static("", id="test_footer")
with Horizontal(id="log_row"):
yield Static("", classes="panel", id="p_log")
def on_mount(self) -> None:
self._prob_hist = deque([0.0] * 40, maxlen=40)
self._session_start_cap: Optional[float] = None # first capital seen → ROI denominator
self._cap_peak: Optional[float] = None # peak capital → live DD numerator
self._mae_deque: deque = deque(maxlen=500) # per-trade MAE values, newest last
# Event-driven callback: fires from HZ listener thread
start_hz_listener(on_scan=lambda: self.call_from_thread(self._update))
# Prefect async poll
self.run_worker(prefect_poll_loop(), name="prefect-poll", exclusive=True)
# Periodic fallback refresh (catches non-HZ sources + any missed events)
self.set_interval(1.0, self._update)
self._update()
# ── Master render ────────────────────────────────────────────────────────
def _update(self) -> None:
now_str = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
hz_up = _S.get("hz_up", False)
# Pull all live data from state
mh = _S.get("hz.meta_health") or self._read_json(_META_JSON)
safe = _S.get("hz.safety", {})
scan = _S.get("hz.latest_eigen_scan", {})
exf = _S.get("hz.exf_latest", {})
acb = _S.get("hz.acb_boost", {})
obf_u = _S.get("hz.obf_universe_latest", {})
mc = _S.get("hz.mc_forewarner_latest", {})
cap = _S.get("hz.capital") or self._read_json(_CAPITAL_JSON) or {}
hb = _S.get("hz.heartbeat", {})
eigen = _eigen_from_scan(scan)
# ── HEADER ───────────────────────────────────────────────────────────
rm_m = mh.get("rm_meta", 0.0) if mh else 0.0
mhs_st = mh.get("status", "?") if mh else "?"
sc_mhs = _SC.get(mhs_st, "dim")
posture= (safe or {}).get("posture", "?")
pc_col = _PC.get(posture, "dim")
hz_tag = "[green][HZ✓][/green]" if hz_up else "[red][HZ✗][/red]"
mc_st = (mc or {}).get("status", "N/A")
mc_col = _MC.get(mc_st, "dim")
self._w("#header").update(
f"[bold cyan]🐬 DOLPHIN-NAUTILUS[/bold cyan] {now_str}"
f" {hz_tag} [{sc_mhs}]MHS:{mhs_st} {rm_m:.3f}[/{sc_mhs}]"
f" [{pc_col}]◈{posture}[/{pc_col}]"
f" [{mc_col}]MC:{mc_st}[/{mc_col}]\n"
f"[dim] localhost:5701 q=quit r=refresh l=log t=tests[/dim]"
)
# ── TRADER ───────────────────────────────────────────────────────────
cap_val = float(cap.get("capital", 0)) if cap else 0.0
hb_phase = hb.get("phase", "?") if hb else "N/A"
hb_ts = hb.get("ts") if hb else None
hb_age = _age(hb_ts) if hb_ts else "?"
hb_col = _age_col(hb_ts, 30, 120) if hb_ts else "red"
vel_div = eigen.get("vel_div", 0.0)
vc = "green" if vel_div > 0 else ("red" if vel_div < -0.02 else "yellow")
scan_no = eigen.get("scan_number", 0)
scan_ts = eigen.get("timestamp", 0)
vol_ok_s = "[green]VOL✓[/green]" # placeholder — would need vol calc
btc_p = eigen.get("btc_price")
btc_str = f"BTC:[cyan]${btc_p:,.0f}[/cyan] " if btc_p else ""
regime = eigen.get("regime", "?")
self._w("#p_trader").update(
f"[bold cyan]TRADER[/bold cyan] {_posture_markup(posture)}"
f" phase:[{hb_col}]{hb_phase}[/{hb_col}] hb:{_col(hb_age, hb_col)}"
f" scan:[dim]#{scan_no}[/dim] {btc_str}"
f" regime:[yellow]{regime}[/yellow] {vol_ok_s}\n"
f" vel_div:[{vc}]{vel_div:+.5f}[/{vc}] thr:[dim]-0.02000[/dim]"
f" cap:[cyan]${cap_val:,.0f}[/cyan]\n"
f" open-pos: [dim]read DOLPHIN_PNL_BLUE (not yet wired)[/dim]"
)
# ── SYS HEALTH ───────────────────────────────────────────────────────
if mh:
svc = mh.get("service_status", {})
hz_ks = mh.get("hz_key_status", {})
def _svc_dot(nm):
st = svc.get(nm, "?")
return "[green]●[/green]" if st=="RUNNING" else "[red]●[/red]"
def _hz_dot(nm):
info = hz_ks.get(nm, {})
sc = info.get("score", 0)
return "[green]●[/green]" if sc >= 0.9 else ("[yellow]●[/yellow]" if sc >= 0.5 else "[red]●[/red]")
self._w("#p_health").update(
f"[bold]SYS HEALTH[/bold] [{sc_mhs}]{mhs_st}[/{sc_mhs}]\n"
f"rm:[{sc_mhs}]{rm_m:.3f}[/{sc_mhs}]"
f" m4:{mh['m4_control_plane']:.2f}"
f" m1d:{mh['m1_data_infra']:.2f}"
f" m3:{mh['m3_data_freshness']:.2f}"
f" m5:{mh['m5_coherence']:.2f}\n"
f"exf:{_svc_dot('dolphin_data:exf_fetcher')}"
f" acb:{_svc_dot('dolphin_data:acb_processor')}"
f" obf:{_svc_dot('dolphin_data:obf_universe')}\n"
f"tdr:{_svc_dot('dolphin:nautilus_trader')}"
f" scb:{_svc_dot('dolphin:scan_bridge')}\n"
f"[dim]hz: exf{_hz_dot('exf_latest')}"
f" scan{_hz_dot('latest_eigen_scan')}"
f" obf{_hz_dot('obf_universe')}[/dim]"
)
else:
self._w("#p_health").update("[bold]SYS HEALTH[/bold]\n[dim]awaiting MHS…[/dim]")
# ── ALPHA ENGINE (SurvivalStack) ──────────────────────────────────────
rm_s = float((safe or {}).get("Rm", 0.0))
bd = (safe or {}).get("breakdown", {})
safe_ts = _S.get("hz.safety._t")
safe_age = _age(safe_ts) if safe_ts else "?"
safe_ac = _age_col(safe_ts, 30, 120) if safe_ts else "red"
def _cat(n):
v = bd.get(f"Cat{n}", 0.0)
c = "green" if v >= 0.9 else ("yellow" if v >= 0.6 else "red")
return f"[{c}]{v:.2f}[/{c}]"
self._w("#p_alpha").update(
f"[bold]ALPHA ENGINE[/bold] {_posture_markup(posture)}\n"
f"Rm:[{_PC.get(posture,'dim')}]{_bar(rm_s,14)}[/{_PC.get(posture,'dim')}]{rm_s:.3f}\n"
f"C1:{_cat(1)} C2:{_cat(2)} C3:{_cat(3)}\n"
f"C4:{_cat(4)} C5:{_cat(5)}"
f" fenv:{bd.get('f_env',0):.2f} fex:{bd.get('f_exe',0):.2f}\n"
f"[dim]age:{_col(safe_age, safe_ac)}[/dim]"
)
# ── SCAN / NG8 ────────────────────────────────────────────────────────
scan_age = _age(eigen.get("timestamp")) if eigen.get("timestamp") else "?"
scan_ac = _age_col(eigen.get("timestamp", 0), 15, 60)
vi = eigen.get("inst_avg", 0)
self._w("#p_scan").update(
f"[bold]SCAN {eigen.get('version','?')}[/bold]"
f" [dim]#{scan_no}[/dim] age:[{scan_ac}]{scan_age}[/{scan_ac}]\n"
f"vel_div:[{vc}]{vel_div:+.5f}[/{vc}]\n"
f"w50:[yellow]{_fmt_vel(eigen.get('v50'))}[/yellow]"
f" w150:[dim]{_fmt_vel(eigen.get('v150'))}[/dim]\n"
f"w300:[dim]{_fmt_vel(eigen.get('v300'))}[/dim]"
f" w750:[dim]{_fmt_vel(eigen.get('v750'))}[/dim]\n"
f"inst:{vi:.4f}"
)
# ── ExtF ──────────────────────────────────────────────────────────────
exf_t = _S.get("hz.exf_latest._t")
exf_age = _age(exf_t) if exf_t else "?"
exf_ac = _age_col(exf_t, 30, 120) if exf_t else "red"
f_btc = exf.get("funding_btc")
dvol = exf.get("dvol_btc")
fng = exf.get("fng")
taker = exf.get("taker")
ls_btc = exf.get("ls_btc")
vix = exf.get("vix")
ok_cnt = exf.get("_ok_count", 0)
dvol_c = "red" if dvol and dvol > 70 else ("yellow" if dvol and dvol > 50 else "green")
fng_c = "red" if fng and fng < 25 else ("yellow" if fng and fng < 45 else "green")
def _exf_str():
if not exf:
return "[bold]ExtF[/bold]\n[dim]no data[/dim]"
_f = f"{f_btc:.5f}" if f_btc is not None else "?"
_dv = f"{dvol:.1f}" if dvol is not None else "?"
_fg = f"{int(fng)}" if fng is not None else "?"
_tk = f"{taker:.3f}" if taker is not None else "?"
_ls = f"{ls_btc:.3f}"if ls_btc is not None else "?"
_vx = f"{vix:.1f}" if vix is not None else "?"
return (
f"[bold]ExtF[/bold] [{exf_ac}]{ok_cnt}/9 {exf_age}[/{exf_ac}]\n"
f"fund:[cyan]{_f}[/cyan] dvol:[{dvol_c}]{_dv}[/{dvol_c}]\n"
f"fng:[{fng_c}]{_fg}[/{fng_c}] taker:[yellow]{_tk}[/yellow]\n"
f"ls_btc:{_ls} vix:{_vx}\n"
f"acb✓:{exf.get('_acb_ready','?')}"
)
self._w("#p_extf").update(_exf_str())
# ── OBF ───────────────────────────────────────────────────────────────
obf_t = _S.get("hz.obf_universe_latest._t")
obf_age = _age(obf_t) if obf_t else "?"
obf_ac = _age_col(obf_t, 30, 120) if obf_t else "red"
n_assets = obf_u.get("_n_assets", 0) if obf_u else 0
lines = [f"[bold]OBF[/bold] [{obf_ac}]n={n_assets} {obf_age}[/{obf_ac}]"]
for sym in _OBF_SYMS:
if not obf_u: break
a = obf_u.get(sym)
if not a: continue
imb = float(a.get("imbalance", 0))
fp = float(a.get("fill_probability", 0))
dq = float(a.get("depth_quality", 0))
imb_c = "green" if imb > 0.1 else ("red" if imb < -0.1 else "yellow")
lines.append(
f"{sym[:3]} [{imb_c}]{imb:+.2f}[/{imb_c}]"
f" fp:{fp:.2f} dq:{dq:.2f}"
)
self._w("#p_obf").update("\n".join(lines[:6]))
# ── CAPITAL ───────────────────────────────────────────────────────────
cap_t = _S.get("hz.capital._t")
cap_ac = _age_col(cap_t, 60, 300) if cap_t else "dim"
cap_age= _age(cap_t) if cap_t else "?"
# Drawdown from safety Cat5 (≈1 at <5%DD, ≈0.5 at 12%)
c5 = bd.get("Cat5", 1.0) if bd else 1.0
# Invert sigmoid: DD_approx = 0.12 + ln((1/c5)-1)/30
try:
dd_est = 0.12 + math.log(1.0/c5 - 1.0) / 30.0 if 0 < c5 < 1 else 0.0
except Exception:
dd_est = 0.0
dd_c = "red" if dd_est > 0.15 else ("yellow" if dd_est > 0.08 else "green")
self._w("#p_capital").update(
f"[bold]CAPITAL[/bold] [{cap_ac}]{cap_age}[/{cap_ac}]\n"
f"Cap:[cyan]${cap_val:,.0f}[/cyan]\n"
f"DD≈:[{dd_c}]{dd_est*100:.1f}%[/{dd_c}] C5:{c5:.3f}\n"
f"Pos:{_posture_markup(posture)}\n"
f"[dim]pnl/trades: DOLPHIN_PNL_BLUE[/dim]"
)
# ── PREFECT ───────────────────────────────────────────────────────────
flows = _S.get("prefect_flows") or []
pf_ok = _S.get("prefect_ok", False)
pf_hdr = "[green]✓[/green]" if pf_ok else "[red]✗[/red]"
flines = "\n".join(
f"{_dot(f['state'])} {f['name'][:22]:<22} {f['ts']}"
for f in flows[:5]
)
self._w("#p_prefect").update(
f"[bold]PREFECT[/bold] {pf_hdr}\n"
+ (flines or "[dim]polling…[/dim]")
)
# ── ACB ───────────────────────────────────────────────────────────────
acb_t = _S.get("hz.acb_boost._t")
acb_age = _age(acb_t) if acb_t else "?"
acb_ac = _age_col(acb_t, 3600, 86400) if acb_t else "dim"
boost = acb.get("boost", 1.0) if acb else 1.0
beta = acb.get("beta", 0.8) if acb else 0.8
cut = acb.get("cut", 0.0) if acb else 0.0
w750_thr = acb.get("w750_threshold", 0.0) if acb else 0.0
acb_date = acb.get("date", "?") if acb else "?"
boost_c = "green" if boost >= 1.5 else ("yellow" if boost >= 1.0 else "red")
cut_c = "red" if cut > 0 else "dim"
self._w("#p_acb").update(
f"[bold]ACB[/bold] [{acb_ac}]{acb_date}[/{acb_ac}]\n"
f"boost:[{boost_c}]{boost:.2f}x[/{boost_c}]"
f" β={beta:.2f}"
f" cut:[{cut_c}]{cut:.2f}[/{cut_c}]\n"
f"w750_thr:{w750_thr:.4f}\n"
f"[dim]factors: dvol={acb.get('factors',{}).get('dvol_btc','?') if acb else '?'}"
f" fng={acb.get('factors',{}).get('fng','?') if acb else '?'}[/dim]\n"
f"[dim]age:{acb_age} cfg:{acb.get('config_used','?') if acb else '?'}[/dim]"
)
# ── MC FOREWARNER FOOTER ─────────────────────────────────────────────
# --- Session capital tracking (for live ROI + DD) ---
cur_cap = float((cap or {}).get("capital", 0.0)) if cap else 0.0
if cur_cap > 0:
if self._session_start_cap is None:
self._session_start_cap = cur_cap
if self._cap_peak is None or cur_cap > self._cap_peak:
self._cap_peak = cur_cap
eng_snap = _S.get("hz.engine_snapshot", {}) or {}
trades_ex = eng_snap.get("trades_executed", None)
# Live ROI and session DD (computable from capital alone)
live_roi: Optional[float] = None
live_dd: Optional[float] = None
if cur_cap > 0 and self._session_start_cap and self._session_start_cap > 0:
live_roi = (cur_cap - self._session_start_cap) / self._session_start_cap
if cur_cap > 0 and self._cap_peak and self._cap_peak > 0 and cur_cap < self._cap_peak:
live_dd = (self._cap_peak - cur_cap) / self._cap_peak
# MC envelope fields
prob = float((mc or {}).get("catastrophic_prob", 0.0))
env = float((mc or {}).get("envelope_score", 0.0))
champ_p = (mc or {}).get("champion_probability", None)
pred_roi = (mc or {}).get("predicted_roi", None)
pred_roi_lo= (mc or {}).get("predicted_roi_p10", None)
pred_roi_hi= (mc or {}).get("predicted_roi_p90", None)
pred_dd = (mc or {}).get("predicted_max_dd", None)
mc_warns = (mc or {}).get("warnings", [])
mc_ts = (mc or {}).get("timestamp", None)
sc = _MC.get(mc_st, "dim")
self._prob_hist.append(prob)
# Staleness: time since last 4h MC run
mc_age_str = ""
if mc_ts:
try:
mc_dt = datetime.fromisoformat(mc_ts.replace("Z", "+00:00"))
age_s = (datetime.now(timezone.utc) - mc_dt).total_seconds()
age_m = int(age_s // 60)
mc_age_str = f"{age_m//60}h{age_m%60:02d}m ago" if age_m >= 60 else f"{age_m}m ago"
except Exception:
pass
# Title row
self._w("#mc_title").update(
f"[bold cyan]⚡ MC-FOREWARNER RISK MANIFOLD[/bold cyan]"
f" [{sc}]▶ {mc_st}[/{sc}]"
f" [dim]src:{(mc or {}).get('source','N/A')} assessed:{mc_age_str} next:~4h cadence[/dim]"
if mc else
"[bold cyan]⚡ MC-FOREWARNER RISK MANIFOLD[/bold cyan]"
" [yellow]awaiting HZ data (Prefect 4h schedule)[/yellow]"
)
# Left: big cat.prob digits + status summary
self._w("#mc_digits", Digits).update(f"{prob:.3f}")
status_str = {"GREEN": "🟢 SAFE", "ORANGE": "🟡 CAUTION", "RED": "🔴 DANGER"}.get(mc_st, "⚪ N/A")
champ_str = f"champ:{champ_p*100:.0f}%" if champ_p is not None else "champ:—"
self._w("#mc_status").update(
f"[{sc}]{status_str}[/{sc}]\n"
f"[dim]cat.prob {champ_str}[/dim]\n"
f"[dim]env:{env:.3f}[/dim]"
)
# Center tier 1: catastrophic_prob bar
pb = self._w("#mc_prob_bar", ProgressBar)
pb.progress = int(prob * 100)
pb.remove_class("-danger", "-warning")
if prob >= 0.30: pb.add_class("-danger")
elif prob >= 0.10: pb.add_class("-warning")
self._w("#mc_prob_label").update(
f"[dim]cat.prob[/dim] [{sc}]{prob:.4f}[/{sc}]"
f" [green]<0.10 OK[/green] [yellow]<0.30 WARN[/yellow] [red]≥0.30 CRIT[/red]"
)
# envelope_score bar (inverted: low = danger)
eb = self._w("#mc_env_bar", ProgressBar)
eb.progress = int(env * 100)
eb.remove_class("-danger", "-warning")
if env < 0.40: eb.add_class("-danger")
elif env < 0.70: eb.add_class("-warning")
self._w("#mc_env_label").update(
f"[dim]env.score[/dim] [green]{env:.4f}[/green]"
f" [red]<0.40 DANGER[/red] [yellow]<0.70 CAUTION[/yellow] [green]≥0.70 SAFE[/green]"
)
# champion_probability bar
chp_val = champ_p if champ_p is not None else 0.0
cb = self._w("#mc_champ_bar", ProgressBar)
cb.progress = int(chp_val * 100)
cb.remove_class("-danger", "-warning")
if chp_val < 0.30: cb.add_class("-danger")
elif chp_val < 0.60: cb.add_class("-warning")
self._w("#mc_champ_label").update(
f"[dim]champ.prob[/dim] "
+ (f"[green]{champ_p*100:.1f}%[/green]" if champ_p is not None else "[dim]—[/dim]")
+ " [green]>60% GOOD[/green] [yellow]>30% MARGINAL[/yellow] [red]<30% RISK[/red]"
)
# Center tier 2: live performance vs MC predicted bounds
def _pct(v):
return f"{v*100:+.1f}%" if v is not None else ""
def _edge_bar(live, lo, hi, width=18, invert=False):
"""ASCII distance-to-edge bar. live/lo/hi all same units (fraction)."""
if live is None or lo is None or hi is None:
return "[dim]" + "·" * width + "[/dim]"
span = hi - lo
if span <= 0:
return "[dim]?[/dim]"
pos = max(0.0, min(1.0, (live - lo) / span))
if invert: # for DD: high = closer to danger
pos = 1.0 - pos
idx = int(pos * (width - 1))
bar = "·" * idx + "|" + "·" * (width - 1 - idx)
col = "green" if pos > 0.40 else ("yellow" if pos > 0.15 else "red")
return f"[{col}]{bar}[/{col}]"
# ROI line — MC hard thresholds from mc_metrics.py:
# strongly_profitable (champion gate) : ROI > +30%
# L_catastrophic : ROI < -30%
# Bar spans -30% → +30% (champion gate); position = distance from catastrophic edge
_ROI_CRIT = -0.30 # catastrophic lower bound
_ROI_CHAMP = 0.30 # champion gate (strongly_profitable)
roi_bar = _edge_bar(live_roi, _ROI_CRIT, _ROI_CHAMP) if live_roi is not None else _edge_bar(None, None, None)
roi_live = _pct(live_roi)
roi_mc = (f"MC p10-p90:[{_pct(pred_roi_lo)},{_pct(pred_roi_hi)}]"
if pred_roi_lo is not None else "MC:—")
roi_line = f"[dim]ROI [/dim]{roi_live:>8} {roi_bar} [dim]{roi_mc} champ:>{_pct(_ROI_CHAMP)}[/dim]"
# DD line — MC hard thresholds from mc_metrics.py:
# drawdown_ok (champion gate) : DD < 20% ← crossing here kills champion_prob
# L_catastrophic : DD > 40% ← crossing here spikes catastrophic_prob
# Bar spans 0% → 40% (catastrophic boundary); 20% marker = champion gate
_DD_CHAMP = 0.20 # champion region gate
_DD_CRIT = 0.40 # catastrophic label threshold
dd_bar = _edge_bar(live_dd, 0.0, _DD_CRIT, invert=True) if live_dd is not None else _edge_bar(None, None, None)
dd_live = f"{live_dd*100:.1f}%" if live_dd is not None else ""
dd_champ_dist = f"edge:{(_DD_CHAMP - live_dd)*100:.1f}%rm" if live_dd is not None else ""
dd_pred_str = f"champ gate:<{_DD_CHAMP*100:.0f}% crit:>{_DD_CRIT*100:.0f}% {dd_champ_dist}"
dd_line = f"[dim]DD [/dim]{dd_live:>8} {dd_bar} [dim]{dd_pred_str}[/dim]"
# ── WR / PF / Sharpe / Calmar — from DOLPHIN_PNL_BLUE when wired ──────
pnl_blue = _S.get("hz.pnl_blue") or {}
def _lm(key, fmt="{:.3f}"):
v = pnl_blue.get(key)
return fmt.format(v) if v is not None else ""
if pnl_blue:
# Ingest per-trade MAE list if present (newest-first list)
raw_mae_list = pnl_blue.get("trade_mae_history") or []
if raw_mae_list:
# Only extend with values not yet in deque (check by length delta)
new_count = max(0, len(raw_mae_list) - len(self._mae_deque))
for v in raw_mae_list[-new_count:]:
try: self._mae_deque.append(float(v))
except Exception: pass
wr_line = (
f"[dim]WR[/dim] {_lm('win_rate','{:.1%}'):>7} "
f"[dim]PF:{_lm('profit_factor','{:.2f}')} "
f"Sh:{_lm('sharpe','{:.2f}')} "
f"Cal:{_lm('calmar','{:.2f}')}[/dim]"
) if pnl_blue else (
f"[dim]WR/PF/Sharpe/Calmar — awaiting DOLPHIN_PNL_BLUE[/dim]"
)
# ── MAE rolling windows ──────────────────────────────────────────────
# MAE = max adverse excursion per trade (for SHORTs: max price rise above entry)
# Windows: last 1 / 5 / 10 / 25 / 75 / 100 / 250 / 500 trades
_MAE_WINDOWS = (1, 5, 10, 25, 75, 100, 250, 500)
mae_list = list(self._mae_deque) # O(n) copy, done once per render
def _mae_window(n):
sl = mae_list[-n:] if len(mae_list) >= n else mae_list
return (max(sl) if sl else None)
if mae_list:
parts = []
for w in _MAE_WINDOWS:
v = _mae_window(w)
if v is None:
parts.append(f"[dim]{w}:—[/dim]")
else:
col = "green" if v < 0.005 else ("yellow" if v < 0.015 else "red")
parts.append(f"[{col}]{w}:{v*100:.2f}%[/{col}]")
mae_line1 = "[dim]MAE max/window [/dim]" + " ".join(parts[:4])
mae_line2 = " " + " ".join(parts[4:])
else:
src = "DOLPHIN_PNL_BLUE" if not pnl_blue else "trade_mae_history"
mae_line1 = f"[dim]MAE — awaiting {src}[/dim]"
mae_line2 = ""
# ── Leverage slider ──────────────────────────────────────────────────
# Sources: engine_snapshot (leverage_soft_cap, leverage_abs_cap, current_leverage)
# ACB boost (scales effective ceiling)
# Zones: 1x5x GREEN (MC champion ref) 5x8x YELLOW (soft cap) 8x9x RED (abs cap)
_LEV_CHAMP = 5.0 # MC champion reference leverage (mc_sampler CHAMPION max_leverage)
_LEV_SOFT = float(eng_snap.get("leverage_soft_cap", 8.0))
_LEV_ABS = float(eng_snap.get("leverage_abs_cap", 9.0))
cur_lev = float(eng_snap.get("current_leverage", 0.0))
acb_boost_v= float((acb or {}).get("boost", 1.0))
# Effective ceiling = base soft cap × ACB boost, clamped to abs cap
eff_ceil = min(_LEV_SOFT * acb_boost_v, _LEV_ABS)
# Build the slider bar (width=40 characters, range 0→_LEV_ABS)
_W = 40
def _lev_bar(cur, ceil, soft, champ, abs_cap, width=_W):
"""
Zones (characters proportional to leverage range 0→abs_cap):
0 ──── champ: green fill / green dot
champ ─ soft: yellow fill / yellow dot
soft ── abs: red fill / red dot
Current position: ◈ (bold white)
Ceiling: ┤ marker
"""
scale = width / abs_cap
# Build char array
chars = []
for i in range(width):
lev_at = (i + 0.5) / scale
if lev_at <= champ: chars.append(("", "green"))
elif lev_at <= soft: chars.append(("", "yellow"))
else: chars.append(("", "red"))
# Place ceiling marker
ceil_idx = min(width - 1, int(ceil * scale))
c, _ = chars[ceil_idx]
chars[ceil_idx] = ("", "bold white")
# Place current leverage cursor (if > 0)
if cur > 0:
cur_idx = min(width - 1, int(cur * scale))
chars[cur_idx] = ("", "bold cyan")
# Render using run-length encoding for performance
out = ""
prev_col = None
seg = ""
for ch, col in chars:
if col != prev_col:
if seg and prev_col:
out += f"[{prev_col}]{seg}[/{prev_col}]"
seg = ch
prev_col = col
else:
seg += ch
if seg and prev_col:
out += f"[{prev_col}]{seg}[/{prev_col}]"
return out
lev_bar_str = _lev_bar(cur_lev, eff_ceil, _LEV_SOFT, _LEV_CHAMP, _LEV_ABS)
cur_lev_str = f"[bold cyan]{cur_lev:.2f}x[/bold cyan]" if cur_lev > 0 else "[dim]—[/dim]"
ceil_col = "green" if eff_ceil <= _LEV_CHAMP else ("yellow" if eff_ceil <= _LEV_SOFT else "red")
lev_line1 = (
f"[dim]LEV[/dim] [{ceil_col}]{eff_ceil:.1f}x[/{ceil_col}][dim]┤[/dim] "
f"{lev_bar_str} "
f"[dim]|{_LEV_ABS:.0f}x[/dim] cur:{cur_lev_str} acb:×{acb_boost_v:.3f}"
)
lev_line2 = (
f"[dim] 1x{''*int((_LEV_CHAMP-1)/_LEV_ABS*_W-2)}"
f"5x(champ){''*int((_LEV_SOFT-_LEV_CHAMP)/_LEV_ABS*_W-9)}"
f"8x(soft){''*int((_LEV_ABS-_LEV_SOFT)/_LEV_ABS*_W-8)}9x(abs)[/dim]"
)
trades_line = (
f"[dim]trades:{trades_ex if trades_ex is not None else ''} "
f"start:${self._session_start_cap:,.0f} "
f"cur:${cur_cap:,.0f}[/dim]"
) if cur_cap > 0 else "[dim]awaiting capital[/dim]"
self._w("#mc_live").update(
f"{roi_line}\n{dd_line}\n{wr_line}\n"
f"{mae_line1}\n{mae_line2}\n"
f"{lev_line1}\n{lev_line2}\n"
f"{trades_line}"
)
# Right column: cat.prob sparkline + MAE sparkline + legend
self._w("#mc_spark_lbl").update(
f"[dim]cat.prob [{min(self._prob_hist):.3f}{max(self._prob_hist):.3f}][/dim]"
)
self._w("#mc_spark", Sparkline).data = list(self._prob_hist)
self._w("#mc_mae_lbl").update(
f"[dim]MAE hist (n={len(mae_list)})[/dim]"
)
self._w("#mc_mae_spark", Sparkline).data = mae_list[-40:] if mae_list else [0.0]
warn_str = ("\n[yellow]⚠ " + mc_warns[0] + "[/yellow]") if mc_warns else ""
self._w("#mc_legend").update(
"[bold]MC THRESHOLDS[/bold]\n"
"[green]GREEN[/green] cat < 0.10\n"
"[yellow]ORANGE[/yellow] cat < 0.30\n"
"[red]RED[/red] cat ≥ 0.30\n"
f"[dim]DD gate: <20%[/dim]\n"
f"[dim]DD crit: >40%[/dim]"
+ warn_str
)
# ── TEST RESULTS FOOTER ──────────────────────────────────────────────
if self._test_vis:
tr = self._read_json(_TEST_JSON) or {}
def _tr_badge(cat):
info = tr.get(cat, {})
if not info: return f"[dim]{cat[:12]}:n/a[/dim]"
p, f = info.get("passed",0), info.get("failed",0)
t = p + f
c = "green" if f == 0 else ("yellow" if f <= 2 else "red")
ts = info.get("ts", "?")[:10]
return f"[{c}]{cat[:10]}:{p}/{t}[/{c}][dim]@{ts}[/dim]"
cats = ["data_integrity","finance_fuzz","signal_fill","degradation","actor"]
badges = " ".join(_tr_badge(c) for c in cats)
last_run = tr.get("_run_at", "never")
self._w("#test_footer").update(
f"[bold dim]TESTS[/bold dim] [dim]last:{last_run}[/dim]\n"
f"{badges}\n"
f"[dim]update: prod/run_logs/test_results_latest.json[/dim]"
)
else:
self._w("#test_footer").update("")
# ── LOG (hidden by default) ───────────────────────────────────────────
if self._log_vis:
hz_err = _S.get("hz_err", "")
pf_err = _S.get("prefect_err", "")
self._w("#p_log").update(
f"[bold]LOG[/bold] (l=hide)\n"
f"[dim]{now_str}[/dim] scan=#{scan_no} vel={vel_div:+.5f}\n"
f"hz_err:{hz_err or 'none'} pf_err:{pf_err or 'none'}\n"
f"[dim]state keys: {len(_S._d)}[/dim]"
)
# ── Actions ──────────────────────────────────────────────────────────────
def action_force_refresh(self) -> None: self._update()
def action_toggle_log(self) -> None:
self._log_vis = not self._log_vis
self.query_one("#log_row").display = self._log_vis
def action_toggle_tests(self) -> None:
self._test_vis = not self._test_vis
self._update()
# ── Helpers ──────────────────────────────────────────────────────────────
def _w(self, selector: str, widget_type=Static):
return self.query_one(selector, widget_type)
@staticmethod
def _read_json(path: Path) -> Optional[dict]:
try:
return json.loads(path.read_text())
except Exception:
return None
# ═════════════════════════════════════════════════════════════════════════════
# Test-result writer (called by CI / manual test scripts)
# ═════════════════════════════════════════════════════════════════════════════
def write_test_results(results: dict):
"""
Called by test scripts to update the TUI test footer.
results = {
"data_integrity": {"passed": 15, "failed": 0, "ts": "2026-04-05T10:00"},
"degradation": {"passed": 12, "failed": 0, "ts": "2026-04-05T10:01"},
...
"_run_at": "2026-04-05T10:01:42Z"
}
"""
_TEST_JSON.parent.mkdir(parents=True, exist_ok=True)
_TEST_JSON.write_text(json.dumps(results, indent=2))
# ═════════════════════════════════════════════════════════════════════════════
# Entry point
# ═════════════════════════════════════════════════════════════════════════════
if __name__ == "__main__":
DolphinTUI().run()