Files
DOLPHIN/nautilus_dolphin/tests/test_redis_integration.py

325 lines
12 KiB
Python
Raw Normal View History

"""Redis Integration Tests for SignalBridgeActor.
Uses fakeredis to provide a mock Redis server for testing.
This tests the full signal flow from Redis to Nautilus.
"""
import asyncio
import json
import pytest
from unittest.mock import Mock, patch, PropertyMock
from datetime import datetime
class TestRedisConnection:
"""Test Redis connection and basic operations."""
def test_fakeredis_available(self):
"""Test that fakeredis is available and working."""
import fakeredis
# Create a fake Redis server
server = fakeredis.FakeServer()
r = fakeredis.FakeStrictRedis(server=server)
# Test basic operations
r.set('test_key', 'test_value')
value = r.get('test_key')
assert value.decode() == 'test_value'
def test_fakeredis_streams(self):
"""Test that fakeredis supports Redis Streams."""
import fakeredis
server = fakeredis.FakeServer()
r = fakeredis.FakeStrictRedis(server=server)
# Add entry to stream
r.xadd('test_stream', {'data': 'value1'})
r.xadd('test_stream', {'data': 'value2'})
# Read from stream
messages = r.xread({'test_stream': '0'}, count=10)
assert len(messages) == 1
stream_name, entries = messages[0]
assert len(entries) == 2
class TestSignalBridgeWithRedis:
"""Test SignalBridgeActor with Redis integration."""
@pytest.fixture
def redis_setup(self):
"""Set up fake Redis server and SignalBridgeActor."""
import fakeredis
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeActor, SignalBridgeConfig
from nautilus_trader.common.component import TestClock
# Create fake Redis server
server = fakeredis.FakeServer()
fake_redis = fakeredis.FakeStrictRedis(server=server)
# Create SignalBridgeActor with mocked Redis
config = SignalBridgeConfig(
redis_url='redis://localhost:6379',
stream_key='dolphin:signals:stream',
max_signal_age_sec=60
)
actor = SignalBridgeActor(config)
# Mock the clock
clock = TestClock()
clock.set_time(int(datetime.now().timestamp() * 1e9))
with patch.object(type(actor), 'clock', new_callable=PropertyMock) as mock_clock:
mock_clock.return_value = clock
# Replace Redis connection with fake
actor._redis = fake_redis
yield actor, fake_redis, clock
@pytest.mark.asyncio
async def test_signal_bridge_consumes_signal(self, redis_setup):
"""Test that SignalBridgeActor consumes signals from Redis."""
actor, fake_redis, clock = redis_setup
# Create a valid signal
signal = {
'timestamp': int(clock.timestamp_ns() / 1e9), # seconds
'asset': 'BTCUSDT',
'direction': 'SHORT',
'vel_div': -0.025,
'strength': 0.75,
'irp_alignment': 0.5,
'direction_confirm': True,
'lookback_momentum': 0.0001
}
# Add signal to Redis stream
fake_redis.xadd(
'dolphin:signals:stream',
{'signal': json.dumps(signal)}
)
# Manually call _consume_stream (simulated)
# Read from stream
messages = fake_redis.xread({'dolphin:signals:stream': '0'}, count=10)
assert len(messages) == 1
stream_name, entries = messages[0]
assert len(entries) == 1
@pytest.mark.asyncio
async def test_signal_bridge_validates_signal(self, redis_setup):
"""Test signal validation in SignalBridgeActor."""
actor, fake_redis, clock = redis_setup
# Create a valid signal
valid_signal = {
'timestamp': int(clock.timestamp_ns() / 1e9),
'asset': 'BTCUSDT',
'direction': 'SHORT',
'vel_div': -0.025,
'strength': 0.75,
'irp_alignment': 0.5,
'direction_confirm': True,
'lookback_momentum': 0.0001
}
# Test validation
is_valid = actor._validate_signal(valid_signal)
assert is_valid is True
# Test invalid signal (missing field)
invalid_signal = {
'timestamp': int(clock.timestamp_ns() / 1e9),
'asset': 'BTCUSDT'
}
is_valid = actor._validate_signal(invalid_signal)
assert is_valid is False
@pytest.mark.asyncio
async def test_signal_bridge_rejects_stale_signal(self, redis_setup):
"""Test that stale signals are rejected."""
actor, fake_redis, clock = redis_setup
# Create a stale signal (70 seconds old, older than max_signal_age_sec=60)
stale_signal = {
'timestamp': int((clock.timestamp_ns() / 1e9) - 70),
'asset': 'BTCUSDT',
'direction': 'SHORT',
'vel_div': -0.025,
'strength': 0.75,
'irp_alignment': 0.5,
'direction_confirm': True,
'lookback_momentum': 0.0001
}
is_valid = actor._validate_signal(stale_signal)
assert is_valid is False
class TestSignalFlow:
"""Test complete signal flow from Redis to strategy."""
@pytest.mark.asyncio
async def test_full_signal_flow(self):
"""Test complete signal flow: Redis -> SignalBridge -> Strategy."""
import fakeredis
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeActor, SignalBridgeConfig
from nautilus_dolphin.nautilus.strategy import DolphinExecutionStrategyForTesting
from nautilus_trader.common.component import TestClock
# Set up fake Redis
server = fakeredis.FakeServer()
fake_redis = fakeredis.FakeStrictRedis(server=server)
# Create SignalBridgeActor
bridge_config = SignalBridgeConfig(
redis_url='redis://localhost:6379',
stream_key='dolphin:signals:stream',
max_signal_age_sec=60
)
bridge_actor = SignalBridgeActor(bridge_config)
bridge_actor._redis = fake_redis
# Create strategy
strategy_config = {
'venue': 'BINANCE_FUTURES',
'acb_enabled': True,
'max_leverage': 5.0
}
strategy = DolphinExecutionStrategyForTesting(strategy_config)
# Set up clock
clock = TestClock()
clock.set_time(int(datetime.now().timestamp() * 1e9))
with patch.object(type(bridge_actor), 'clock', new_callable=PropertyMock) as mock_clock:
mock_clock.return_value = clock
# Create and publish signal to Redis
signal = {
'timestamp': int(clock.timestamp_ns() / 1e9),
'asset': 'BTCUSDT',
'direction': 'SHORT',
'vel_div': -0.025,
'strength': 0.75,
'irp_alignment': 0.5,
'direction_confirm': True,
'lookback_momentum': 0.0001,
'price': 50000.0
}
fake_redis.xadd(
'dolphin:signals:stream',
{'signal': json.dumps(signal)}
)
# Verify signal is in Redis
messages = fake_redis.xread({'dolphin:signals:stream': '0'}, count=10)
assert len(messages) == 1
# Verify strategy would process it (filters)
can_trade = strategy._should_trade(signal)
assert can_trade == "" # Empty string means can trade
@pytest.mark.asyncio
async def test_multiple_signals_processing(self):
"""Test processing multiple signals from Redis."""
import fakeredis
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeActor, SignalBridgeConfig
from nautilus_trader.common.component import TestClock
# Set up fake Redis
server = fakeredis.FakeServer()
fake_redis = fakeredis.FakeStrictRedis(server=server)
# Create SignalBridgeActor
bridge_config = SignalBridgeConfig(
redis_url='redis://localhost:6379',
stream_key='dolphin:signals:stream',
max_signal_age_sec=60
)
bridge_actor = SignalBridgeActor(bridge_config)
bridge_actor._redis = fake_redis
clock = TestClock()
clock.set_time(int(datetime.now().timestamp() * 1e9))
with patch.object(type(bridge_actor), 'clock', new_callable=PropertyMock) as mock_clock:
mock_clock.return_value = clock
# Publish multiple signals
assets = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT']
for i, asset in enumerate(assets):
signal = {
'timestamp': int(clock.timestamp_ns() / 1e9),
'asset': asset,
'direction': 'SHORT',
'vel_div': -0.025 - (i * 0.01),
'strength': 0.75,
'irp_alignment': 0.5,
'direction_confirm': True,
'lookback_momentum': 0.0001,
'price': 50000.0 - (i * 1000)
}
fake_redis.xadd(
'dolphin:signals:stream',
{'signal': json.dumps(signal)}
)
# Verify all signals are in Redis
messages = fake_redis.xread({'dolphin:signals:stream': '0'}, count=10)
assert len(messages) == 1
stream_name, entries = messages[0]
assert len(entries) == 3
class TestRedisConnectionConfig:
"""Test Redis connection configuration."""
def test_signal_bridge_config_defaults(self):
"""Test SignalBridgeConfig default values."""
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeConfig
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_signal_bridge_config_custom(self):
"""Test SignalBridgeConfig custom values."""
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeConfig
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
def test_config_yaml_matches(self):
"""Test that config.yaml matches SignalBridgeConfig."""
import yaml
from nautilus_dolphin.nautilus.signal_bridge import SignalBridgeConfig
# Load config.yaml
with open('config/config.yaml', 'r') as f:
config = yaml.safe_load(f)
signal_bridge_config = config.get('signal_bridge', {})
# Verify keys match
assert 'redis_url' in signal_bridge_config
assert 'stream_key' in signal_bridge_config
assert 'max_signal_age_sec' in signal_bridge_config