154 lines
5.2 KiB
Python
154 lines
5.2 KiB
Python
|
|
"""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
|