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:
179
nautilus_dolphin/tests/test_smart_exec_algorithm.py
Executable file
179
nautilus_dolphin/tests/test_smart_exec_algorithm.py
Executable file
@@ -0,0 +1,179 @@
|
||||
"""Tests for SmartExecAlgorithm enhancements."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, MagicMock
|
||||
from nautilus_trader.model.enums import OrderSide
|
||||
from nautilus_trader.model.objects import Price
|
||||
|
||||
from nautilus_dolphin.nautilus.smart_exec_algorithm import SmartExecAlgorithmForTesting
|
||||
|
||||
|
||||
class TestSmartExecAlgorithmAbortLogic:
|
||||
"""Tests for the abort logic when price moves against position."""
|
||||
|
||||
def test_should_abort_entry_buy_price_rises(self):
|
||||
"""Test BUY entry aborted when ask rises 5bps above limit."""
|
||||
algo = SmartExecAlgorithmForTesting({'entry_abort_threshold_bps': 5.0})
|
||||
|
||||
# Create mock order (BUY at 50000)
|
||||
order = Mock()
|
||||
order.side = OrderSide.BUY
|
||||
|
||||
# Create mock tick (ask moved up 1% - well above 5bps threshold)
|
||||
tick = Mock()
|
||||
tick.ask_price = Price(50500, 2)
|
||||
tick.bid_price = Price(50499, 2)
|
||||
|
||||
metadata = {'limit_price': 50000.0}
|
||||
|
||||
# Should abort (price moved more than 5bps against)
|
||||
assert algo._should_abort_entry(order, tick, metadata) is True
|
||||
|
||||
def test_should_not_abort_entry_buy_price_stable(self):
|
||||
"""Test BUY entry not aborted when price hasn't moved much."""
|
||||
algo = SmartExecAlgorithmForTesting({'entry_abort_threshold_bps': 5.0})
|
||||
|
||||
order = Mock()
|
||||
order.side = OrderSide.BUY
|
||||
|
||||
# Ask only 2bps higher (below 5bps threshold)
|
||||
tick = Mock()
|
||||
tick.ask_price = Price(50010, 2)
|
||||
tick.bid_price = Price(50009, 2)
|
||||
|
||||
metadata = {'limit_price': 50000.0}
|
||||
|
||||
assert algo._should_abort_entry(order, tick, metadata) is False
|
||||
|
||||
def test_should_abort_entry_sell_price_drops(self):
|
||||
"""Test SELL entry aborted when bid drops 5bps below limit."""
|
||||
algo = SmartExecAlgorithmForTesting({'entry_abort_threshold_bps': 5.0})
|
||||
|
||||
order = Mock()
|
||||
order.side = OrderSide.SELL
|
||||
|
||||
# Bid moved down 1% - well above 5bps threshold
|
||||
tick = Mock()
|
||||
tick.ask_price = Price(49501, 2)
|
||||
tick.bid_price = Price(49500, 2)
|
||||
|
||||
metadata = {'limit_price': 50000.0}
|
||||
|
||||
assert algo._should_abort_entry(order, tick, metadata) is True
|
||||
|
||||
def test_abort_entry_cancels_order(self):
|
||||
"""Test abort_entry cancels the order and updates metrics."""
|
||||
algo = SmartExecAlgorithmForTesting({'entry_abort_threshold_bps': 5.0})
|
||||
|
||||
# Setup pending entry
|
||||
order = Mock()
|
||||
order.is_closed = False
|
||||
algo._pending_entries = {'order1': {'limit_price': 50000.0}}
|
||||
|
||||
algo._abort_entry('order1', {'limit_price': 50000.0})
|
||||
|
||||
assert algo.cancel_order.called
|
||||
assert algo._metrics['entries_aborted'] == 1
|
||||
assert 'order1' not in algo._pending_entries
|
||||
|
||||
|
||||
class TestSmartExecAlgorithmFeeTracking:
|
||||
"""Tests for fee and slippage tracking."""
|
||||
|
||||
def test_fee_calculation_maker(self):
|
||||
"""Test maker fee calculation (0.02%)."""
|
||||
algo = SmartExecAlgorithmForTesting({
|
||||
'maker_fee_rate': 0.0002,
|
||||
'taker_fee_rate': 0.0005,
|
||||
})
|
||||
|
||||
# $50000 notional
|
||||
fee = algo._calculate_fee(50000.0, 'maker')
|
||||
assert fee == 10.0 # 50000 * 0.0002
|
||||
|
||||
def test_fee_calculation_taker(self):
|
||||
"""Test taker fee calculation (0.05%)."""
|
||||
algo = SmartExecAlgorithmForTesting({
|
||||
'maker_fee_rate': 0.0002,
|
||||
'taker_fee_rate': 0.0005,
|
||||
})
|
||||
|
||||
fee = algo._calculate_fee(50000.0, 'taker')
|
||||
assert fee == 25.0 # 50000 * 0.0005
|
||||
|
||||
def test_slippage_calculation(self):
|
||||
"""Test slippage calculation in basis points."""
|
||||
algo = SmartExecAlgorithmForTesting({})
|
||||
|
||||
# BUY: expected 50000, got 50050 (10bps worse)
|
||||
slippage = algo._calculate_slippage_bps(50000.0, 50050.0, OrderSide.BUY)
|
||||
assert slippage == 10.0
|
||||
|
||||
# SELL: expected 50000, got 49950 (10bps worse)
|
||||
slippage = algo._calculate_slippage_bps(50000.0, 49950.0, OrderSide.SELL)
|
||||
assert slippage == 10.0
|
||||
|
||||
def test_get_metrics_summary(self):
|
||||
"""Test metrics summary generation."""
|
||||
algo = SmartExecAlgorithmForTesting({})
|
||||
|
||||
# Add some metrics
|
||||
algo._metrics['entries_maker'] = 8
|
||||
algo._metrics['entries_taker'] = 2
|
||||
algo._metrics['total_fees'] = 100.0
|
||||
algo._metrics['fill_count'] = 10
|
||||
|
||||
summary = algo.get_metrics_summary()
|
||||
|
||||
assert summary['entries']['total'] == 10
|
||||
assert summary['entries']['maker_pct'] == 80.0
|
||||
assert summary['fees']['total'] == 100.0
|
||||
|
||||
def test_reset_metrics(self):
|
||||
"""Test metrics reset functionality."""
|
||||
algo = SmartExecAlgorithmForTesting({})
|
||||
|
||||
# Add some metrics
|
||||
algo._metrics['entries_maker'] = 5
|
||||
algo._metrics['total_fees'] = 50.0
|
||||
|
||||
algo.reset_metrics()
|
||||
|
||||
assert algo._metrics['entries_maker'] == 0
|
||||
assert algo._metrics['total_fees'] == 0.0
|
||||
|
||||
|
||||
class TestSmartExecAlgorithmIntegration:
|
||||
"""Integration tests for fee tracking with fills."""
|
||||
|
||||
def test_entry_fill_tracked_correctly(self):
|
||||
"""Test that entry fills are tracked with correct fee calculation."""
|
||||
algo = SmartExecAlgorithmForTesting({
|
||||
'maker_fee_rate': 0.0002,
|
||||
})
|
||||
|
||||
order = Mock()
|
||||
order.side = OrderSide.BUY
|
||||
order.tags = {'type': 'entry', 'expected_price': 50000.0}
|
||||
|
||||
# Fill at expected price (no slippage)
|
||||
algo._record_fill(order, 50000.0, 1.0, 'maker')
|
||||
|
||||
assert algo._metrics['entries_maker'] == 1
|
||||
assert algo._metrics['total_fees'] == 10.0 # 50000 * 0.0002
|
||||
assert algo._metrics['fill_count'] == 1
|
||||
|
||||
def test_exit_fill_tracked_correctly(self):
|
||||
"""Test that exit fills are tracked separately."""
|
||||
algo = SmartExecAlgorithmForTesting({
|
||||
'maker_fee_rate': 0.0002,
|
||||
})
|
||||
|
||||
order = Mock()
|
||||
order.side = OrderSide.SELL
|
||||
order.tags = {'type': 'exit'}
|
||||
|
||||
algo._record_fill(order, 51000.0, 1.0, 'maker')
|
||||
|
||||
assert algo._metrics['exits_maker'] == 1
|
||||
assert algo._metrics['entries_maker'] == 0 # Entry not counted
|
||||
Reference in New Issue
Block a user