Files
DOLPHIN/Observability/TUI/dolphin_tui_v5.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

741 lines
38 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 v4
==============
Fixes vs v3:
• SYS HEALTH: tdr/scb labels expanded to full service names
• ALPHA ENGINE: falls back to engine_snapshot when DOLPHIN_SAFETY is empty
• MC-FOREWARNER: graceful "not yet run" display; shows last-run age; no crash on absent key
• Version number shown in header after MC status
Run: source /home/dolphin/siloqy_env/bin/activate && python dolphin_tui_v4.py
Keys: q=quit r=force-refresh l=toggle log t=toggle test footer
"""
# ── stdlib ────────────────────────────────────────────────────────────────────
import asyncio, json, math, threading, 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
TUI_VERSION = "TUI v5"
_META_JSON = Path("/mnt/dolphinng5_predict/run_logs/meta_health.json")
_CAPITAL_JSON = Path("/tmp/dolphin_capital_checkpoint.json")
# Path relative to this file: Observability/TUI/ → ../../run_logs/
_TEST_JSON = Path(__file__).parent.parent.parent / "run_logs" / "test_results_latest.json"
_OBF_SYMS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]
_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 ─────────────────────────────────────────────────────────
class _State:
def __init__(self):
self._l = threading.Lock(); self._d: Dict[str, Any] = {}
def put(self, k, v):
with self._l: self._d[k] = v
def get(self, k, default=None):
with self._l: return self._d.get(k, default)
def update(self, m):
with self._l: self._d.update(m)
_S = _State()
def _ingest(key, raw):
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):
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)
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)
for map_name, key, state_key in [
("DOLPHIN_META_HEALTH", "latest", "meta_health"),
("DOLPHIN_SAFETY", "latest", "safety"),
("DOLPHIN_HEARTBEAT", "nautilus_flow_heartbeat", "heartbeat"),
]:
m = client.get_map(map_name).blocking()
_ingest(state_key, m.get(key))
def _cb(e, sk=state_key, ek=key):
if e.key == ek: _ingest(sk, e.value)
m.add_entry_listener(include_value=True, updated=_cb, added=_cb)
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)
try:
pm = client.get_map("DOLPHIN_PNL_BLUE").blocking()
_ingest("pnl_blue", pm.get("session_perf"))
def _pnl(e):
if e.key == "session_perf": _ingest("pnl_blue", e.value)
pm.add_entry_listener(include_value=True, updated=_pnl, added=_pnl)
except Exception: pass
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()
async def prefect_poll_loop():
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)
# ── Helpers ───────────────────────────────────────────────────────────────────
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 _age_col(ts, warn=15, dead=60):
s = time.time() - ts if ts else 9999
return "red" if s > dead else ("yellow" if s > warn else "green")
def _bar(v, width=12):
v = max(0.0, min(1.0, v))
f = round(v * width)
return "" * f + "" * (width - f)
def _fmt_vel(v):
return "---" if v is None else f"{float(v):+.5f}"
def _dot(state):
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):
c = _PC.get(p, "dim")
return f"[{c}]{p}[/{c}]"
def _col(v, c): return f"[{c}]{v}[/{c}]"
def _eigen_from_scan(scan):
if not scan: return {}
r = scan.get("result", scan)
mwr_raw = r.get("multi_window_results", {})
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", {})
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_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),
"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; }
#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; }
#mc_live { height: 8; }
#mc_spark_lbl { height: 1; }
#mc_spark { height: 2; }
#mc_mae_lbl { height: 1; }
#mc_mae_spark { height: 2; }
#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; }
"""
# ── 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; _mae_deque: deque
_session_start_cap: Optional[float] = None
_cap_peak: Optional[float] = None
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"):
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")
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._mae_deque = deque(maxlen=500)
start_hz_listener(on_scan=lambda: self.call_from_thread(self._update))
self.run_worker(prefect_poll_loop(), name="prefect-poll", exclusive=True)
self.set_interval(1.0, self._update)
self._update()
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)
mh = _S.get("hz.meta_health") or self._read_json(_META_JSON)
safe = _S.get("hz.safety") or {}
scan = _S.get("hz.latest_eigen_scan") or {}
exf = _S.get("hz.exf_latest") or {}
acb = _S.get("hz.acb_boost") or {}
obf_u = _S.get("hz.obf_universe_latest") or {}
mc = _S.get("hz.mc_forewarner_latest") or {}
cap = _S.get("hz.capital") or self._read_json(_CAPITAL_JSON) or {}
hb = _S.get("hz.heartbeat") or {}
eng = _S.get("hz.engine_snapshot") or {}
eigen = _eigen_from_scan(scan)
# ── posture: DOLPHIN_SAFETY first, fall back to engine_snapshot ───────
posture = safe.get("posture") or eng.get("posture") or "?"
# ── Rm / breakdown: DOLPHIN_SAFETY first, fall back to zeros ─────────
rm_s = float(safe.get("Rm", 0.0))
bd = safe.get("breakdown") or {}
# If DOLPHIN_SAFETY is empty but engine_snapshot has posture, show that
safety_live = bool(safe.get("posture") or safe.get("Rm"))
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")
pc_col = _PC.get(posture, "dim")
hz_tag = "[green][HZ✓][/green]" if hz_up else "[red][HZ✗][/red]"
mc_st = mc.get("status", "N/A") if mc else "N/A"
mc_col = _MC.get(mc_st, "dim")
# ── HEADER ────────────────────────────────────────────────────────────
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}]"
f" [dim]{TUI_VERSION}[/dim]\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)
btc_p = eigen.get("btc_price")
btc_str = f"BTC:[cyan]${btc_p:,.0f}[/cyan] " if btc_p else ""
trades_ex= eng.get("trades_executed")
last_vd = eng.get("last_vel_div")
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}\n"
f" vel_div:[{vc}]{vel_div:+.5f}[/{vc}] thr:[dim]-0.02000[/dim]"
f" cap:[cyan]${cap_val:,.0f}[/cyan]"
f" trades:{trades_ex if trades_ex is not None else ''}\n"
f" last_vel_div:[dim]{last_vd:+.5f}[/dim] open-pos:[dim]DOLPHIN_PNL_BLUE[/dim]"
if last_vd is not None else
f" vel_div:[{vc}]{vel_div:+.5f}[/{vc}] thr:[dim]-0.02000[/dim]"
f" cap:[cyan]${cap_val:,.0f}[/cyan]"
f" trades:{trades_ex if trades_ex is not None else ''}\n"
f" open-pos:[dim]DOLPHIN_PNL_BLUE (not yet wired)[/dim]"
)
# ── SYS HEALTH — FIX: expand tdr/scb labels ──────────────────────────
if mh:
svc = mh.get("service_status", {})
hz_ks= mh.get("hz_key_status", {})
def _svc(nm, label):
st = svc.get(nm, "?")
dot = "[green]●[/green]" if st == "RUNNING" else "[red]●[/red]"
return f"{dot}[dim]{label}[/dim]"
def _hz_dot(nm):
sc = hz_ks.get(nm, {}).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.get('m4_control_plane',0):.2f}"
f" m3:{mh.get('m3_data_freshness',0):.2f}"
f" m5:{mh.get('m5_coherence',0):.2f}\n"
f"{_svc('dolphin_data:exf_fetcher','exf')}"
f" {_svc('dolphin_data:acb_processor','acb')}"
f" {_svc('dolphin_data:obf_universe','obf')}\n"
f"{_svc('dolphin:nautilus_trader','trader')}"
f" {_svc('dolphin:scan_bridge','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 — FIX: fall back to engine_snapshot when safety empty ─
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}]"
if safety_live:
self._w("#p_alpha").update(
f"[bold]ALPHA ENGINE[/bold] {_posture_markup(posture)}\n"
f"Rm:[{pc_col}]{_bar(rm_s,14)}[/{pc_col}]{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]"
)
else:
# DOLPHIN_SAFETY empty — show what we have from engine_snapshot
bars_idx = eng.get("bar_idx", "?")
scans_p = eng.get("scans_processed", "?")
self._w("#p_alpha").update(
f"[bold]ALPHA ENGINE[/bold] {_posture_markup(posture)}\n"
f"[yellow]DOLPHIN_SAFETY empty[/yellow]\n"
f"posture from engine_snapshot\n"
f"bar:{bars_idx} scans:{scans_p}\n"
f"[dim]Rm/Cat1-5: awaiting DOLPHIN_SAFETY[/dim]"
)
# ── SCAN ──────────────────────────────────────────────────────────────
scan_ac = _age_col(eigen.get("timestamp", 0), 15, 60)
scan_age= _age(eigen.get("timestamp")) if eigen.get("timestamp") else "?"
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")
if exf:
self._w("#p_extf").update(
f"[bold]ExtF[/bold] [{exf_ac}]{ok_cnt}/9 {exf_age}[/{exf_ac}]\n"
f"fund:[cyan]{f_btc:.5f}[/cyan] dvol:[{dvol_c}]{dvol:.1f}[/{dvol_c}]\n"
f"fng:[{fng_c}]{int(fng) if fng else '?'}[/{fng_c}] taker:[yellow]{taker:.3f}[/yellow]\n"
f"ls_btc:{ls_btc:.3f} vix:{vix:.1f}\n"
f"acb✓:{exf.get('_acb_ready','?')}"
)
else:
self._w("#p_extf").update("[bold]ExtF[/bold]\n[dim]no data[/dim]")
# ── 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}] 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 "?"
c5 = bd.get("Cat5", 1.0) if bd else 1.0
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
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.get('date','?') if acb else '?'}[/{acb_ac}]\n"
f"boost:[{boost_c}]{boost:.2f}x[/{boost_c}] β={beta:.2f}"
f" cut:[{cut_c}]{cut:.2f}[/{cut_c}]\n"
f"w750_thr:{acb.get('w750_threshold',0.0) if acb else 0.0:.4f}\n"
f"[dim]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 — FIX: graceful when absent ─────────────────────────
prob = float(mc.get("catastrophic_prob", 0.0)) if mc else 0.0
env = float(mc.get("envelope_score", 0.0)) if mc else 0.0
champ_p = mc.get("champion_probability") if mc else None
mc_ts = mc.get("timestamp") if mc else None
mc_warns = mc.get("warnings", []) if mc else []
sc = _MC.get(mc_st, "dim")
self._prob_hist.append(prob)
# Age since last 4h run
mc_age_str = "never run"
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
mc_present = bool(mc)
self._w("#mc_title").update(
f"[bold cyan]⚡ MC-FOREWARNER RISK MANIFOLD[/bold cyan] {TUI_VERSION}"
+ (f" [{sc}]▶ {mc_st}[/{sc}] [dim]assessed:{mc_age_str} cadence:4h[/dim]"
if mc_present else
" [yellow]⚠ no data yet — Prefect 4h schedule not yet run[/yellow]"
" [dim](mc_forewarner_flow runs every 4h)[/dim]")
)
# Left: digits + status
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[dim]cat.prob {champ_str}[/dim]\n[dim]env:{env:.3f}[/dim]")
if mc_present else
"[yellow]awaiting[/yellow]\n[dim]first run[/dim]\n[dim]in ~4h[/dim]"
)
# Center bars
for bar_id, val, lo_thr, hi_thr, lo_cls, hi_cls, label, fmt in [
("mc_prob_bar", prob, 0.10, 0.30, "-warning", "-danger",
f"[dim]cat.prob[/dim] [{sc}]{prob:.4f}[/{sc}] [green]<0.10 OK[/green] [yellow]<0.30 WARN[/yellow] [red]≥0.30 CRIT[/red]",
int(prob * 100)),
("mc_env_bar", env, 0.40, 0.70, "-danger", "-warning",
f"[dim]env.score[/dim] [green]{env:.4f}[/green] [red]<0.40 DANGER[/red] [yellow]<0.70 CAUTION[/yellow] [green]≥0.70 SAFE[/green]",
int(env * 100)),
]:
pb = self._w(f"#{bar_id}", ProgressBar)
pb.progress = fmt
pb.remove_class("-danger", "-warning")
if val < lo_thr: pb.add_class(lo_cls)
elif val < hi_thr: pb.add_class(hi_cls)
self._w(f"#{bar_id.replace('_bar','_label')}").update(label)
# 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]"
)
# Live performance tier
cur_cap = float(cap.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
live_roi = ((cur_cap - self._session_start_cap) / self._session_start_cap
if cur_cap > 0 and self._session_start_cap else None)
live_dd = ((self._cap_peak - cur_cap) / self._cap_peak
if cur_cap > 0 and self._cap_peak and cur_cap < self._cap_peak else None)
pnl_blue = _S.get("hz.pnl_blue") or {}
def _pct(v): return f"{v*100:+.1f}%" if v is not None else ""
def _lm(k, fmt="{:.3f}"):
v = pnl_blue.get(k); return fmt.format(v) if v is not None else ""
roi_c = "green" if live_roi and live_roi > 0 else ("red" if live_roi and live_roi < -0.10 else "yellow")
dd_c2 = "red" if live_dd and live_dd > 0.20 else ("yellow" if live_dd and live_dd > 0.08 else "green")
self._w("#mc_live").update(
f"[dim]ROI[/dim] [{roi_c}]{_pct(live_roi):>8}[/{roi_c}]"
f" [dim]champ gate:>+30% crit:<-30%[/dim]\n"
f"[dim]DD [/dim] [{dd_c2}]{_pct(live_dd):>8}[/{dd_c2}]"
f" [dim]champ gate:<20% crit:>40%[/dim]\n"
f"[dim]WR:{_lm('win_rate','{:.1%}')} PF:{_lm('profit_factor','{:.2f}')}"
f" Sh:{_lm('sharpe','{:.2f}')} Cal:{_lm('calmar','{:.2f}')}[/dim]\n"
f"[dim]cap:${cur_cap:,.0f} start:${self._session_start_cap:,.0f} "
f"trades:{eng.get('trades_executed','')}[/dim]"
if cur_cap > 0 and self._session_start_cap else
"[dim]awaiting capital data…[/dim]"
)
# Right: sparklines + legend
self._w("#mc_spark_lbl").update(
f"[dim]cat.prob history [{min(self._prob_hist):.3f}{max(self._prob_hist):.3f}][/dim]")
self._w("#mc_spark", Sparkline).data = list(self._prob_hist)
mae_list = list(self._mae_deque)
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"
"[dim]DD gate: <20%[/dim]\n"
"[dim]DD crit: >40%[/dim]" + warn_str
)
# ── TEST FOOTER — schema: {passed, total, status: PASS|FAIL|N/A} ────────
if self._test_vis:
tr = self._read_json(_TEST_JSON) or {}
run_at = tr.get("_run_at", "never")
cats = [
("data_integrity", "data"),
("finance_fuzz", "fuzz"),
("signal_fill", "signal"),
("degradation", "degrad"),
("actor", "actor"),
]
def _badge(key, short):
info = tr.get(key, {})
if not info:
return f"[dim]{short}:n/a[/dim]"
status = info.get("status", "N/A")
passed = info.get("passed")
total = info.get("total")
if status == "N/A" or passed is None:
return f"[dim]{short}:N/A[/dim]"
col = "green" if status == "PASS" else "red"
return f"[{col}]{short}:{passed}/{total}[/{col}]"
badges = " ".join(_badge(k, s) for k, s in cats)
self._w("#test_footer").update(
f"[bold dim]TESTS[/bold dim] [dim]last run: {run_at}[/dim]"
f" [dim]t=toggle r=reload[/dim]\n"
f"{badges}\n"
f"[dim]file: run_logs/test_results_latest.json "
f"API: write_test_results() in dolphin_tui_v5.py[/dim]"
)
else:
self._w("#test_footer").update("")
# ── LOG ───────────────────────────────────────────────────────────────
if self._log_vis:
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:{_S.get('hz_err','none')} pf_err:{_S.get('prefect_err','none')}\n"
f"[dim]state keys:{len(_S._d)} safety_live:{safety_live}[/dim]"
)
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()
def _w(self, selector, widget_type=Static):
return self.query_one(selector, widget_type)
@staticmethod
def _read_json(path):
try: return json.loads(path.read_text())
except Exception: return None
def write_test_results(results: dict):
"""
Update the TUI test footer. Called by test scripts / CI / conftest.py.
Schema:
{
"_run_at": "auto-injected",
"data_integrity": {"passed": 15, "total": 15, "status": "PASS"},
"finance_fuzz": {"passed": null, "total": null, "status": "N/A"},
...
}
status: "PASS" | "FAIL" | "N/A"
Example (conftest.py):
import sys; sys.path.insert(0, "/mnt/dolphinng5_predict/Observability/TUI")
from dolphin_tui_v5 import write_test_results
write_test_results({"data_integrity": {"passed": 15, "total": 15, "status": "PASS"}})
"""
_TEST_JSON.parent.mkdir(parents=True, exist_ok=True)
# Merge with existing file so missing categories are preserved
existing = {}
try:
existing = json.loads(_TEST_JSON.read_text())
except Exception:
pass
existing.update(results)
existing["_run_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
_TEST_JSON.write_text(json.dumps(existing, indent=2))
if __name__ == "__main__":
DolphinTUI().run()