Files
ramseshk 5304534e38 feat: advanced microstructure modules — HLP, Hawkes, Whipsaw, Term Structure, Liq Waterfall, Spoof Detector
6 new modules with 46 new tests (230 total):

#21 HLP Vault Monitor (live/monitors/hlp_vault.py):
  Tracks Hyperliquid's native protocol market maker at address
  0xfefefe... Queries clearinghouseState + metaAndAssetCtxs.
  - Delta exposure per asset (notional + PnL)
  - Overextension detection (notional exceeds  M threshold)
  - Rebalancing signals: fade_short when HLP too short,
    fade_long when HLP too long (front-run forced rebalancing)
  - Toxicity score: HLP losing money = absorbing informed flow
  - Historical delta tracking

#29 Hawkes Processes (microstructure/hawkes.py):
  Multivariate Hawkes calibrator for limit order book dynamics.
  - MLE calibration via SGD gradient descent on log-likelihood
  - Branching ratio enforcement (alpha/beta < 0.99 for stationarity)
  - Intensity computation λ_i(t) with cross-excitation
  - Activity forecasting (expected event count in horizon)
  - Synthetic event generator (Ogata thinning)
  - Pure functions: hawkes_intensity, hawkes_log_likelihood,
    generate_hawkes_events

#23 Funding Whipsaw Trader (live/strategies/funding_whipsaw.py):
  Premium index decay trading in final 60s of funding epoch.
  - Detects deterministic convergence of premium→0 at settlement
  - Time-scaled position sizing (larger closer to settlement)
  - Auto-close after funding epoch completes
  - Confidence scoring based on premium magnitude

#32 Term Structure Monitor (live/monitors/term_structure.py):
  Perp/quarterly/bi-quarterly futures basis curve trading.
  - Quarterly-perp basis with z-score anomaly detection
  - BiQ-quarterly curve steepness monitoring
  - Fair quarterly price via interest rate parity + funding carry
  - Calendar spread signals: buy_basis, sell_basis,
    curve_steepener, curve_flattener

#24 Liquidation Waterfall (live/monitors/liq_waterfall.py):
  Cross-margin liquidation order prediction.
  - Margin ratio tracking (equity / maintenance margin)
  - Danger/critical level classification
  - Asset liquidation priority: maintenance / book_liquidity ratio
    (least liquid asset relative to margin = dumped first)
  - Strategy output: widen_spreads on target, tighten on rest

#31 Spoof Detector (microstructure/spoof_detector.py):
  Adversarial ML-style spoofing pattern recognition.
  - Rule 1: Large order far from mid, cancelled immediately
  - Rule 2: Cancel right before trade approaches price level
  - Rule 3: Oversized order with no fill within short lifetime
  - Spoof probability (rolling window ratio)
  - Cancel-to-fill ratio monitoring
2026-08-07 17:52:20 +08:00

270 lines
11 KiB
Python

"""
HLP (Hyperliquidity Provider) Vault monitoring.
Tracks Hyperliquid's native protocol-level market-making vault at
address 0xfefefefefefefefefefefefefefefefefefefefe.
HLP acts as counterparty to all user trades. When it absorbs toxic flow
or becomes directionally overextended, it must rebalance — creating
predictable market impact that can be traded.
Signals:
- fade_short: HLP is too short → expect buying rebalance → go long
- fade_long: HLP is too long → expect selling rebalance → go short
- neutral: HLP delta is balanced, safe to provide liquidity alongside
"""
from __future__ import annotations
import logging
import time
from collections import deque
from typing import Optional
import requests
logger = logging.getLogger(__name__)
HLP_ADDRESS = "0xfefefefefefefefefefefefefefefefefefefefe"
TESTNET_API = "https://api.hyperliquid-testnet.xyz/info"
MAINNET_API = "https://api.hyperliquid.xyz/info"
class HlpVaultMonitor:
"""Monitor HLP vault state — delta, PnL, rebalancing pressure, toxicity."""
def __init__(
self,
testnet: bool = True,
overextended_threshold: float = 5.0, # notional in $M before overextended
history_window: int = 1000,
):
self._api_url = TESTNET_API if testnet else MAINNET_API
self._overextended_threshold = overextended_threshold * 1_000_000 # convert to USD
self._testnet = testnet
# Per-coin state
self._positions: dict[str, dict] = {} # coin → {side, szi, entry_px, upnl}
self._mark_prices: dict[str, float] = {} # coin → mark price
self._oracle_prices: dict[str, float] = {} # coin → oracle price
self._funding_rates: dict[str, float] = {} # coin → funding rate
self._open_interest: dict[str, float] = {} # coin → OI
# History
self._delta_history: dict[str, deque] = {} # coin → deque of (time, notional)
self._pnl_history: list[dict] = []
self._history_window = history_window
self._last_update: float = 0
self._update_count: int = 0
# ── Core update ──────────────────────────────────────────
def _api_post(self, payload: dict) -> dict:
"""Make a POST request to HL info API (testable via mock)."""
resp = requests.post(self._api_url, json=payload, timeout=10)
resp.raise_for_status()
return resp.json()
def update(self):
"""Fetch latest HLP state from Hyperliquid API.
Makes two calls:
1. metaAndAssetCtxs → mark prices, funding, OI
2. clearinghouseState(HLP_ADDRESS) → positions, PnL
"""
now = time.time()
# Fetch market data
try:
meta_and_ctx = self._api_post({"type": "metaAndAssetCtxs"})
if isinstance(meta_and_ctx, list) and len(meta_and_ctx) >= 2:
universe = meta_and_ctx[0].get("universe", [])
ctxs = meta_and_ctx[1]
for i, asset in enumerate(universe):
name = asset.get("name", "")
if name and i < len(ctxs):
self._mark_prices[name] = float(ctxs[i].get("markPx", 0))
self._oracle_prices[name] = float(ctxs[i].get("oraclePx", 0))
self._funding_rates[name] = float(ctxs[i].get("funding", 0))
self._open_interest[name] = float(ctxs[i].get("openInterest", 0))
except Exception as e:
logger.warning("HLP meta fetch error: %s", e)
# Fetch HLP positions
try:
ch_state = self._api_post({
"type": "clearinghouseState",
"user": HLP_ADDRESS,
})
self._parse_positions(ch_state, now)
except Exception as e:
logger.warning("HLP clearinghouse fetch error: %s", e)
self._last_update = now
self._update_count += 1
def _parse_positions(self, data: dict, now: float):
"""Parse clearinghouseState response into per-coin positions."""
asset_positions = data.get("assetPositions", [])
new_positions: dict[str, dict] = {}
for ap in asset_positions:
pos = ap.get("position", {})
if not pos:
continue
coin = pos.get("coin", "")
if not coin:
continue
szi = float(pos.get("szi", 0))
entry_px = float(pos.get("entryPx", 0))
upnl = float(pos.get("unrealizedPnl", 0))
side = pos.get("side", "") # "A" = short, "B" = long
# HLP short is side="A", long is side="B"
signed_szi = -szi if side == "A" else szi
new_positions[coin] = {
"side": side,
"szi": szi,
"signed_szi": signed_szi,
"entry_px": entry_px,
"unrealized_pnl": upnl,
"mark_px": self._mark_prices.get(coin, entry_px),
"notional_usd": abs(szi) * self._mark_prices.get(coin, entry_px),
}
# Update delta history
if coin not in self._delta_history:
self._delta_history[coin] = deque(maxlen=self._history_window)
self._delta_history[coin].append({
"t": now,
"signed_szi": signed_szi,
"notional_usd": new_positions[coin]["notional_usd"],
"upnl": upnl,
})
self._positions = new_positions
self._pnl_history.append({
"t": now,
"total_upnl": sum(p["unrealized_pnl"] for p in new_positions.values()),
"asset_count": len(new_positions),
})
if len(self._pnl_history) > self._history_window:
self._pnl_history = self._pnl_history[-self._history_window:]
# ── Queries ──────────────────────────────────────────────
def position(self, coin: str) -> float:
"""Signed position for a coin (positive = long)."""
pos = self._positions.get(coin.upper(), {})
return pos.get("signed_szi", 0.0)
def delta_exposure(self) -> dict:
"""Delta exposure per coin with notional and PnL."""
result = {}
for coin, pos in self._positions.items():
result[coin] = {
"signed_size": pos["signed_szi"],
"notional_usd": round(pos["notional_usd"], 2),
"unrealized_pnl": round(pos["unrealized_pnl"], 2),
"side": "long" if pos["side"] == "B" else "short",
}
return result
def is_overextended(self, coin: str) -> bool:
"""Check if HLP delta on this coin exceeds the threshold."""
pos = self._positions.get(coin.upper(), {})
notional = pos.get("notional_usd", 0)
return notional > self._overextended_threshold
def overextended_assets(self) -> list[str]:
"""List of assets where HLP is overextended."""
return [c for c in self._positions if self.is_overextended(c)]
def rebalancing_signal(self, coin: str) -> dict:
"""Generate a trading signal based on HLP rebalancing pressure.
Returns:
signal: "fade_short" | "fade_long" | "neutral"
direction: -1 (short) | 0 | 1 (long) for the TRADE direction
overextended: whether HLP is over capacity
"""
pos = self._positions.get(coin.upper(), {})
if not pos:
return {"signal": "neutral", "direction": 0, "overextended": False, "reason": "no_position"}
signed = pos["signed_szi"]
is_over = self.is_overextended(coin)
if not is_over:
return {"signal": "neutral", "direction": 0, "overextended": False, "reason": "balanced"}
# HLP is short (side=A, signed_szi negative) → it will need to buy to rebalance
# We should fade the short (go long)
if signed < -0.001:
return {
"signal": "fade_short",
"direction": 1,
"overextended": True,
"reason": f"HLP short {abs(signed):.2f} units, expect buying rebalance",
"notional_usd": round(pos["notional_usd"], 2),
}
# HLP is long (side=B, signed_szi positive) → it will need to sell to rebalance
# We should fade the long (go short)
elif signed > 0.001:
return {
"signal": "fade_long",
"direction": -1,
"overextended": True,
"reason": f"HLP long {signed:.2f} units, expect selling rebalance",
"notional_usd": round(pos["notional_usd"], 2),
}
else:
return {"signal": "neutral", "direction": 0, "overextended": False, "reason": "flat"}
def toxicity_score(self) -> float:
"""Estimate how much toxic flow HLP is absorbing.
Higher score = HLP is losing money = informed traders are beating it.
Range: 0 (healthy) to 1 (toxic).
Uses: unrealized PnL / total notional as a proxy.
"""
total_notional = sum(p["notional_usd"] for p in self._positions.values())
total_upnl = sum(p["unrealized_pnl"] for p in self._positions.values())
if total_notional <= 0:
return 0.0
# Negative PnL → toxic score > 0
# Positive PnL → toxic score 0 (healthy)
loss_ratio = max(0.0, -total_upnl / total_notional)
toxicity = min(1.0, loss_ratio * 10) # scale: 10% loss = 1.0 toxicity
return round(toxicity, 4)
def delta_history(self, coin: str) -> list[dict]:
"""Historical delta trace for a coin."""
return list(self._delta_history.get(coin.upper(), []))
# ── Summary ──────────────────────────────────────────────
def summary(self) -> dict:
"""One-shot summary of HLP state for dashboard/monitoring."""
assets = list(self._positions.keys())
total_delta = sum(p["notional_usd"] for p in self._positions.values())
overextended = self.overextended_assets()
signals = {coin: self.rebalancing_signal(coin) for coin in assets}
return {
"assets_tracked": len(assets),
"total_delta_usd": round(total_delta, 2),
"total_delta_m": round(total_delta / 1_000_000, 2),
"toxicity_score": self.toxicity_score(),
"overextended_assets": overextended,
"signals": signals,
"positions": self.delta_exposure(),
"last_update": self._last_update,
"update_count": self._update_count,
}