feat: Phase 4 — controlled strategy deployment module + 38 tests
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.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
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}
|
||||
Reference in New Issue
Block a user