"""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