Files
DOLPHIN/nautilus_dolphin/tests/test_strategy.py

119 lines
5.2 KiB
Python
Raw Normal View History

"""Tests for DolphinExecutionStrategy."""
import pytest
from collections import deque
from unittest.mock import Mock
from nautilus_dolphin.nautilus.strategy import DolphinExecutionStrategyForTesting as DolphinExecutionStrategy
def _make_strategy_vol_ready(strategy, high=True):
"""Set vol regime state. high=True → regime is high (vol above p60)."""
strategy._vol_regime_ready = True
strategy._vol_p60 = 0.0001
strategy._current_vol = 0.01 if high else 0.000001
strategy._bar_count_in_day = 200 # past warmup
class TestDolphinExecutionStrategy:
def test_should_trade_vol_regime_filter(self):
"""Layer 2: vol regime not ready → reject."""
strategy = DolphinExecutionStrategy({})
# Default state: _vol_regime_ready=False → vol_regime_not_high
signal = {'vel_div': -0.03, 'asset': 'BTCUSDT', 'irp_alignment': 0.50}
assert strategy._should_trade(signal) == "vol_regime_not_high"
def test_should_trade_irp_filter(self):
"""Layer 3: IRP alignment filter."""
strategy = DolphinExecutionStrategy({})
_make_strategy_vol_ready(strategy)
signal_low_irp = {'vel_div': -0.03, 'asset': 'BTCUSDT', 'irp_alignment': 0.30}
assert strategy._should_trade(signal_low_irp) == "irp_too_low"
signal_good = {'vel_div': -0.03, 'asset': 'BTCUSDT', 'irp_alignment': 0.50}
assert strategy._should_trade(signal_good) == ""
def test_should_trade_direction_contradicted(self):
"""Layer 6: rising BTC price contradicts SHORT signal."""
strategy = DolphinExecutionStrategy({})
_make_strategy_vol_ready(strategy)
# BTC rose: p0=10000 (lookback_bars=7+1 ago) → p_now=10100 → +100bps → contradict
prices = [10000.0] * 5 + [10100.0] * 5
strategy._btc_dc_prices = deque(prices, maxlen=strategy.dc_lookback_bars + 2)
signal = {'vel_div': -0.03, 'asset': 'BTCUSDT', 'irp_alignment': 0.50}
assert strategy._should_trade(signal) == "direction_contradicted"
def test_should_trade_excluded_assets(self):
"""Asset exclusion filter."""
strategy = DolphinExecutionStrategy({})
_make_strategy_vol_ready(strategy)
signal = {'vel_div': -0.03, 'asset': 'TUSDUSDT', 'irp_alignment': 0.50}
assert strategy._should_trade(signal) == "asset_excluded"
def test_should_trade_position_limits(self):
"""Max concurrent positions gate."""
config = {'max_concurrent_positions': 2}
strategy = DolphinExecutionStrategy(config)
_make_strategy_vol_ready(strategy)
strategy.active_positions = {'BTCUSDT': {}, 'ETHUSDT': {}}
signal = {'vel_div': -0.03, 'asset': 'SOLUSDT', 'irp_alignment': 0.50}
assert strategy._should_trade(signal) == "max_positions_reached"
def test_should_trade_existing_position(self):
"""Duplicate position guard."""
strategy = DolphinExecutionStrategy({})
_make_strategy_vol_ready(strategy)
strategy.active_positions = {'BTCUSDT': {}}
signal = {'vel_div': -0.03, 'asset': 'BTCUSDT', 'irp_alignment': 0.50}
assert strategy._should_trade(signal) == "position_already_exists"
def test_calculate_leverage_base(self):
"""Layer 4: cubic-convex dynamic leverage from vel_div."""
config = {'min_leverage': 0.5, 'max_leverage': 5.0, 'leverage_convexity': 3.0}
strategy = DolphinExecutionStrategy(config)
# Near threshold (low strength): vel_div=-0.021 → strength≈0.03
lev_low = strategy.calculate_leverage({'vel_div': -0.021})
assert 0.5 <= lev_low < 1.0
# Near extreme (high strength): vel_div=-0.048 → strength≈0.93
lev_high = strategy.calculate_leverage({'vel_div': -0.048})
assert lev_high > 3.0
def test_calculate_leverage_with_multipliers(self):
"""Layer 5: alpha layer multipliers applied to base leverage."""
config = {'min_leverage': 0.5, 'max_leverage': 5.0, 'leverage_convexity': 3.0}
strategy = DolphinExecutionStrategy(config)
# vel_div = -0.035 → strength = 0.5 → scaled = 0.125 → base_lev = 1.0625
signal = {
'vel_div': -0.035,
'bucket_boost': 1.2,
'streak_mult': 1.1,
'trend_mult': 1.05,
}
lev = strategy.calculate_leverage(signal)
base_lev = 0.5 + (0.5 ** 3.0) * (5.0 - 0.5) # 1.0625
expected = base_lev * 1.2 * 1.1 * 1.05
assert abs(lev - expected) < 0.01
def test_calculate_position_size(self):
"""Position size: balance * fraction * leverage."""
strategy = DolphinExecutionStrategy({'capital_fraction': 0.20})
notional = strategy.calculate_position_size({'vel_div': -0.035}, 10000.0)
assert notional > 0
assert notional <= 10000.0 * 0.5 # sanity cap
def test_position_size_sanity_cap(self):
"""50% of balance hard cap even with high multipliers."""
strategy = DolphinExecutionStrategy({'capital_fraction': 0.50, 'max_leverage': 10.0})
signal = {
'vel_div': -0.08,
'bucket_boost': 2.0,
'streak_mult': 2.0,
'trend_mult': 2.0,
}
notional = strategy.calculate_position_size(signal, 10000.0)
assert notional <= 10000.0 * 0.5