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

90 lines
2.9 KiB
Python
Raw Permalink 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