""" 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), }, }