Files
crypto_trader/tests/unit/strategies/test_pairs_trading.py

90 lines
2.9 KiB
Python
Raw Normal View History

import pytest
import pandas as pd
import numpy as np
from unittest.mock import MagicMock, AsyncMock, patch
from decimal import Decimal
from src.strategies.technical.pairs_trading import PairsTradingStrategy
from src.strategies.base import SignalType
@pytest.fixture
def mock_pricing_service():
service = MagicMock()
service.get_ohlcv = MagicMock()
return service
@pytest.fixture
def strategy(mock_pricing_service):
with patch('src.strategies.technical.pairs_trading.get_pricing_service', return_value=mock_pricing_service):
params = {
'second_symbol': 'AVAX/USD',
'lookback_period': 5,
'z_score_threshold': 1.5,
'symbol': 'SOL/USD'
}
strat = PairsTradingStrategy("test_pairs", params)
strat.enabled = True
return strat
@pytest.mark.asyncio
async def test_pairs_trading_short_spread_signal(strategy, mock_pricing_service):
# Setup Data
# Scenario: SOL (A) pumps relative to AVAX (B) -> Spread widens -> Z-Score High -> Sell A / Buy B
# Prices for A (SOL): 100, 100, 100, 100, 120 (Pump)
ohlcv_a = [
[0, 100, 100, 100, 100, 1000],
[0, 100, 100, 100, 100, 1000],
[0, 100, 100, 100, 100, 1000],
[0, 100, 100, 100, 100, 1000],
[0, 120, 120, 120, 120, 1000],
]
# Prices for B (AVAX): 25, 25, 25, 25, 25 (Flat)
ohlcv_b = [
[0, 25, 25, 25, 25, 1000],
[0, 25, 25, 25, 25, 1000],
[0, 25, 25, 25, 25, 1000],
[0, 25, 25, 25, 25, 1000],
[0, 25, 25, 25, 25, 1000],
]
# Spread: 4, 4, 4, 4, 4.8
# Mean: 4.16, StdDev: approx small but let's see.
# Actually StdDev will be non-zero because of the last value.
mock_pricing_service.get_ohlcv.side_effect = [ohlcv_a, ohlcv_b]
# Execute
signal = await strategy.on_tick("SOL/USD", Decimal(120), "1h", {})
# Verify
assert signal is not None
assert signal.signal_type == SignalType.SELL # Sell Primary (SOL)
assert signal.metadata['secondary_action'] == 'buy' # Buy Secondary (AVAX)
assert signal.metadata['z_score'] > 1.5
@pytest.mark.asyncio
async def test_pairs_trading_long_spread_signal(strategy, mock_pricing_service):
# Scenario: SOL (A) dumps -> Spread drops -> Z-Score Low -> Buy A / Sell B
ohlcv_a = [
[0, 100, 100, 100, 100, 1000],
[0, 100, 100, 100, 100, 1000],
[0, 100, 100, 100, 100, 1000],
[0, 100, 100, 100, 100, 1000],
[0, 80, 80, 80, 80, 1000], # Dump
]
ohlcv_b = [
[0, 25, 25, 25, 25, 1000] for _ in range(5)
]
mock_pricing_service.get_ohlcv.side_effect = [ohlcv_a, ohlcv_b]
signal = await strategy.on_tick("SOL/USD", Decimal(80), "1h", {})
assert signal is not None
assert signal.signal_type == SignalType.BUY # Buy Primary
assert signal.metadata['secondary_action'] == 'sell' # Sell Secondary
assert signal.metadata['z_score'] < -1.5