Files
DOLPHIN/Observability/TUI/dolphin_tui_v2.py

373 lines
17 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
DOLPHIN TUI v2 full layout, mock data, sexy MC-Forewarner footer.
Run: python3 dolphin_tui_v2.py
q=quit r=refresh l=toggle log
"""
import time
import math
from collections import deque
from textual.app import App, ComposeResult
from textual.widgets import Static, ProgressBar, Sparkline, Digits, Rule
from textual.containers import Horizontal, Vertical
# ── CSS ───────────────────────────────────────────────────────────────────────
CSS = """
Screen { background: #0d0d0d; color: #d0d0d0; }
#header { height: 2; background: #111; border: 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 Footer */
#mc_footer_outer {
height: 7;
border: solid #336;
background: #080818;
}
#mc_title { height: 1; padding: 0 1; }
#mc_body { height: 6; }
#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_spark_label { height: 1; }
#mc_sparkline { height: 2; }
#mc_digits { height: 3; }
#mc_status_text { height: 2; }
ProgressBar > .bar--bar { color: $success; }
ProgressBar > .bar--complete { color: $success; }
ProgressBar.-danger > .bar--bar { color: $error; }
ProgressBar.-warning > .bar--bar { color: $warning; }
Static.panel {
border: solid #3a3a3a;
padding: 0 1;
height: 100%;
}
#panel_trader { width: 1fr; border: solid #00aa88; }
#panel_health { width: 1fr; }
#panel_alpha { width: 1fr; }
#panel_scan { width: 1fr; }
#panel_extf { width: 1fr; }
#panel_esof { width: 1fr; }
#panel_capital { width: 1fr; }
#panel_prefect { width: 1fr; }
#panel_obf { width: 1fr; }
#panel_log { width: 1fr; border: solid #444; padding: 0 1; }
"""
# ── Helpers ───────────────────────────────────────────────────────────────────
def prefect_dot(status: str, blink_frame: bool) -> str:
s = status.upper()
if s == "COMPLETED": return "[green]●[/green]"
if s == "RUNNING": return "[cyan]◉[/cyan]" if blink_frame else "[dim]◌[/dim]"
if s in ("FAILED", "CRASHED"): return "[red]●[/red]"
if s == "LATE": return "[dark_orange]●[/dark_orange]"
if s == "PENDING": return "[yellow]●[/yellow]"
return "[dim]●[/dim]"
MOCK_FLOWS = [
("paper_trade_flow", "COMPLETED", "2m"),
("nautilus_prefect", "COMPLETED", "8m"),
("obf_prefect_flow", "RUNNING", "0m"),
("exf_fetcher_flow", "COMPLETED", "15m"),
("mc_forewarner_flow", "RUNNING", "3m"),
]
MOCK_POSITIONS = [
("BTCUSDT", "SHORT", 0.01, 83420.5, 83278.2),
("ETHUSDT", "SHORT", 0.10, 1612.3, 1598.7),
]
def mock_open_positions(n: int) -> list:
phase = (n // 20) % 3
if phase == 0: return []
if phase == 1:
p = MOCK_POSITIONS[0]
return [(p[0], p[1], p[2], p[3], p[4] - (n % 10) * 2.1)]
return [(p[0], p[1], p[2], p[3], p[4] - (n % 10) * 1.5) for p in MOCK_POSITIONS]
def mc_mock(n: int) -> dict:
"""Real schema: DOLPHIN_FEATURES['mc_forewarner_latest']
Thresholds: GREEN<0.10 ORANGE<0.30 RED>=0.30"""
t = n * 0.05
prob = max(0.0, min(1.0, 0.12 + 0.10 * math.sin(t)))
env = max(0.0, min(1.0, 0.82 - 0.08 * abs(math.sin(t * 1.3))))
status = "GREEN" if prob < 0.10 else ("ORANGE" if prob < 0.30 else "RED")
return {"status": status, "catastrophic_prob": prob,
"envelope_score": env, "source": "MOCK",
"timestamp": time.strftime("%H:%M:%SZ", time.gmtime())}
# ── App ───────────────────────────────────────────────────────────────────────
class DolphinTUI(App):
CSS = CSS
BINDINGS = [("q","quit","Quit"),("r","refresh","Refresh"),("l","toggle_log","Log")]
_log_visible = False
_tick_n = 0
_prob_history: deque = None
def compose(self) -> ComposeResult:
yield Static("", id="header")
with Horizontal(id="trader_row"):
yield Static("", classes="panel", id="panel_trader")
with Horizontal(id="top_row"):
yield Static("", classes="panel", id="panel_health")
yield Static("", classes="panel", id="panel_alpha")
yield Static("", classes="panel", id="panel_scan")
with Horizontal(id="mid_row"):
yield Static("", classes="panel", id="panel_extf")
yield Static("", classes="panel", id="panel_esof")
yield Static("", classes="panel", id="panel_capital")
with Horizontal(id="bot_row"):
yield Static("", classes="panel", id="panel_prefect")
yield Static("", classes="panel", id="panel_obf")
# ── MC-Forewarner footer ──────────────────────────────────────────────
with Vertical(id="mc_footer_outer"):
yield Static("", id="mc_title")
with Horizontal(id="mc_body"):
# Left: big probability digits
with Vertical(id="mc_left"):
yield Digits("0.00", id="mc_digits")
yield Static("", id="mc_status_text")
# Center: progress bars + sparkline
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_spark_label")
yield Sparkline([], id="mc_sparkline")
# Right: threshold legend + source
with Vertical(id="mc_right"):
yield Static("", id="mc_legend")
with Horizontal(id="log_row"):
yield Static("", classes="panel", id="panel_log")
def on_mount(self) -> None:
self._prob_history = deque([0.12] * 40, maxlen=40)
self.set_interval(1, self._update)
self._update()
def _update(self) -> None:
n = self._tick_n
self._tick_n += 1
blink = (n % 2 == 0)
t = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime())
cap = 124532.10 + n * 0.5
pnl = 1240.50 + n * 0.1
rm = 0.82 + (n % 10) * 0.01
vel = -0.031 - (n % 5) * 0.002
scan = 59000 + n
age = (n % 5) + 0.1
age_col = "green" if age < 15 else "yellow"
mc = mc_mock(n)
# ── HEADER ────────────────────────────────────────────────────────────
hz = "[green][HZ ✓][/green]"
sc = {"GREEN":"green","ORANGE":"dark_orange","RED":"red"}.get(mc["status"],"dim")
self.query_one("#header", Static).update(
f"[bold cyan]🐬 DOLPHIN-NAUTILUS[/bold cyan] v2.0 │ {t}"
f" │ [green]● GREEN[/green] {hz}"
f" │ MC:[{sc}]{mc['status']}[/{sc}]\n"
f"[dim] localhost:5701 │ q=quit r=refresh l=log[/dim]"
)
# ── TRADER ────────────────────────────────────────────────────────────
positions = mock_open_positions(n)
pos_lines = " ".join(
f"[cyan]{sym}[/cyan] [yellow]{side}[/yellow] {qty}"
f"@[dim]{entry:,.0f}[/dim]→[green]{cur:,.0f}[/green]"
f"([green]+${abs((entry-cur)*qty):,.1f}[/green])"
for sym, side, qty, entry, cur in positions
) if positions else "[dim]NONE[/dim]"
vol_ok = "[green]YES[/green]" if (n % 8) < 6 else "[yellow]NO[/yellow]"
self.query_one("#panel_trader", Static).update(
f"[bold cyan]NAUTILUS-DOLPHIN TRADER[/bold cyan]"
f" posture:[green]APEX[/green] bar:{scan} vol:{vol_ok}"
f" trades:[cyan]{12+n//30}[/cyan] cap:[cyan]${cap:,.0f}[/cyan]\n"
f" open: {pos_lines}\n"
f" vel:[yellow]{vel:.5f}[/yellow] thr:-0.02000 pnl:[green]+${pnl:,.2f}[/green]"
)
# ── SYSTEM HEALTH ─────────────────────────────────────────────────────
self.query_one("#panel_health", Static).update(
f"[bold]SYS HEALTH[/bold]\n"
f"rm_meta:[green]{rm:.3f}[/green]\n"
f"M1:[green]1.0[/green] M2:[green]1.0[/green] M3:[green]1.0[/green]\n"
f"M4:[green]1.0[/green] M5:[green]1.0[/green]\n"
f"[green]● GREEN[/green]"
)
# ── ALPHA ENGINE ──────────────────────────────────────────────────────
filled = int(rm * 16)
bar = "" * filled + "" * (16 - filled)
self.query_one("#panel_alpha", Static).update(
f"[bold]ALPHA ENGINE[/bold]\n"
f"Posture:[green]APEX ●[/green]\n"
f"Rm:[green]{bar}[/green]{rm:.2f}\n"
f"ACB:1.55x β=0.80\n"
f"C1:[green].9[/green] C2:[green].8[/green] C3:[yellow].7[/yellow]"
f" C4:[green]1.[/green] C5:[green].9[/green]"
)
# ── SCAN BRIDGE ───────────────────────────────────────────────────────
self.query_one("#panel_scan", Static).update(
f"[bold]SCAN / NG7[/bold]\n"
f"#{scan} age:[{age_col}]{age:.1f}s[/{age_col}]\n"
f"vel_div:[{age_col}]{vel:.4f}[/{age_col}]\n"
f"w50:-0.0421 w750:-0.0109\n"
f"inst:0.0234"
)
# ── ExtF ──────────────────────────────────────────────────────────────
self.query_one("#panel_extf", Static).update(
f"[bold]ExtF[/bold] [green]9/9 ✓[/green]\n"
f"fund:[cyan]-0.012[/cyan] dvol:[cyan]62.4[/cyan]\n"
f"fng:[yellow]28[/yellow] taker:0.81\n"
f"vix:18.2 ls:0.48\n"
f"age:[green]4.2s[/green]"
)
# ── EsoF ──────────────────────────────────────────────────────────────
self.query_one("#panel_esof", Static).update(
f"[bold]EsoF[/bold]\n"
f"Moon: Waxing Gibbous\n"
f"Merc:[green]Normal[/green]\n"
f"Sess:London MC:0.42\n"
f"age:[green]3.8s[/green]"
)
# ── CAPITAL ───────────────────────────────────────────────────────────
self.query_one("#panel_capital", Static).update(
f"[bold]CAPITAL[/bold]\n"
f"Cap:[cyan]${cap:,.0f}[/cyan]\n"
f"DD:[yellow]-3.21%[/yellow]\n"
f"PnL:[green]+${pnl:,.2f}[/green]\n"
f"Pos:[green]APEX[/green] T:{12+n//30}"
)
# ── PREFECT ───────────────────────────────────────────────────────────
flow_lines = "\n".join(
f"{prefect_dot(st, blink)} {name:<22} {dur}"
for name, st, dur in MOCK_FLOWS
)
self.query_one("#panel_prefect", Static).update(
f"[bold]PREFECT[/bold] [green]✓[/green]\n{flow_lines}"
)
# ── OBF ───────────────────────────────────────────────────────────────
self.query_one("#panel_obf", Static).update(
f"[bold]OBF TOP[/bold]\n"
f"BTC [green]+0.18[/green] fp:0.72\n"
f"ETH [green]+0.12[/green] fp:0.68\n"
f"SOL [green]+0.09[/green] fp:0.61\n"
f"BNB [red]-0.05[/red] fp:0.51"
)
# ── MC-FOREWARNER FOOTER (sexy) ───────────────────────────────────────
prob = mc["catastrophic_prob"]
env = mc["envelope_score"]
self._prob_history.append(prob)
# Title bar
self.query_one("#mc_title", Static).update(
f"[bold cyan]⚡ MC-FOREWARNER RISK MANIFOLD[/bold cyan]"
f" [{sc}]▶ {mc['status']}[/{sc}]"
f" [dim]src:{mc['source']} {mc['timestamp']}[/dim]"
)
# Left: Digits widget showing probability as large text
self.query_one("#mc_digits", Digits).update(f"{prob:.3f}")
status_emoji = {"GREEN": "🟢 SAFE", "ORANGE": "🟡 CAUTION", "RED": "🔴 DANGER"}.get(mc["status"], "")
self.query_one("#mc_status_text", Static).update(
f"[{sc}]{status_emoji}[/{sc}]\n[dim]cat.prob[/dim]"
)
# Center: ProgressBar for catastrophic_prob (danger = high value)
prob_pct = int(prob * 100)
prob_bar = self.query_one("#mc_prob_bar", ProgressBar)
prob_bar.progress = prob_pct
# Apply danger CSS class based on threshold
prob_bar.remove_class("-danger", "-warning")
if prob >= 0.30: prob_bar.add_class("-danger")
elif prob >= 0.10: prob_bar.add_class("-warning")
self.query_one("#mc_prob_label", Static).update(
f"[dim]catastrophic_prob[/dim] "
f"[green]▏GREEN<0.10[/green] [yellow]▏ORANGE<0.30[/yellow] [red]▏RED≥0.30[/red]"
f" [{sc}]{prob:.4f}[/{sc}]"
)
# ProgressBar for envelope_score (safe = high value, so invert display)
env_pct = int(env * 100)
env_bar = self.query_one("#mc_env_bar", ProgressBar)
env_bar.progress = env_pct
env_bar.remove_class("-danger", "-warning")
if env < 0.40: env_bar.add_class("-danger")
elif env < 0.70: env_bar.add_class("-warning")
self.query_one("#mc_env_label", Static).update(
f"[dim]envelope_score [/dim]"
f"[red]▏DANGER<0.40[/red] [yellow]▏CAUTION<0.70[/yellow] [green]▏SAFE≥0.70[/green]"
f" [green]{env:.4f}[/green]"
)
# Sparkline: rolling 40-sample history of catastrophic_prob
self.query_one("#mc_spark_label", Static).update(
f"[dim]prob history (40s)[/dim] "
f"[dim]min:{min(self._prob_history):.3f} "
f"max:{max(self._prob_history):.3f}[/dim]"
)
self.query_one("#mc_sparkline", Sparkline).data = list(self._prob_history)
# Right: threshold legend
self.query_one("#mc_legend", Static).update(
f"[bold]THRESHOLDS[/bold]\n"
f"[green]GREEN[/green] prob < 0.10\n"
f"[yellow]ORANGE[/yellow] prob < 0.30\n"
f"[red]RED[/red] prob ≥ 0.30\n"
f"\n"
f"[dim]runs every 4h[/dim]\n"
f"[dim]model: DolphinForewarner[/dim]"
)
# ── LOG ───────────────────────────────────────────────────────────────
if self._log_visible:
self.query_one("#panel_log", Static).update(
f"[bold]LOG[/bold] (l=hide)\n"
f"[dim]{t}[/dim] [INFO] RM_META=0.923 GREEN\n"
f"[dim]{t}[/dim] [INFO] SCAN #{scan} vel={vel:.4f}\n"
f"[dim]{t}[/dim] [INFO] MC {mc['status']} prob={prob:.4f}"
)
def action_refresh(self) -> None:
self._update()
def action_toggle_log(self) -> None:
self._log_visible = not self._log_visible
self.query_one("#log_row").display = self._log_visible
if __name__ == "__main__":
DolphinTUI().run()