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.
This commit is contained in:
212
nautilus_dolphin/tests/test_metrics_monitor.py
Executable file
212
nautilus_dolphin/tests/test_metrics_monitor.py
Executable file
@@ -0,0 +1,212 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user