"""Tests for MetricsMonitor.""" import pytest from datetime import datetime from unittest.mock import Mock from nautilus_dolphin.nautilus.metrics_monitor import ( MetricsMonitor, ThresholdConfig, Alert ) class TestMetricsMonitor: def test_record_fill_updates_counters(self): """Test fill recording updates maker/taker counters.""" monitor = MetricsMonitor() monitor.record_fill('maker', 0.5) monitor.record_fill('maker', 0.3) monitor.record_fill('taker', 1.2) assert monitor._maker_fills == 2 assert monitor._taker_fills == 1 def test_get_maker_fill_rate(self): """Test maker fill rate calculation.""" monitor = MetricsMonitor() # Default when no fills assert monitor.get_maker_fill_rate() == 100.0 # After some fills monitor.record_fill('maker', 0.5) monitor.record_fill('maker', 0.3) monitor.record_fill('taker', 1.2) assert monitor.get_maker_fill_rate() == (2/3) * 100 def test_get_average_slippage(self): """Test average slippage calculation.""" monitor = MetricsMonitor() # Default when no data assert monitor.get_average_slippage() == 0.0 # After fills monitor.record_fill('maker', 2.0) monitor.record_fill('taker', 4.0) monitor.record_fill('maker', 6.0) assert monitor.get_average_slippage() == 4.0 def test_record_trade_result_updates_pnl(self): """Test trade result recording updates P&L.""" monitor = MetricsMonitor() monitor.record_trade_result(100.0) monitor.record_trade_result(-50.0) monitor.record_trade_result(200.0) assert monitor._total_pnl == 250.0 assert monitor._winning_trades == 2 assert monitor._total_trades == 3 def test_get_win_rate(self): """Test win rate calculation.""" monitor = MetricsMonitor() # Default when no trades assert monitor.get_win_rate() == 0.0 monitor.record_trade_result(100.0) monitor.record_trade_result(200.0) monitor.record_trade_result(-50.0) assert monitor.get_win_rate() == (2/3) * 100 def test_get_profit_factor(self): """Test profit factor calculation.""" monitor = MetricsMonitor() monitor.record_trade_result(100.0) monitor.record_trade_result(200.0) monitor.record_trade_result(-50.0) assert monitor.get_profit_factor() == 300.0 / 50.0 def test_profit_factor_no_losses(self): """Test profit factor when no losses.""" monitor = MetricsMonitor() monitor.record_trade_result(100.0) assert monitor.get_profit_factor() == float('inf') def test_alert_on_low_maker_fill_rate(self): """Test alert raised when maker fill rate drops below threshold.""" config = ThresholdConfig( critical_maker_fill_rate=50.0, warning_maker_fill_rate=60.0 ) monitor = MetricsMonitor(config) # Add many taker fills to drop maker rate below threshold for _ in range(10): monitor.record_fill('taker', 1.0) # Check alert was raised alerts = [a for a in monitor._alerts if a.metric == 'maker_fill_rate'] assert len(alerts) > 0 assert alerts[0].level == 'critical' def test_alert_on_high_slippage(self): """Test alert raised when slippage exceeds threshold.""" config = ThresholdConfig( warning_slippage_bps=3.0, critical_slippage_bps=5.0 ) monitor = MetricsMonitor(config) monitor.record_fill('taker', 10.0) # Check alert was raised alerts = [a for a in monitor._alerts if a.metric == 'slippage'] assert len(alerts) > 0 assert alerts[0].level == 'critical' def test_alert_deduplication(self): """Test alerts are deduplicated within 5-minute window.""" config = ThresholdConfig(critical_maker_fill_rate=50.0) monitor = MetricsMonitor(config) # Add many taker fills for _ in range(20): monitor.record_fill('taker', 1.0) # Should only have 1 alert (deduplicated) alerts = [a for a in monitor._alerts if a.metric == 'maker_fill_rate'] assert len(alerts) == 1 def test_add_alert_handler(self): """Test custom alert handler registration.""" monitor = MetricsMonitor() handler = Mock() monitor.add_alert_handler(handler) # Trigger an alert config = ThresholdConfig(critical_maker_fill_rate=50.0) monitor.config = config for _ in range(10): monitor.record_fill('taker', 1.0) # Handler should have been called assert handler.called def test_get_metrics_summary(self): """Test metrics summary report.""" monitor = MetricsMonitor() monitor.record_fill('maker', 0.5) monitor.record_trade_result(100.0) summary = monitor.get_metrics_summary() assert 'maker_fill_rate_pct' in summary assert 'average_slippage_bps' in summary assert 'total_trades' in summary assert 'win_rate_pct' in summary assert 'profit_factor' in summary assert 'total_pnl' in summary def test_prometheus_export(self): """Test Prometheus format export.""" monitor = MetricsMonitor() monitor.record_fill('maker', 0.5) monitor.record_trade_result(100.0) prometheus = monitor.get_prometheus_metrics() assert 'dolphin_maker_fill_rate_pct' in prometheus assert 'dolphin_slippage_bps' in prometheus assert 'dolphin_total_trades' in prometheus assert 'dolphin_win_rate_pct' in prometheus assert 'dolphin_profit_factor' in prometheus assert 'dolphin_total_pnl' in prometheus class TestThresholdConfig: def test_default_thresholds(self): """Test default threshold configuration.""" config = ThresholdConfig() assert config.min_maker_fill_rate == 48.0 assert config.max_slippage_bps == 5.0 assert config.critical_maker_fill_rate == 48.0 assert config.warning_slippage_bps == 3.0 def test_custom_thresholds(self): """Test custom threshold configuration.""" config = ThresholdConfig( min_maker_fill_rate=60.0, max_slippage_bps=3.0, critical_maker_fill_rate=55.0 ) assert config.min_maker_fill_rate == 60.0 assert config.max_slippage_bps == 3.0 assert config.critical_maker_fill_rate == 55.0