initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems: - prod/ (BLUE harness, configs, scripts, docs) - nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved) - adaptive_exit/ (AEM engine + models/bucket_assignments.pkl) - Observability/ (EsoF advisor, TUI, dashboards) - external_factors/ (EsoF producer) - mc_forewarning_qlabs_fork/ (MC regime/envelope) Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
This commit is contained in:
118
nautilus_dolphin/tests/test_strategy.py
Executable file
118
nautilus_dolphin/tests/test_strategy.py
Executable file
@@ -0,0 +1,118 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user