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
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
Cross-margin liquidation waterfall prediction.
|
||||
|
||||
When a whale's cross-margin portfolio approaches liquidation, the
|
||||
Hyperliquid liquidation engine selects which asset to dump first based
|
||||
on maintenance margin requirements and order book liquidity.
|
||||
|
||||
By monitoring large cross-margin accounts via clearinghouseState,
|
||||
we can predict which asset gets liquidated first and position accordingly:
|
||||
- Widen spreads on the predicted liquidation asset
|
||||
- Tighten spreads on non-liquidation assets
|
||||
- Pre-position for the post-liquidation bounce
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import deque
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LiquidationWaterfall:
|
||||
"""Predict the order of cross-margin liquidations for large accounts."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
danger_margin_ratio: float = 1.2, # margin_ratio < this = danger
|
||||
critical_margin_ratio: float = 1.05, # margin_ratio < this = imminent
|
||||
maintenance_margin_pct: float = 0.03, # 3% maintenance
|
||||
book_depth_window: int = 10, # levels to estimate liquidity
|
||||
):
|
||||
self._danger = danger_margin_ratio
|
||||
self._critical = critical_margin_ratio
|
||||
self._mm_pct = maintenance_margin_pct
|
||||
self._depth_window = book_depth_window
|
||||
|
||||
self._accounts: dict[str, dict] = {} # address → positions, equity
|
||||
self._book_depths: dict[str, dict] = {} # coin → {bid_depth, ask_depth}
|
||||
self._history: deque = deque(maxlen=1000)
|
||||
|
||||
# ── Data feed ────────────────────────────────────────────
|
||||
|
||||
def update_account(self, address: str, positions: list[dict], margin_balance: float):
|
||||
"""Update a tracked account's positions and margin."""
|
||||
self._accounts[address] = {
|
||||
"positions": positions,
|
||||
"margin_balance": margin_balance,
|
||||
"margin_ratio": self._compute_margin_ratio(positions, margin_balance),
|
||||
}
|
||||
|
||||
def update_book_depth(self, coin: str, bid_depth: float, ask_depth: float):
|
||||
"""Update estimated order book depth for a coin."""
|
||||
self._book_depths[coin.upper()] = {
|
||||
"bid_depth": bid_depth,
|
||||
"ask_depth": ask_depth,
|
||||
}
|
||||
|
||||
# ── Risk assessment ─────────────────────────────────────
|
||||
|
||||
def _compute_margin_ratio(
|
||||
self, positions: list[dict], margin_balance: float
|
||||
) -> float:
|
||||
"""Compute margin ratio = equity / maintenance_margin."""
|
||||
if not positions or margin_balance <= 0:
|
||||
return float("inf")
|
||||
|
||||
total_mm = 0.0
|
||||
for pos in positions:
|
||||
size = abs(float(pos.get("szi", 0)))
|
||||
px = float(pos.get("entryPx", pos.get("markPx", 0)))
|
||||
total_mm += size * px * self._mm_pct
|
||||
|
||||
return margin_balance / total_mm if total_mm > 0 else float("inf")
|
||||
|
||||
def at_risk_accounts(self) -> list[dict]:
|
||||
"""List accounts approaching liquidation."""
|
||||
risky = []
|
||||
for addr, acct in self._accounts.items():
|
||||
ratio = acct["margin_ratio"]
|
||||
if ratio < self._danger:
|
||||
level = "critical" if ratio < self._critical else "danger"
|
||||
risky.append({
|
||||
"address": addr[:10] + "...",
|
||||
"margin_ratio": round(ratio, 3),
|
||||
"level": level,
|
||||
"positions": len(acct["positions"]),
|
||||
})
|
||||
return sorted(risky, key=lambda r: r["margin_ratio"])
|
||||
|
||||
def predict_liquidation_order(self, address: str) -> list[dict]:
|
||||
"""Predict which assets get liquidated first for a given account.
|
||||
|
||||
Returns assets ranked by liquidation priority (first to go = top).
|
||||
Uses: maintenance_margin_requirement / book_liquidity ratio.
|
||||
Higher ratio = less liquid relative to margin cost = dumped first.
|
||||
"""
|
||||
acct = self._accounts.get(address)
|
||||
if not acct:
|
||||
return []
|
||||
|
||||
positions = acct["positions"]
|
||||
ranked = []
|
||||
|
||||
for pos in positions:
|
||||
coin = pos.get("coin", "").upper()
|
||||
size = abs(float(pos.get("szi", 0)))
|
||||
px = float(pos.get("entryPx", pos.get("markPx", 0)))
|
||||
|
||||
maintenance = size * px * self._mm_pct
|
||||
|
||||
# Liquidity: how much the book can absorb before significant slippage
|
||||
book = self._book_depths.get(coin, {})
|
||||
side = "buy" if float(pos.get("szi", 0)) < 0 else "sell"
|
||||
depth = book.get("bid_depth" if side == "buy" else "ask_depth", size * px * 0.1)
|
||||
|
||||
# Liquidation priority score: higher = dumped first
|
||||
liquidity_ratio = maintenance / max(depth, 1e-8)
|
||||
ranked.append({
|
||||
"coin": coin,
|
||||
"size": size,
|
||||
"notional_usd": round(size * px, 2),
|
||||
"maintenance_usd": round(maintenance, 2),
|
||||
"liquidity_ratio": round(liquidity_ratio, 4),
|
||||
"predicted_first": False, # set below
|
||||
})
|
||||
|
||||
# Sort by liquidity ratio (highest = least liquid = dumped first)
|
||||
ranked.sort(key=lambda r: r["liquidity_ratio"], reverse=True)
|
||||
if ranked:
|
||||
ranked[0]["predicted_first"] = True
|
||||
|
||||
return ranked
|
||||
|
||||
def signal(self, address: str) -> dict:
|
||||
"""Generate trading signal based on predicted liquidation waterfall.
|
||||
|
||||
If an account is in danger and we can predict the liquidation order:
|
||||
- Asset predicted to be dumped first → widen spreads, go short
|
||||
- Other assets in the portfolio → tighten spreads (safer to quote)
|
||||
"""
|
||||
acct = self._accounts.get(address)
|
||||
if not acct or acct["margin_ratio"] > self._danger:
|
||||
return {"action": "none", "reason": "account_safe"}
|
||||
|
||||
order = self.predict_liquidation_order(address)
|
||||
if not order:
|
||||
return {"action": "none", "reason": "no_positions"}
|
||||
|
||||
first_asset = order[0]
|
||||
return {
|
||||
"action": "position",
|
||||
"reason": f"liquidation_imminent_{acct['margin_ratio']:.2f}",
|
||||
"margin_ratio": round(acct["margin_ratio"], 3),
|
||||
"liquidation_target": first_asset["coin"],
|
||||
"strategy": {
|
||||
first_asset["coin"]: "widen_spreads_2x",
|
||||
**{r["coin"]: "tighten_spreads" for r in order[1:]},
|
||||
},
|
||||
"predicted_order": [r["coin"] for r in order],
|
||||
}
|
||||
|
||||
def summary(self) -> dict:
|
||||
return {
|
||||
"at_risk_accounts": self.at_risk_accounts(),
|
||||
"tracked_accounts": len(self._accounts),
|
||||
}
|
||||
Reference in New Issue
Block a user