Files
DOLPHIN/nautilus_dolphin/tests/test_metrics_monitor.py
hjnormey 01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
2026-04-21 16:58:38 +02:00

213 lines
7.1 KiB
Python
Executable File

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