Files
ftdt-quant-lab/tests/test_live_monitors.py
ramseshk 4f66ef36a9 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.
2026-08-07 14:47:08 +08:00

94 lines
3.5 KiB
Python

"""
Tests for live/monitors — cross-venue, funding/basis, liquidation risk.
"""
from live.monitors.cross_venue import CrossVenueMonitor
from live.monitors.funding_basis import FundingBasisMonitor
from live.monitors.liq_risk import LiquidationRiskOverlay
from live.treasury import Treasury
class TestCrossVenueMonitor:
def test_update_and_lead_lag(self):
cm = CrossVenueMonitor(window=50, max_lag=5)
for i in range(50):
cm.update("hl", "BTC", 50000.0 + i * 10, float(i))
cm.update("binance", "BTC", 50000.0 + i * 10 + 2, float(i))
result = cm.lead_lag("BTC", "hl", "binance")
assert result is not None
assert "correlation" in result
assert "lag" in result
def test_spot_premium(self):
cm = CrossVenueMonitor()
for _ in range(10):
cm.update("hl", "BTC", 50005.0, 0)
cm.update("binance", "BTC", 50000.0, 0)
premium = cm.spot_premium("BTC")
assert premium is not None
assert premium["basis_bps"] > 0
def test_nonexistent_coin_returns_none(self):
cm = CrossVenueMonitor()
assert cm.lead_lag("XYZ", "hl", "binance") is None
def test_summary(self):
cm = CrossVenueMonitor()
for i in range(50):
cm.update("hl", "BTC", 50000.0 + i * 10, float(i))
cm.update("binance", "BTC", 50000.0 + i * 10, float(i))
s = cm.summary("BTC")
assert "hl_binance" in s
class TestFundingBasisMonitor:
def test_initial_no_signal(self):
fm = FundingBasisMonitor()
result = fm.signal("BTC")
assert result["signal"] == "insufficient_data"
def test_signal_with_data(self):
fm = FundingBasisMonitor(funding_window=100, samples_per_hour=60)
for _ in range(200):
fm.update_funding("BTC", 0.00001)
result = fm.signal("BTC")
assert result["signal"] in ("neutral", "positive", "negative", "high_positive", "high_negative")
assert "funding_mean_annual_pct" in result
def test_basis_requires_spot_and_perp(self):
fm = FundingBasisMonitor()
for i in range(50):
fm.update_perp("BTC", 50005.0)
fm.update_spot("BTC", 50000.0)
fm.update_funding("BTC", 0.00001)
result = fm.signal("BTC")
assert result["basis_current_bps"] > 0
class TestLiquidationRiskOverlay:
def test_safe_position(self):
t = Treasury(initial_equity=100000.0)
t.record_fill("BTC", side="buy", size=0.001, price=50000.0, fee=10.0, pnl=0)
overlay = LiquidationRiskOverlay(treasury=t)
result = overlay.check("BTC")
assert result["level"] == "safe"
def test_no_position(self):
t = Treasury(initial_equity=10000.0)
overlay = LiquidationRiskOverlay(treasury=t)
result = overlay.check("BTC")
assert result["distance_pct"] > 1e5 # capped at 999999 for display
def test_recommended_action(self):
t = Treasury(initial_equity=10000.0)
t.record_fill("BTC", side="buy", size=0.001, price=50000.0, fee=10.0, pnl=0)
overlay = LiquidationRiskOverlay(treasury=t)
assert overlay.recommended_action("BTC") == "none"
def test_summary(self):
t = Treasury(initial_equity=100000.0)
t.record_fill("BTC", side="buy", size=0.001, price=50000.0, fee=10.0, pnl=0)
overlay = LiquidationRiskOverlay(treasury=t)
s = overlay.summary()
assert "positions" in s
assert "worst_case" in s