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.
100 lines
3.5 KiB
Python
100 lines
3.5 KiB
Python
"""
|
|
Funding and basis carry monitor.
|
|
|
|
Tracks funding rates across Hyperliquid and estimates carry
|
|
trade profitability. Signals when funding arbitrage is attractive.
|
|
|
|
Phase 4: informational only — no automated trading.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import deque
|
|
from typing import Optional
|
|
|
|
from microstructure.funding import (
|
|
funding_regime,
|
|
funding_predictability,
|
|
funding_carry_pnl,
|
|
)
|
|
|
|
|
|
class FundingBasisMonitor:
|
|
"""Monitor funding rates and carry trade opportunities.
|
|
|
|
Usage:
|
|
monitor = FundingBasisMonitor()
|
|
monitor.update_funding("BTC", 0.0001)
|
|
monitor.update_spot("BTC", 50000.0)
|
|
monitor.update_perp("BTC", 50005.0)
|
|
result = monitor.signal("BTC")
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
funding_window: int = 1440, # 24h at 1 sample/min
|
|
samples_per_hour: int = 60,
|
|
):
|
|
self._samples_per_hour = samples_per_hour
|
|
self._funding: dict[str, deque[float]] = {}
|
|
self._perp_prices: dict[str, deque[float]] = {}
|
|
self._spot_prices: dict[str, deque[float]] = {}
|
|
self._window = funding_window
|
|
|
|
def update_funding(self, coin: str, rate: float):
|
|
"""Record an hourly funding rate."""
|
|
self._funding.setdefault(coin.upper(), deque(maxlen=self._window)).append(rate)
|
|
|
|
def update_perp(self, coin: str, price: float):
|
|
self._perp_prices.setdefault(coin.upper(), deque(maxlen=self._window)).append(price)
|
|
|
|
def update_spot(self, coin: str, price: float):
|
|
self._spot_prices.setdefault(coin.upper(), deque(maxlen=self._window)).append(price)
|
|
|
|
def signal(self, coin: str) -> dict:
|
|
"""Generate funding/basis signal for a coin."""
|
|
c = coin.upper()
|
|
funding_list = list(self._funding.get(c, []))
|
|
perp_list = list(self._perp_prices.get(c, []))
|
|
spot_list = list(self._spot_prices.get(c, []))
|
|
|
|
if not funding_list:
|
|
return {"signal": "insufficient_data", "action": "none"}
|
|
|
|
regime = funding_regime(funding_list, window_hours=min(24, len(funding_list) // self._samples_per_hour),
|
|
n_samples_per_hour=self._samples_per_hour)
|
|
predictability = funding_predictability(funding_list)
|
|
|
|
basis = None
|
|
if perp_list and spot_list:
|
|
from microstructure.funding import basis_spread
|
|
basis = basis_spread(perp_list, spot_list)
|
|
|
|
carry = funding_carry_pnl(
|
|
funding_list,
|
|
position_size=1.0,
|
|
mark_prices=perp_list if perp_list else None,
|
|
n_samples_per_hour=self._samples_per_hour,
|
|
)
|
|
|
|
regime_name = regime.get("regime", "unknown")
|
|
action = "none"
|
|
|
|
if regime_name in ("high_positive",) and predictability.get("is_momentum"):
|
|
action = "consider_short" # shorts earn positive funding
|
|
elif regime_name in ("high_negative",) and predictability.get("is_momentum"):
|
|
action = "consider_long"
|
|
|
|
return {
|
|
"signal": regime_name,
|
|
"action": action,
|
|
"funding_mean_annual_pct": regime.get("mean_annual_pct", 0),
|
|
"momentum": predictability.get("momentum_strength", 0),
|
|
"carry_cumulative_pnl": carry.get("cumulative_pnl", 0),
|
|
"basis_current_bps": basis.get("current_basis_bps", 0) if basis else 0,
|
|
"basis_mean_bps": basis.get("mean_basis_bps", 0) if basis else 0,
|
|
}
|
|
|
|
def summary(self) -> dict:
|
|
return {coin: self.signal(coin) for coin in self._funding}
|