4f66ef36a9
New live/ sub-modules for production-ready market making:
live/filters/toxicity.py (ToxicityFilter):
VPIN-based pre-trade filter. Accumulates buy/sell volume, computes
VPIN via microstructure module, produces quoting decision:
- allow_quoting: bool
- size_multiplier: 0.0–1.0 (graduated reduction approaching alarm)
- granular thresholds (threshold vs alarm) with smooth reduction
live/treasury.py (Treasury):
Central capital/risk management — single source of truth:
- Position tracking per coin (opening, closing, average entry)
- Realized + unrealized PnL computation
- Pre-trade constraint checks (inventory limits, fee estimates)
- Circuit breaker (drawdown, trade count, toxic fill rate, API errors)
- Liquidation distance monitoring
- Automatic cooldown reset after trip expiry
live/makers/hl_btc_eth.py:
HlMaker — per-coin market maker integrating:
- AvellanedaStoikovMaker (Phase 3) for optimal quotes
- ToxicityFilter for pre-trade gating
- Treasury for position/risk checks
HlMakerPool — manages multiple HlMaker instances with shared treasury
and coordinated observe_all()/quote_all()
live/monitors/cross_venue.py (CrossVenueMonitor):
Cross-exchange lead-lag detection via cross-correlation at multiple
lags. Spot premium (basis proxy) computation. Multi-venue summary.
live/monitors/funding_basis.py (FundingBasisMonitor):
Funding regime classification, momentum detection, carry PnL
estimation, basis spread analysis. Uses microstructure/funding.py.
live/monitors/liq_risk.py (LiquidationRiskOverlay):
Per-position liquidation distance monitoring with tiered warnings
(safe/warning/danger/critical). Recommended position reduction.
38 tests across 4 files (all pass):
test_live_filters.py (5)
test_live_maker.py (9)
test_live_monitors.py (11)
test_live_treasury.py (13)
Total test suite: 172 tests, all passing.
121 lines
4.0 KiB
Python
121 lines
4.0 KiB
Python
"""
|
|
Cross-venue lead-lag monitor.
|
|
|
|
Detects when one exchange leads another in price discovery.
|
|
Used for informational purposes only in Phase 4 — no auto-trading.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import deque
|
|
from typing import Optional
|
|
|
|
import numpy as np
|
|
|
|
|
|
class CrossVenueMonitor:
|
|
"""Monitor price lead-lag relationships between exchanges.
|
|
|
|
Tracks mid prices across venues and computes cross-correlation
|
|
and lead-lag structure. Can detect when Hyperliquid follows
|
|
Binance or vice versa.
|
|
|
|
Usage:
|
|
monitor = CrossVenueMonitor(pairs=[("hl", "binance")], window=100)
|
|
monitor.update("hl", "BTC", 50000.0)
|
|
monitor.update("binance", "BTC", 50000.5)
|
|
result = monitor.lead_lag("BTC")
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
pairs: list[tuple[str, str]] | None = None,
|
|
window: int = 100,
|
|
max_lag: int = 10,
|
|
):
|
|
self._window = window
|
|
self._max_lag = max_lag
|
|
self._pairs = pairs or [("hl", "binance"), ("hl", "bybit"), ("hl", "okx")]
|
|
|
|
# venue → coin → deque of mid prices
|
|
self._prices: dict[str, dict[str, deque[float]]] = {}
|
|
self._timestamps: dict[str, dict[str, deque[float]]] = {}
|
|
|
|
def update(self, venue: str, coin: str, price: float, timestamp: float):
|
|
"""Record a mid price observation from a venue."""
|
|
v = venue.lower()
|
|
c = coin.upper()
|
|
self._prices.setdefault(v, {}).setdefault(c, deque(maxlen=self._window)).append(price)
|
|
self._timestamps.setdefault(v, {}).setdefault(c, deque(maxlen=self._window)).append(timestamp)
|
|
|
|
def lead_lag(self, coin: str, venue_a: str = "hl", venue_b: str = "binance") -> dict | None:
|
|
"""Determine which venue leads by cross-correlation at various lags.
|
|
|
|
Returns {leading_venue: str, max_correlation: float, lag: int}
|
|
Negative lag = venue_a leads, positive lag = venue_b leads.
|
|
"""
|
|
prices_a = list(self._prices.get(venue_a.lower(), {}).get(coin.upper(), []))
|
|
prices_b = list(self._prices.get(venue_b.lower(), {}).get(coin.upper(), []))
|
|
|
|
min_len = min(len(prices_a), len(prices_b))
|
|
if min_len < self._max_lag + 2:
|
|
return None
|
|
|
|
a = np.array(prices_a[-min_len:])
|
|
b = np.array(prices_b[-min_len:])
|
|
|
|
best_corr = -1.0
|
|
best_lag = 0
|
|
|
|
for lag in range(-self._max_lag, self._max_lag + 1):
|
|
if lag < 0:
|
|
corr = np.corrcoef(a[-lag:], b[:lag])[0, 1] if lag < 0 else 0
|
|
elif lag > 0:
|
|
corr = np.corrcoef(a[:min_len - lag], b[lag:])[0, 1]
|
|
else:
|
|
corr = np.corrcoef(a, b)[0, 1]
|
|
|
|
if not np.isnan(corr) and abs(corr) > abs(best_corr):
|
|
best_corr = float(corr)
|
|
best_lag = lag
|
|
|
|
return {
|
|
"leading_venue": venue_a if best_lag < 0 else venue_b if best_lag > 0 else "none",
|
|
"correlation": round(best_corr, 4),
|
|
"lag": best_lag,
|
|
"samples": min_len,
|
|
}
|
|
|
|
def spot_premium(self, coin: str, venue: str = "hl", spot_venue: str = "binance") -> dict | None:
|
|
"""Compute the premium of venue over spot (basis proxy)."""
|
|
v = self._prices.get(venue.lower(), {}).get(coin.upper())
|
|
sv = self._prices.get(spot_venue.lower(), {}).get(coin.upper())
|
|
if not v or not sv:
|
|
return None
|
|
|
|
perp = v[-1]
|
|
spot = sv[-1]
|
|
basis_bps = (perp - spot) / spot * 10000 if spot > 0 else 0
|
|
|
|
return {
|
|
"venue": venue,
|
|
"spot_venue": spot_venue,
|
|
"perp_price": perp,
|
|
"spot_price": spot,
|
|
"basis_bps": round(basis_bps, 2),
|
|
}
|
|
|
|
def summary(self, coin: str) -> dict:
|
|
"""Summary for a given coin across all venues."""
|
|
result = {}
|
|
for va, vb in self._pairs:
|
|
ll = self.lead_lag(coin, va, vb)
|
|
if ll:
|
|
result[f"{va}_{vb}"] = ll
|
|
|
|
premium = self.spot_premium(coin)
|
|
if premium:
|
|
result["premium"] = premium
|
|
|
|
return result
|