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:
153
nautilus_dolphin/tests/test_signal_bridge.py
Executable file
153
nautilus_dolphin/tests/test_signal_bridge.py
Executable file
@@ -0,0 +1,153 @@
|
||||
"""Tests for SignalBridgeActor.
|
||||
|
||||
Uses TestClock pattern from Nautilus to properly initialize Actor components.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, PropertyMock
|
||||
|
||||
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeActor, SignalBridgeConfig
|
||||
|
||||
|
||||
class TestSignalBridgeTimestampParsing:
|
||||
"""Test timestamp parsing without full Nautilus initialization."""
|
||||
|
||||
def test_parse_timestamp_ns_from_seconds(self):
|
||||
"""Test timestamp conversion from seconds."""
|
||||
config = SignalBridgeConfig(redis_url='redis://localhost')
|
||||
actor = SignalBridgeActor(config)
|
||||
|
||||
ts_sec = 1234567890
|
||||
ts_ns = actor._parse_timestamp_ns(ts_sec)
|
||||
|
||||
assert ts_ns == 1234567890 * 1_000_000_000
|
||||
|
||||
def test_parse_timestamp_ns_from_milliseconds(self):
|
||||
"""Test timestamp conversion from milliseconds."""
|
||||
config = SignalBridgeConfig(redis_url='redis://localhost')
|
||||
actor = SignalBridgeActor(config)
|
||||
|
||||
ts_ms = 1234567890123
|
||||
ts_ns = actor._parse_timestamp_ns(ts_ms)
|
||||
|
||||
assert ts_ns == 1234567890123 * 1_000
|
||||
|
||||
def test_parse_timestamp_ns_from_nanoseconds(self):
|
||||
"""Test timestamp already in nanoseconds."""
|
||||
config = SignalBridgeConfig(redis_url='redis://localhost')
|
||||
actor = SignalBridgeActor(config)
|
||||
|
||||
ts_ns = 1234567890123456789
|
||||
result = actor._parse_timestamp_ns(ts_ns)
|
||||
|
||||
assert result == ts_ns
|
||||
|
||||
|
||||
class TestSignalBridgeWithNautilus:
|
||||
"""Test SignalBridgeActor with proper Nautilus initialization."""
|
||||
|
||||
@pytest.fixture
|
||||
def nautilus_actor(self):
|
||||
"""Create a properly initialized SignalBridgeActor with mocked clock."""
|
||||
from nautilus_trader.common.component import TestClock
|
||||
|
||||
config = SignalBridgeConfig(
|
||||
redis_url='redis://localhost',
|
||||
max_signal_age_sec=10
|
||||
)
|
||||
actor = SignalBridgeActor(config)
|
||||
|
||||
# Create TestClock and patch the clock property
|
||||
clock = TestClock()
|
||||
|
||||
# Use patch to mock the clock property
|
||||
with patch.object(
|
||||
type(actor),
|
||||
'clock',
|
||||
new_callable=PropertyMock
|
||||
) as mock_clock:
|
||||
mock_clock.return_value = clock
|
||||
yield actor, clock
|
||||
|
||||
def test_validate_signal_missing_fields(self, nautilus_actor):
|
||||
"""Test signal validation rejects missing fields."""
|
||||
actor, clock = nautilus_actor
|
||||
|
||||
signal = {'timestamp': 1234567890, 'asset': 'BTCUSDT'}
|
||||
|
||||
assert not actor._validate_signal(signal)
|
||||
|
||||
def test_validate_signal_valid(self, nautilus_actor):
|
||||
"""Test signal validation accepts valid signal."""
|
||||
actor, clock = nautilus_actor
|
||||
|
||||
# Set clock to be after signal timestamp
|
||||
clock.set_time(1234567890 * 1_000_000_000 + 5_000_000_000)
|
||||
|
||||
signal = {
|
||||
'timestamp': 1234567890,
|
||||
'asset': 'BTCUSDT',
|
||||
'direction': 'SHORT',
|
||||
'vel_div': -0.025,
|
||||
'strength': 0.75
|
||||
}
|
||||
|
||||
assert actor._validate_signal(signal)
|
||||
|
||||
def test_validate_signal_stale(self, nautilus_actor):
|
||||
"""Test signal validation rejects stale signals."""
|
||||
actor, clock = nautilus_actor
|
||||
|
||||
# Set clock to be much later than signal (older than max_signal_age_sec)
|
||||
clock.set_time(1234567890 * 1_000_000_000 + 20_000_000_000) # 20s later
|
||||
|
||||
signal = {
|
||||
'timestamp': 1234567890,
|
||||
'asset': 'BTCUSDT',
|
||||
'direction': 'SHORT',
|
||||
'vel_div': -0.025,
|
||||
'strength': 0.75
|
||||
}
|
||||
|
||||
assert not actor._validate_signal(signal)
|
||||
|
||||
def test_validate_signal_future(self, nautilus_actor):
|
||||
"""Test signal validation rejects future signals."""
|
||||
actor, clock = nautilus_actor
|
||||
|
||||
# Set clock to be before signal timestamp
|
||||
clock.set_time(1234567890 * 1_000_000_000 - 1_000_000_000)
|
||||
|
||||
signal = {
|
||||
'timestamp': 1234567890,
|
||||
'asset': 'BTCUSDT',
|
||||
'direction': 'SHORT',
|
||||
'vel_div': -0.025,
|
||||
'strength': 0.75
|
||||
}
|
||||
|
||||
assert not actor._validate_signal(signal)
|
||||
|
||||
|
||||
class TestSignalBridgeConfig:
|
||||
"""Test SignalBridgeConfig initialization."""
|
||||
|
||||
def test_default_config(self):
|
||||
"""Test default configuration values."""
|
||||
config = SignalBridgeConfig()
|
||||
|
||||
assert config.redis_url == "redis://localhost:6379"
|
||||
assert config.stream_key == "dolphin:signals:stream"
|
||||
assert config.max_signal_age_sec == 10
|
||||
|
||||
def test_custom_config(self):
|
||||
"""Test custom configuration values."""
|
||||
config = SignalBridgeConfig(
|
||||
redis_url="redis://custom:6380",
|
||||
stream_key="custom:stream",
|
||||
max_signal_age_sec=30
|
||||
)
|
||||
|
||||
assert config.redis_url == "redis://custom:6380"
|
||||
assert config.stream_key == "custom:stream"
|
||||
assert config.max_signal_age_sec == 30
|
||||
Reference in New Issue
Block a user