Local changes: Updated model training, removed debug instrumentation, and configuration improvements
This commit is contained in:
2
tests/unit/autopilot/__init__.py
Normal file
2
tests/unit/autopilot/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Tests for autopilot module."""
|
||||
|
||||
175
tests/unit/autopilot/test_intelligent_autopilot.py
Normal file
175
tests/unit/autopilot/test_intelligent_autopilot.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""Tests for intelligent autopilot functionality."""
|
||||
|
||||
import pytest
|
||||
from decimal import Decimal
|
||||
from unittest.mock import Mock, patch, AsyncMock
|
||||
from src.core.database import OrderSide, OrderType
|
||||
|
||||
|
||||
class TestPreFlightValidation:
|
||||
"""Tests for pre-flight order validation."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_autopilot(self):
|
||||
"""Create mock autopilot with necessary attributes."""
|
||||
from src.autopilot.intelligent_autopilot import IntelligentAutopilot
|
||||
|
||||
with patch.object(IntelligentAutopilot, '__init__', lambda x, *args, **kwargs: None):
|
||||
autopilot = IntelligentAutopilot.__new__(IntelligentAutopilot)
|
||||
autopilot.symbol = 'BTC/USD'
|
||||
autopilot.paper_trading = True
|
||||
autopilot.logger = Mock()
|
||||
|
||||
# Mock trading engine
|
||||
autopilot.trading_engine = Mock()
|
||||
autopilot.trading_engine.paper_trading = Mock()
|
||||
autopilot.trading_engine.paper_trading.get_balance.return_value = Decimal('1000.0')
|
||||
autopilot.trading_engine.paper_trading.get_positions.return_value = []
|
||||
|
||||
return autopilot
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_can_execute_order_insufficient_funds(self, mock_autopilot):
|
||||
"""Test that insufficient funds returns False."""
|
||||
mock_autopilot.trading_engine.paper_trading.get_balance.return_value = Decimal('10.0')
|
||||
|
||||
can_execute, reason = await mock_autopilot._can_execute_order(
|
||||
side=OrderSide.BUY,
|
||||
quantity=Decimal('1.0'),
|
||||
price=Decimal('100.0')
|
||||
)
|
||||
|
||||
assert can_execute is False
|
||||
assert 'Insufficient funds' in reason
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_can_execute_order_sufficient_funds(self, mock_autopilot):
|
||||
"""Test that sufficient funds returns True."""
|
||||
mock_autopilot.trading_engine.paper_trading.get_balance.return_value = Decimal('1000.0')
|
||||
|
||||
can_execute, reason = await mock_autopilot._can_execute_order(
|
||||
side=OrderSide.BUY,
|
||||
quantity=Decimal('1.0'),
|
||||
price=Decimal('100.0')
|
||||
)
|
||||
|
||||
assert can_execute is True
|
||||
assert reason == 'OK'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_can_execute_order_no_position_for_sell(self, mock_autopilot):
|
||||
"""Test that SELL without position returns False."""
|
||||
mock_autopilot.trading_engine.paper_trading.get_positions.return_value = []
|
||||
|
||||
can_execute, reason = await mock_autopilot._can_execute_order(
|
||||
side=OrderSide.SELL,
|
||||
quantity=Decimal('1.0'),
|
||||
price=Decimal('100.0')
|
||||
)
|
||||
|
||||
assert can_execute is False
|
||||
assert 'No position to sell' in reason
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_can_execute_order_minimum_value(self, mock_autopilot):
|
||||
"""Test that order below minimum value returns False."""
|
||||
can_execute, reason = await mock_autopilot._can_execute_order(
|
||||
side=OrderSide.BUY,
|
||||
quantity=Decimal('0.001'),
|
||||
price=Decimal('0.10') # Order value = $0.0001
|
||||
)
|
||||
|
||||
assert can_execute is False
|
||||
assert 'below minimum' in reason
|
||||
|
||||
|
||||
class TestSmartOrderTypeSelection:
|
||||
"""Tests for smart order type selection."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_autopilot(self):
|
||||
"""Create mock autopilot for order type tests."""
|
||||
from src.autopilot.intelligent_autopilot import IntelligentAutopilot
|
||||
|
||||
with patch.object(IntelligentAutopilot, '__init__', lambda x, *args, **kwargs: None):
|
||||
autopilot = IntelligentAutopilot.__new__(IntelligentAutopilot)
|
||||
autopilot.logger = Mock()
|
||||
return autopilot
|
||||
|
||||
def test_strong_signal_uses_market(self, mock_autopilot):
|
||||
"""Test that strong signals (>80%) use MARKET orders."""
|
||||
order_type, price = mock_autopilot._determine_order_type_and_price(
|
||||
side=OrderSide.BUY,
|
||||
signal_strength=0.85,
|
||||
current_price=Decimal('100.0'),
|
||||
is_stop_loss=False
|
||||
)
|
||||
|
||||
assert order_type == OrderType.MARKET
|
||||
assert price is None
|
||||
|
||||
def test_normal_signal_uses_limit(self, mock_autopilot):
|
||||
"""Test that normal signals use LIMIT orders."""
|
||||
order_type, price = mock_autopilot._determine_order_type_and_price(
|
||||
side=OrderSide.BUY,
|
||||
signal_strength=0.65,
|
||||
current_price=Decimal('100.0'),
|
||||
is_stop_loss=False
|
||||
)
|
||||
|
||||
assert order_type == OrderType.LIMIT
|
||||
assert price is not None
|
||||
# BUY limit should be below market
|
||||
assert price < Decimal('100.0')
|
||||
|
||||
def test_stop_loss_uses_market(self, mock_autopilot):
|
||||
"""Test that stop-loss exits use MARKET orders."""
|
||||
order_type, price = mock_autopilot._determine_order_type_and_price(
|
||||
side=OrderSide.SELL,
|
||||
signal_strength=0.5,
|
||||
current_price=Decimal('100.0'),
|
||||
is_stop_loss=True
|
||||
)
|
||||
|
||||
assert order_type == OrderType.MARKET
|
||||
assert price is None
|
||||
|
||||
def test_take_profit_uses_limit(self, mock_autopilot):
|
||||
"""Test that take-profit exits can use LIMIT orders."""
|
||||
order_type, price = mock_autopilot._determine_order_type_and_price(
|
||||
side=OrderSide.SELL,
|
||||
signal_strength=0.6,
|
||||
current_price=Decimal('100.0'),
|
||||
is_stop_loss=False
|
||||
)
|
||||
|
||||
assert order_type == OrderType.LIMIT
|
||||
assert price is not None
|
||||
# SELL limit should be above market
|
||||
assert price > Decimal('100.0')
|
||||
|
||||
def test_buy_limit_price_discount(self, mock_autopilot):
|
||||
"""Test BUY LIMIT price is 0.1% below market."""
|
||||
order_type, price = mock_autopilot._determine_order_type_and_price(
|
||||
side=OrderSide.BUY,
|
||||
signal_strength=0.6,
|
||||
current_price=Decimal('1000.00'),
|
||||
is_stop_loss=False
|
||||
)
|
||||
|
||||
# 0.1% discount = 999.00
|
||||
expected = Decimal('999.00')
|
||||
assert price == expected
|
||||
|
||||
def test_sell_limit_price_premium(self, mock_autopilot):
|
||||
"""Test SELL LIMIT price is 0.1% above market."""
|
||||
order_type, price = mock_autopilot._determine_order_type_and_price(
|
||||
side=OrderSide.SELL,
|
||||
signal_strength=0.6,
|
||||
current_price=Decimal('1000.00'),
|
||||
is_stop_loss=False
|
||||
)
|
||||
|
||||
# 0.1% premium = 1001.00
|
||||
expected = Decimal('1001.00')
|
||||
assert price == expected
|
||||
161
tests/unit/autopilot/test_strategy_groups.py
Normal file
161
tests/unit/autopilot/test_strategy_groups.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Tests for strategy grouping module."""
|
||||
|
||||
import pytest
|
||||
from src.autopilot.strategy_groups import (
|
||||
StrategyGroup,
|
||||
STRATEGY_TO_GROUP,
|
||||
GROUP_TO_STRATEGIES,
|
||||
get_strategy_group,
|
||||
get_strategies_in_group,
|
||||
get_all_groups,
|
||||
get_best_strategy_in_group,
|
||||
convert_strategy_to_group_label,
|
||||
)
|
||||
|
||||
|
||||
class TestStrategyGroupMappings:
|
||||
"""Tests for strategy group mappings."""
|
||||
|
||||
def test_all_strategies_have_group(self):
|
||||
"""Verify all registered strategies are mapped to a group."""
|
||||
# These are the strategies registered in src/strategies/__init__.py
|
||||
expected_strategies = [
|
||||
"rsi", "macd", "moving_average", "confirmed", "divergence",
|
||||
"bollinger_mean_reversion", "dca", "grid", "momentum",
|
||||
"consensus", "pairs_trading", "volatility_breakout",
|
||||
"sentiment", "market_making"
|
||||
]
|
||||
|
||||
for strategy in expected_strategies:
|
||||
group = get_strategy_group(strategy)
|
||||
assert group is not None, f"Strategy '{strategy}' is not mapped to any group"
|
||||
|
||||
def test_get_strategy_group_case_insensitive(self):
|
||||
"""Test that strategy lookup is case-insensitive."""
|
||||
assert get_strategy_group("RSI") == get_strategy_group("rsi")
|
||||
assert get_strategy_group("MACD") == get_strategy_group("macd")
|
||||
assert get_strategy_group("Moving_Average") == get_strategy_group("moving_average")
|
||||
|
||||
def test_get_strategy_group_unknown(self):
|
||||
"""Test that unknown strategies return None."""
|
||||
assert get_strategy_group("nonexistent_strategy") is None
|
||||
assert get_strategy_group("") is None
|
||||
|
||||
def test_group_to_strategies_reverse_mapping(self):
|
||||
"""Verify GROUP_TO_STRATEGIES is the reverse of STRATEGY_TO_GROUP."""
|
||||
for strategy, group in STRATEGY_TO_GROUP.items():
|
||||
assert strategy in GROUP_TO_STRATEGIES[group]
|
||||
|
||||
def test_all_groups_have_strategies(self):
|
||||
"""Verify all groups have at least one strategy."""
|
||||
for group in StrategyGroup:
|
||||
strategies = get_strategies_in_group(group)
|
||||
assert len(strategies) > 0, f"Group '{group}' has no strategies"
|
||||
|
||||
|
||||
class TestGetAllGroups:
|
||||
"""Tests for get_all_groups function."""
|
||||
|
||||
def test_returns_all_groups(self):
|
||||
"""Verify all groups are returned."""
|
||||
groups = get_all_groups()
|
||||
assert len(groups) == 5
|
||||
assert StrategyGroup.TREND_FOLLOWING in groups
|
||||
assert StrategyGroup.MEAN_REVERSION in groups
|
||||
assert StrategyGroup.MOMENTUM in groups
|
||||
assert StrategyGroup.MARKET_MAKING in groups
|
||||
assert StrategyGroup.SENTIMENT_BASED in groups
|
||||
|
||||
|
||||
class TestGetStrategiesInGroup:
|
||||
"""Tests for get_strategies_in_group function."""
|
||||
|
||||
def test_trend_following_strategies(self):
|
||||
"""Test trend following group contains expected strategies."""
|
||||
strategies = get_strategies_in_group(StrategyGroup.TREND_FOLLOWING)
|
||||
assert "moving_average" in strategies
|
||||
assert "macd" in strategies
|
||||
assert "confirmed" in strategies
|
||||
|
||||
def test_mean_reversion_strategies(self):
|
||||
"""Test mean reversion group contains expected strategies."""
|
||||
strategies = get_strategies_in_group(StrategyGroup.MEAN_REVERSION)
|
||||
assert "rsi" in strategies
|
||||
assert "bollinger_mean_reversion" in strategies
|
||||
assert "grid" in strategies
|
||||
|
||||
def test_momentum_strategies(self):
|
||||
"""Test momentum group contains expected strategies."""
|
||||
strategies = get_strategies_in_group(StrategyGroup.MOMENTUM)
|
||||
assert "momentum" in strategies
|
||||
assert "volatility_breakout" in strategies
|
||||
|
||||
|
||||
class TestGetBestStrategyInGroup:
|
||||
"""Tests for get_best_strategy_in_group function."""
|
||||
|
||||
def test_trend_following_high_adx(self):
|
||||
"""Test trend following selection with high ADX."""
|
||||
features = {"adx": 35.0, "rsi": 50.0, "atr_percent": 2.0, "volume_ratio": 1.0}
|
||||
strategy, confidence = get_best_strategy_in_group(
|
||||
StrategyGroup.TREND_FOLLOWING, features
|
||||
)
|
||||
assert strategy == "confirmed"
|
||||
assert confidence > 0.5
|
||||
|
||||
def test_mean_reversion_extreme_rsi(self):
|
||||
"""Test mean reversion selection with extreme RSI."""
|
||||
features = {"adx": 15.0, "rsi": 25.0, "atr_percent": 1.5, "volume_ratio": 1.0}
|
||||
strategy, confidence = get_best_strategy_in_group(
|
||||
StrategyGroup.MEAN_REVERSION, features
|
||||
)
|
||||
assert strategy == "rsi"
|
||||
assert confidence > 0.5
|
||||
|
||||
def test_momentum_high_volume(self):
|
||||
"""Test momentum selection with high volume."""
|
||||
features = {"adx": 25.0, "rsi": 55.0, "atr_percent": 3.0, "volume_ratio": 2.0}
|
||||
strategy, confidence = get_best_strategy_in_group(
|
||||
StrategyGroup.MOMENTUM, features
|
||||
)
|
||||
assert strategy == "volatility_breakout"
|
||||
assert confidence > 0.5
|
||||
|
||||
def test_respects_available_strategies(self):
|
||||
"""Test that only available strategies are selected."""
|
||||
features = {"adx": 35.0, "rsi": 50.0}
|
||||
# Only MACD available from trend following
|
||||
strategy, confidence = get_best_strategy_in_group(
|
||||
StrategyGroup.TREND_FOLLOWING,
|
||||
features,
|
||||
available_strategies=["macd"]
|
||||
)
|
||||
assert strategy == "macd"
|
||||
|
||||
def test_fallback_when_no_strategies_available(self):
|
||||
"""Test fallback when no strategies in group are available."""
|
||||
features = {"adx": 25.0, "rsi": 50.0}
|
||||
strategy, confidence = get_best_strategy_in_group(
|
||||
StrategyGroup.TREND_FOLLOWING,
|
||||
features,
|
||||
available_strategies=["some_other_strategy"]
|
||||
)
|
||||
# Should return safe default
|
||||
assert strategy == "rsi"
|
||||
assert confidence == 0.5
|
||||
|
||||
|
||||
class TestConvertStrategyToGroupLabel:
|
||||
"""Tests for convert_strategy_to_group_label function."""
|
||||
|
||||
def test_converts_known_strategies(self):
|
||||
"""Test conversion of known strategies."""
|
||||
assert convert_strategy_to_group_label("rsi") == "mean_reversion"
|
||||
assert convert_strategy_to_group_label("macd") == "trend_following"
|
||||
assert convert_strategy_to_group_label("momentum") == "momentum"
|
||||
assert convert_strategy_to_group_label("dca") == "market_making"
|
||||
assert convert_strategy_to_group_label("sentiment") == "sentiment_based"
|
||||
|
||||
def test_unknown_strategy_returns_original(self):
|
||||
"""Test that unknown strategies return original name."""
|
||||
assert convert_strategy_to_group_label("unknown") == "unknown"
|
||||
Reference in New Issue
Block a user