""" 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}