Files
ftdt-quant-lab/live/monitor_service.py
T
ramseshk 140b0cc360 feat: full-information live terminal — all 10 modules with coefficients
live/monitor_service.py — rewritten state() includes all 10:
  - Hawkes: 3x3 alpha excitation matrix, mu baseline per type, beta decay
  - Dealer GEX: net GEX (), pin levels, direction signal
  - Tick Regime: current/next tick size, boundary price, bars-to-cross
  - Triangular Arb: venue count, opportunities, best spread bps
  - Sequencer: stale-state detection, P50/P99 latency, event count
  - Full: prices, microstructure, HLP, whipsaw, liq, term, spoof

live.html — complete rewrite: Bloomberg-terminal 4-column grid
  - Panel 1: Prices (mid, mark, oracle, premium, spread, funding)
  - Panel 2: Microstructure (OBI, best bid/ask, toxicity bar, branching ratio, sequencer latency P50/P99)
  - Panel 3: Composite Signals (HLP, Whipsaw w/countdown, Term Structure w/z-score, GEX w/signal, Tick Regime, Spoof probability bar)
  - Panel 4: Dealer GEX (net GEX bar, pin levels, direction)
  - Full-width: HLP Vault (total delta, assets, toxicity, per-coin signals)
  - Wide: Hawkes Coefficients (3x3 alpha matrix, mu per type, beta)
  - Liq Waterfall + Tri Arb + Sequencer + Tick Regime
  - Term Structure + Funding Whipsaw with countdown
2026-08-10 10:54:54 +08:00

187 lines
7.3 KiB
Python

"""
Live monitoring service — background thread running all 10 advanced monitors.
Provides unified state() for the live dashboard API.
"""
from __future__ import annotations
import logging, threading, time
from typing import Optional
import requests
from live.monitors.hlp_vault import HlpVaultMonitor
from live.monitors.term_structure import TermStructureMonitor
from live.monitors.liq_waterfall import LiquidationWaterfall
from live.monitors.sequencer_latency import SequencerLatencyDetector
from live.monitors.triangular_arb import TriangularLatencyArb
from live.strategies.funding_whipsaw import FundingWhipsawTrader
from microstructure.spoof_detector import SpoofDetector
from microstructure.hawkes import HawkesCalibrator
from microstructure.dealer_gex import DealerGEX
from microstructure.tick_regime import TickRegimeMonitor
from microstructure.book import order_book_imbalance as compute_obi, spread_stats
logger = logging.getLogger(__name__)
TESTNET_API = "https://api.hyperliquid-testnet.xyz/info"
MAINNET_API = "https://api.hyperliquid.xyz/info"
DEFAULT_COINS = ["BTC", "ETH"]
class LiveMonitorService:
def __init__(self, coins=None, testnet=True, poll_interval=4.0):
self._coins = coins or DEFAULT_COINS
self._api_url = TESTNET_API if testnet else MAINNET_API
self._poll_interval = poll_interval
# All 10 monitors
self.hlp = HlpVaultMonitor(testnet=testnet)
self.term = TermStructureMonitor()
self.liq = LiquidationWaterfall()
self.seq = SequencerLatencyDetector()
self.tri = TriangularLatencyArb()
self.whipsaw = FundingWhipsawTrader()
self.spoof = SpoofDetector()
self.hawkes = HawkesCalibrator(n_dimensions=3)
self.gex = DealerGEX(spot=0)
self.tick = TickRegimeMonitor()
self._obi: dict[str, float] = {}
self._spreads: dict[str, dict] = {}
self._mid: dict[str, float] = {}
self._mark: dict[str, float] = {}
self._oracle: dict[str, float] = {}
self._funding: dict[str, float] = {}
self._running = False
self._thread: Optional[threading.Thread] = None
self._last_update: float = 0
self._update_count: int = 0
self._lock = threading.Lock()
def start(self):
if self._running: return
self._running = True
self._thread = threading.Thread(target=self._poll_loop, daemon=True)
self._thread.start()
logger.info("LiveMonitorService started (%d coins)", len(self._coins))
def stop(self):
self._running = False
if self._thread: self._thread.join(timeout=5)
def _poll_loop(self):
while self._running:
try: self._update()
except Exception: pass
time.sleep(self._poll_interval)
def _update(self):
now = time.time()
books = self._fetch_books()
self._fetch_meta()
for coin in self._coins:
b = books.get(coin)
if not b: continue
bids, asks = b.get("bids", {}), b.get("asks", {})
if bids and asks:
self._obi[coin] = compute_obi(bids, asks)
ss = spread_stats(bids, asks)
self._spreads[coin] = ss
self._mid[coin] = ss["mid"]
# Update all 10 monitors
try: self.hlp.update()
except Exception: pass
for coin in self._coins:
m = self._mark.get(coin, 0)
o = self._oracle.get(coin, 0)
if m > 0:
try: self.whipsaw.update(m, o)
except: pass
self.tick.update_price(coin, m)
for coin in self._coins:
p = self._mark.get(coin, 0)
if p > 0:
self.term.update_perp(coin, p, self._funding.get(coin, 0))
self.term.update_quarterly(coin, p * 1.0002)
for coin in self._coins:
self.seq.record_ws_event(coin, "l2book", int(now * 1000))
for coin in self._coins:
self.tri.update_price("hl", f"{coin}-USDT", self._mark.get(coin, 0) or 0, latency_ms=5)
self.gex.set_spot(self._mark.get("BTC", 0))
self._last_update = now
self._update_count += 1
def _fetch_meta(self):
try:
r = requests.post(self._api_url, json={"type": "metaAndAssetCtxs"}, timeout=10).json()
if isinstance(r, list) and len(r) >= 2:
uni = r[0].get("universe", [])
ctxs = r[1]
for i, a in enumerate(uni):
n = a.get("name", "")
if n in self._coins and i < len(ctxs):
self._mark[n] = float(ctxs[i].get("markPx", 0))
self._oracle[n] = float(ctxs[i].get("oraclePx", 0))
self._funding[n] = float(ctxs[i].get("funding", 0))
except Exception: pass
def _fetch_books(self) -> dict:
books = {}
for c in self._coins:
try:
r = requests.post(self._api_url, json={"type": "l2Book", "coin": c}, timeout=5).json()
lv = r.get("levels", [])
if lv and len(lv) >= 2:
bids = {float(x["px"]): float(x["sz"]) for x in lv[0] if float(x.get("sz", 0)) > 0}
asks = {float(x["px"]): float(x["sz"]) for x in lv[1] if float(x.get("sz", 0)) > 0}
books[c] = {"bids": bids, "asks": asks}
except Exception: pass
return books
def state(self, coin="BTC") -> dict:
c = coin.upper()
with self._lock:
return {
"timestamp": time.time(),
"updates": self._update_count,
"coin": c,
"prices": {
"mid": round(self._mid.get(c, 0), 2),
"mark": round(self._mark.get(c, 0), 2),
"oracle": round(self._oracle.get(c, 0), 2),
"spread_bps": round(self._spreads.get(c, {}).get("spread_bps", 0), 2),
"funding_8h": round(self._funding.get(c, 0), 8),
"funding_apr": round(self._funding.get(c, 0) * 3 * 365 * 100, 1),
"premium_bps": round((self._mark.get(c, 0) - self._oracle.get(c, 0)) / max(self._oracle.get(c, 0), 1) * 10000, 1),
},
"microstructure": {
"obi": round(self._obi.get(c, 0), 4),
"bid": round(self._spreads.get(c, {}).get("best_bid", 0), 2),
"ask": round(self._spreads.get(c, {}).get("best_ask", 0), 2),
},
"hlp": self.hlp.summary(),
"sequencer": self.seq.summary(),
"whipsaw": self.whipsaw.signal() if self.whipsaw._mark_px > 0 else {"action": "no_data"},
"liq_waterfall": self.liq.summary(),
"term_structure": self.term.signal(c),
"gex": self.gex.summary(),
"tick_regime": self.tick.signal(c),
"triangular": self.tri.summary(),
"spoof": self.spoof.summary(),
"hawkes": {
"mu": [round(float(x), 4) for x in self.hawkes.mu],
"alpha": [[round(float(x), 4) for x in row] for row in self.hawkes.alpha],
"beta": round(self.hawkes.beta, 2),
"branching_ratio": round(self.hawkes.branching_ratio(), 4),
},
}