Files
DOLPHIN/Observability/dolphin_status_v4.py

482 lines
18 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""DOLPHIN live status — v4
Polls HZ every 1s. Three gear rows: SIG / TRD / FIL (signalfill path).
v4: vol_ok is THE master gate shown prominently; live BTC vol vs threshold.
Run: source /home/dolphin/siloqy_env/bin/activate && python dolphin_status.py
Quit: Ctrl-C
"""
# v1 archived as dolphin_status_v1.py
# v2 archived inline (added SIG+TRD rows)
# v3 archived as dolphin_status_v3.py (added FIL row)
# v4: vol_ok promoted to master-gate prominence; BTC vol readout
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"
ORANGE = "\033[38;5;208m"
PC = {"APEX": GREEN, "STALKER": YELLOW, "TURTLE": ORANGE, "HIBERNATE": RED}
SC = {"GREEN": GREEN, "DEGRADED": YELLOW, "CRITICAL": ORANGE, "DEAD": RED}
# Thresholds from nautilus_event_trader.py
VEL_DIV_THRESHOLD = -0.020 # signal fires when vel_div < this
VEL_DIV_EXTREME = -0.050 # extreme bearish
VEL_DIV_WARN = -0.010 # approaching threshold (yellow)
VEL_DIV_CLOSE = -0.015 # nearly there (orange→yellow)
VOL_P60 = 0.00026414 # BTC 50-bar realised vol p60 — MASTER GATE
BTC_VOL_WINDOW = 50 # bars used for vol calc
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))
return "" * round(v * w) + "" * (w - round(v * w))
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 {}
# ── Gear items ────────────────────────────────────────────────────────────────
# Each returns (label, color, value_str)
def _item(label, color, val=""):
dot = f"{color}{RST}"
v = f":{val}" if val else ""
return f"{dot}{DIM}{label}{v}{RST}"
def _vel_item(vel_div):
"""vel_div colored by distance to threshold (-0.02)."""
v = f"{vel_div:+.4f}"
if vel_div <= VEL_DIV_EXTREME:
return _item("vel_div", GREEN, v) # extremely bearish — great
elif vel_div <= VEL_DIV_THRESHOLD:
return _item("vel_div", GREEN, v) # past threshold — signal green
elif vel_div <= VEL_DIV_CLOSE:
return _item("vel_div", YELLOW, v) # -0.015 to -0.020 — close
elif vel_div <= VEL_DIV_WARN:
return _item("vel_div", ORANGE, v) # -0.010 to -0.015 — approaching
elif vel_div < 0:
return _item("vel_div", RED, v) # negative but far
else:
return _item("vel_div", RED, v) # positive — not bearish
def signal_fired(vel_div, vol_ok, posture, acb_ready, exf_ok, halt):
"""True if ALL signal preconditions are green."""
return (
vel_div <= VEL_DIV_THRESHOLD
and vol_ok
and posture not in ("HIBERNATE", "TURTLE")
and acb_ready
and exf_ok
and not halt
)
def trade_can_execute(open_count, lev, abs_cap, daily_loss_ok, boost):
return (
open_count == 0 # no open position already
and lev < abs_cap # leverage headroom
and daily_loss_ok
and boost > 0
)
OB_IMBALANCE_BIAS = -0.09 # from engine config: ob_imbalance_bias
def _best_fill_candidate(obf_universe):
"""Pick best SHORT candidate from OBF universe.
Criteria: negative imbalance (bearish pressure) + high fill_probability + low spread.
Returns (symbol, asset_dict) or (None, {}).
"""
candidates = []
for k, v in obf_universe.items():
if not isinstance(v, dict) or "fill_probability" not in v:
continue
candidates.append((k, v))
if not candidates:
return None, {}
# Score: fill_prob * (1 + bearish_imbalance_bonus) / (1 + spread_bps/10)
def score(item):
sym, a = item
imb = float(a.get("imbalance", 0))
fp = float(a.get("fill_probability", 0))
sp = float(a.get("spread_bps", 99))
dq = float(a.get("depth_quality", 0))
# Bearish bias: reward negative imbalance, penalise positive
imb_bonus = max(0.0, -imb) # 0..1 for imbalance in [-1,0]
return fp * (1 + imb_bonus) * dq / max(0.1, sp)
candidates.sort(key=score, reverse=True)
return candidates[0]
def fill_row(obf_universe, acb, eng):
"""Row 3: signal → asset-pick → OBF liquidity → size → ORDER."""
f_items = []
# ── Asset picker (IRP/ARS) ─────────────────────────────────────────────
n_assets = int(obf_universe.get("_n_assets", 0) if obf_universe else 0)
n_stale = int(obf_universe.get("_n_stale", 0) if obf_universe else 0)
n_fresh = n_assets - n_stale
f_items.append(_item("universe",
GREEN if n_fresh >= 200 else (YELLOW if n_fresh >= 50 else RED),
f"{n_fresh}/{n_assets}"))
sym, ab = _best_fill_candidate(obf_universe)
if sym:
fill_p = float(ab.get("fill_probability", 0))
spread = float(ab.get("spread_bps", 99))
dq = float(ab.get("depth_quality", 0))
imb = float(ab.get("imbalance", 0))
depth = float(ab.get("depth_1pct_usd", 0))
# Best candidate asset
asset_color = GREEN if fill_p >= 0.80 else (YELLOW if fill_p >= 0.50 else RED)
f_items.append(_item("best", asset_color, sym[:6]))
# OBF: fill probability
f_items.append(_item("fill_p",
GREEN if fill_p >= 0.85 else (YELLOW if fill_p >= 0.60 else RED),
f"{fill_p:.2f}"))
# OBF: spread
f_items.append(_item("spread",
GREEN if spread <= 3 else (YELLOW if spread <= 8 else RED),
f"{spread:.1f}bps"))
# OBF: depth quality
f_items.append(_item("depth_q",
GREEN if dq >= 0.5 else (YELLOW if dq >= 0.1 else RED),
f"{dq:.2f}"))
# OBF: imbalance direction (SHORT needs bearish = negative)
imb_ok = imb < OB_IMBALANCE_BIAS # confirmed bearish pressure
f_items.append(_item("imb",
GREEN if imb_ok else
YELLOW if imb < 0 else
ORANGE if imb < 0.1 else RED,
f"{imb:+.2f}"))
# OBF: depth USD
f_items.append(_item("depth",
GREEN if depth >= 50_000 else (YELLOW if depth >= 10_000 else RED),
f"${depth/1000:.0f}k"))
else:
f_items.append(_item("OBF", RED, "no data"))
# ── Sizing — ACB boost × proxy_B prank ────────────────────────────────
# proxy_B prank not exposed in HZ snapshot; show ACB boost as sizing proxy
boost = float(acb.get("boost", 1.0) if acb else 1.0)
beta = float(acb.get("beta", 0.8) if acb else 0.8)
f_items.append(_item("acb_boost",
GREEN if boost >= 1.5 else (YELLOW if boost >= 1.0 else ORANGE),
f"×{boost:.2f}"))
f_items.append(_item("beta",
GREEN if beta >= 0.7 else (YELLOW if beta >= 0.4 else RED),
f"{beta:.2f}"))
# ── ORDER indicator ────────────────────────────────────────────────────
# Would an order fire if signal were green right now?
open_count = len(eng.get("open_positions") or [])
lev = float(eng.get("current_leverage", 0) or 0)
abs_c = float(eng.get("leverage_abs_cap", 9.0) or 9.0)
order_ready = (
sym is not None
and fill_p >= 0.60
and open_count == 0
and lev < abs_c
and boost > 0
) if sym else False
if order_ready:
f_items.append(f" {CYAN}{BOLD}◉ ORDER READY{RST}")
else:
f_items.append(f" {DIM}(order: waiting){RST}")
return " ".join(f_items)
def gear_rows(eng, safe, acb, exf, hb, obf_universe=None):
"""Return three formatted rows: SIGNAL, TRADE gates, FILL path."""
vel_div = float(eng.get("last_vel_div", 0) or 0)
vol_ok = bool(eng.get("vol_ok", False))
posture = safe.get("posture") or eng.get("posture") or "?"
halt = posture in ("HIBERNATE", "TURTLE")
acb_boost_val = float(acb.get("boost", acb.get("cut", 0)) or 0)
acb_ready = acb_boost_val > 0 # cut=0 means blocked
exf_ok_count = int(exf.get("_ok_count", 0) if exf else 0)
exf_ok = exf_ok_count >= 3
open_count = len(eng.get("open_positions") or [])
lev = float(eng.get("current_leverage", 0) or 0)
abs_cap = float(eng.get("leverage_abs_cap", 9.0) or 9.0)
trades_ex = int(eng.get("trades_executed") or 0)
hb_ts = hb.get("ts")
hb_ok = bool(hb_ts and (time.time() - hb_ts) < 30)
# ── SIGNAL ROW ────────────────────────────────────────────────────────────
# vol_ok is the MASTER GATE — listed first. When False, _try_entry is never
# called regardless of vel_div. BTC 50-bar realised vol must exceed p60=0.000264.
s_items = []
# BTC vol — try to get live reading from exf or obf for display context
btc_vol_str = ""
if exf:
dvol_raw = exf.get("dvol_btc") or exf.get("dvol")
fng_raw = exf.get("fng")
if dvol_raw:
btc_vol_str = f"dV:{float(dvol_raw):.0f}"
if fng_raw:
btc_vol_str += f" FnG:{float(fng_raw):.0f}"
vol_label = f"vol_ok({btc_vol_str})"
s_items.append(_item(vol_label,
GREEN if vol_ok else RED,
"" if vol_ok else f"✗ BLOCKED"))
s_items.append(_vel_item(vel_div))
# posture gate
pc = PC.get(posture, DIM)
posture_ok = posture in ("APEX", "STALKER")
s_items.append(_item("posture",
GREEN if posture == "APEX" else (YELLOW if posture == "STALKER" else RED),
posture))
# acb_ready
s_items.append(_item("acb",
GREEN if acb_ready else (ORANGE if acb_boost_val > 0 else RED),
f"{acb_boost_val:.2f}"))
# exf_ok — external factors pipeline
s_items.append(_item("exf",
GREEN if exf_ok else (YELLOW if exf_ok_count >= 1 else RED),
f"{exf_ok_count}/5"))
# halt gate
s_items.append(_item("no_halt",
GREEN if not halt else RED,
"" if not halt else "HALT"))
# heartbeat
s_items.append(_item("hb",
GREEN if hb_ok else RED,
_age(hb_ts)))
# ALL GREEN → fire indicator
all_sig = signal_fired(vel_div, vol_ok, posture, acb_ready, exf_ok, halt)
if all_sig:
s_items.append(f" {GREEN}{BOLD}◉ SIGNAL{RST}")
# ── TRADE ROW ─────────────────────────────────────────────────────────────
# Additional gates that must pass before a matched signal becomes a fill
t_items = []
# open positions
t_items.append(_item("open_pos",
GREEN if open_count == 0 else ORANGE,
str(open_count)))
# leverage headroom
lev_pct = lev / abs_cap if abs_cap else 0
t_items.append(_item("lev",
GREEN if lev_pct < 0.3 else (YELLOW if lev_pct < 0.7 else RED),
f"{lev:.2f}x/{abs_cap:.0f}"))
# regime_dd_halt
t_items.append(_item("regime",
GREEN if not halt else RED,
"free" if not halt else "HALTED"))
# Rm strength
rm = float(safe.get("Rm", 0) or 0)
t_items.append(_item("Rm",
GREEN if rm >= 0.90 else (YELLOW if rm >= 0.70 else (ORANGE if rm >= 0.50 else RED)),
f"{rm:.3f}"))
# Cat5 (intraday drawdown contribution)
c5 = float((safe.get("breakdown") or {}).get("Cat5", 1.0) or 1.0)
t_items.append(_item("Cat5",
GREEN if c5 >= 0.95 else (YELLOW if c5 >= 0.85 else (ORANGE if c5 >= 0.70 else RED)),
f"{c5:.3f}"))
# trades today
t_items.append(_item("trades",
GREEN if trades_ex < 20 else (YELLOW if trades_ex < 35 else ORANGE),
str(trades_ex)))
# ALL GREEN trade execute indicator
daily_loss_ok = c5 > 0.50 # reasonable proxy — Cat5 tracks drawdown
all_trade = all_sig and trade_can_execute(open_count, lev, abs_cap, daily_loss_ok, acb_boost_val)
if all_trade:
t_items.append(f" {CYAN}{BOLD}◉ TRADE{RST}")
sig_row = " ".join(s_items)
trade_row = " ".join(t_items)
fill = fill_row(obf_universe or {}, acb, eng)
return sig_row, trade_row, fill
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")
exf = _get(hz, "DOLPHIN_FEATURES", "exf_latest")
obf = _get(hz, "DOLPHIN_FEATURES", "obf_universe_latest")
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))
hb_ts = hb.get("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))
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)
sig_row, trade_row, fill_row_str = gear_rows(eng, safe, acb, exf, hb, obf)
svc = mh.get("service_status", {})
hz_ks = mh.get("hz_key_status", {})
L = []
L.append(f"{BOLD}{CYAN}🐬 DOLPHIN-NAUTILUS{RST} {DIM}{now}{RST}")
L.append("" * 60)
# TRADER
L.append(f"{BOLD}TRADER{RST} {tc}{'● LIVE' if trader_up else '● DOWN'}{RST}"
f" phase:{phase} hb:{_age(hb_ts)}"
f" scan:#{eng.get('last_scan_number','?')}")
# ── SIGNAL → FILL GEARS ───────────────────────────────────────────────────
vol_ok_live = bool(eng.get("vol_ok", False))
if not vol_ok_live:
L.append(f" {RED}{BOLD}⛔ VOL_OK=FALSE — engine gate closed, NO trades until BTC vol > {VOL_P60:.6f}{RST}")
L.append(f" {DIM}SIG │{RST} {sig_row}")
L.append(f" {DIM}TRD │{RST} {trade_row}")
L.append(f" {DIM}FIL │{RST} {fill_row_str}")
L.append("")
# CAPITAL
L.append(f"{BOLD}CAPITAL{RST} {CYAN}${capital:,.2f}{RST}"
+ (f" ROI:{roi_c}{roi:+.2f}%{RST} DD:{dd_c}{dd:.2f}%{RST}"
f" start:${START_CAP:,.0f}" if START_CAP else ""))
L.append(f" trades:{trades} scans:{scans} bar:{eng.get('bar_idx','?')}"
f" lev:{lev:.2f}x notional:${notional:,.0f}")
# Open positions
positions = eng.get("open_positions") or []
if positions:
L.append(f" {BOLD}OPEN:{RST}")
for p in positions:
sc2 = GREEN if p.get("side") == "LONG" else RED
L.append(f" {sc2}{p.get('asset','?')} {p.get('side','?')}{RST}"
f" qty:{p.get('quantity',0):.4f}"
f" entry:{p.get('entry_price',0):.2f}"
f" upnl:{p.get('unrealized_pnl',0):+.2f}")
else:
L.append(f" {DIM}no open positions{RST}")
L.append("")
# POSTURE
bd = safe.get("breakdown") or {}
L.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))
L.append(f" {cats} f_env:{float(bd.get('f_env',0)):.3f} f_exe:{float(bd.get('f_exe',0)):.3f}")
L.append("")
# SYS HEALTH
L.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)
L.append(f" {c}{m}:{v:.3f}{RST}")
L.append(f" {DIM}services:{RST} "
+ " ".join(
f"{'' if st=='RUNNING' else f'{RED}{RST}'}{DIM}{n.split(':')[-1]}{RST}"
if st == "RUNNING" else
f"{RED}{DIM}{n.split(':')[-1]}{RST}"
for n, st in sorted(svc.items())))
L.append(f" {DIM}hz_keys:{RST} "
+ " ".join(
f"{GREEN if float(i.get('score',0))>=0.9 else (YELLOW if float(i.get('score',0))>=0.5 else RED)}{RST}{DIM}{k}{RST}"
for k, i in sorted(hz_ks.items())))
L.append("")
L.append(f"{DIM}v4 • 1s poll • vol_ok is master gate • Ctrl-C quit{RST}")
return "\n".join(L)
def main():
print("Connecting to HZ...")
hz = hazelcast.HazelcastClient(
cluster_name="dolphin", cluster_members=["localhost:5701"],
connection_timeout=5.0)
print("Connected.\n")
try:
while True:
try:
sys.stdout.write(CLEAR + render(hz) + "\n")
sys.stdout.flush()
except Exception as e:
sys.stdout.write(f"\n{RED}render error: {e}{RST}\n")
sys.stdout.flush()
time.sleep(1)
except KeyboardInterrupt:
print(f"\n{DIM}Bye.{RST}")
finally:
hz.shutdown()
if __name__ == "__main__":
main()