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:
ramseshk
2026-08-07 17:52:20 +08:00
parent 31c1fe7fbe
commit 5304534e38
11 changed files with 1892 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
"""
Term structure monitor — perp vs quarterly vs bi-quarterly futures basis.
Hyperliquid offers perpetual (funding-based), quarterly, and bi-quarterly
futures contracts. The basis curve (perp→quarterly→bi-quarterly) contains
information about market expectations and can be traded.
Anomalies:
- Perp funding deeply negative but quarterly basis remains steep →
go long perp (collect funding), short quarterly (lock basis)
- Quarterly futures converging to perp at expiration →
calendar spread mean-reversion
- Bi-quarterly premium over quarterly deviating from fair value →
curve steepener/flattener trades
"""
from __future__ import annotations
from collections import deque
from typing import Optional
class TermStructureMonitor:
"""Monitor perp/futures term structure for arbitrage opportunities.
Tracks:
- Perp funding rate and mark price
- Quarterly futures price
- Bi-quarterly futures price (if available)
- Basis spreads: quarterly-perp, biq-quarterly, biq-perp
- Calendar spread mean-reversion
"""
def __init__(
self,
funding_window: int = 1440, # 24h of 1-min samples
basis_window: int = 100,
):
self._funding_window = funding_window
self._basis_window = basis_window
self._perp_prices: dict[str, deque[float]] = {}
self._quarterly_prices: dict[str, deque[float]] = {}
self._biq_prices: dict[str, deque[float]] = {}
self._funding_rates: dict[str, deque[float]] = {}
self._funding_epoch_seconds: int = 8 * 3600
self._quarterly_expiry_days: int = 90
self._biq_expiry_days: int = 180
# ── Data feed ────────────────────────────────────────────
def update_perp(self, coin: str, price: float, funding_rate: float):
c = coin.upper()
self._perp_prices.setdefault(c, deque(maxlen=self._basis_window)).append(price)
self._funding_rates.setdefault(c, deque(maxlen=self._funding_window)).append(funding_rate)
def update_quarterly(self, coin: str, price: float):
self._quarterly_prices.setdefault(coin.upper(), deque(maxlen=self._basis_window)).append(price)
def update_biq(self, coin: str, price: float):
self._biq_prices.setdefault(coin.upper(), deque(maxlen=self._basis_window)).append(price)
# ── Basis computation ────────────────────────────────────
def quarterly_perp_basis(self, coin: str) -> dict | None:
"""Basis between quarterly future and perpetual."""
q = list(self._quarterly_prices.get(coin.upper(), []))
p = list(self._perp_prices.get(coin.upper(), []))
min_len = min(len(q), len(p))
if min_len < 2:
return None
qq = q[-min_len:]
pp = p[-min_len:]
basis_bps = [(qq[i] - pp[i]) / pp[i] * 10000 for i in range(min_len) if pp[i] > 0]
if not basis_bps:
return None
return {
"current_bps": round(basis_bps[-1], 2),
"mean_bps": round(sum(basis_bps) / len(basis_bps), 2),
"std_bps": round(_std(basis_bps), 2),
"z_score": round((basis_bps[-1] - sum(basis_bps) / len(basis_bps)) / max(_std(basis_bps), 0.01), 2),
"n_samples": len(basis_bps),
}
def biq_quarterly_basis(self, coin: str) -> dict | None:
"""Basis between bi-quarterly and quarterly futures (curve steepness)."""
bq = list(self._biq_prices.get(coin.upper(), []))
q = list(self._quarterly_prices.get(coin.upper(), []))
min_len = min(len(bq), len(q))
if min_len < 2:
return None
bb = bq[-min_len:]
qq = q[-min_len:]
basis_bps = [(bb[i] - qq[i]) / qq[i] * 10000 for i in range(min_len) if qq[i] > 0]
if not basis_bps:
return None
return {
"current_bps": round(basis_bps[-1], 2),
"mean_bps": round(sum(basis_bps) / len(basis_bps), 2),
"std_bps": round(_std(basis_bps), 2),
"z_score": round((basis_bps[-1] - sum(basis_bps) / len(basis_bps)) / max(_std(basis_bps), 0.01), 2),
"n_samples": len(basis_bps),
}
def fair_quarterly_price(self, coin: str, risk_free_annual: float = 0.05) -> dict | None:
"""Compute fair quarterly price from perp via interest rate parity."""
p = list(self._perp_prices.get(coin.upper(), []))
if not p:
return None
perp_px = p[-1]
days = self._quarterly_expiry_days
funding = list(self._funding_rates.get(coin.upper(), []))
avg_funding = sum(funding[-100:]) / max(len(funding[-100:]), 1) if funding else 0
# Fair quarterly = perp * (1 + (r + avg_funding) * days/365)
carry_rate = risk_free_annual + avg_funding * 3 * 365 # annualize 8h funding
fair_px = perp_px * (1 + carry_rate * days / 365)
return {
"perp_price": perp_px,
"fair_quarterly": round(fair_px, 2),
"carry_rate_annual_pct": round(carry_rate * 100, 2),
"days_to_expiry": days,
}
def signal(self, coin: str) -> dict:
"""Generate term-structure trading signal.
Returns:
signal: "buy_basis", "sell_basis", "curve_steepener", "curve_flattener", "none"
"""
c = coin.upper()
qp = self.quarterly_perp_basis(c)
bq = self.biq_quarterly_basis(c)
fair = self.fair_quarterly_price(c)
signals = []
# Check quarterly-perp basis anomalies
if qp and abs(qp["z_score"]) > 2.0:
if qp["z_score"] > 0:
signals.append({
"signal": "sell_basis",
"reason": f"Quarterly {qp['z_score']:.1f}σ rich vs perp",
"confidence": min(1.0, abs(qp["z_score"]) / 4.0),
})
else:
signals.append({
"signal": "buy_basis",
"reason": f"Quarterly {qp['z_score']:.1f}σ cheap vs perp",
"confidence": min(1.0, abs(qp["z_score"]) / 4.0),
})
# Check curve steepness
if bq and abs(bq["z_score"]) > 2.0:
if bq["z_score"] > 0:
signals.append({
"signal": "curve_flattener",
"reason": f"BiQ {bq['z_score']:.1f}σ rich vs quarterly",
"confidence": min(1.0, abs(bq["z_score"]) / 4.0),
})
else:
signals.append({
"signal": "curve_steepener",
"reason": f"BiQ {bq['z_score']:.1f}σ cheap vs quarterly",
"confidence": min(1.0, abs(bq["z_score"]) / 4.0),
})
result = {
"coin": c,
"signals": signals,
"primary_signal": signals[0]["signal"] if signals else "none",
"quarterly_perp_basis": qp,
"biq_quarterly_basis": bq,
"fair_quarterly": fair,
}
return result
def summary(self) -> dict:
return {coin: self.signal(coin) for coin in self._perp_prices}
def _std(vals: list[float]) -> float:
"""Population standard deviation."""
if len(vals) < 2:
return 0.0
mean = sum(vals) / len(vals)
return (sum((v - mean) ** 2 for v in vals) / len(vals)) ** 0.5