Skip to content

Commit

Permalink
Increase code coverage for OrderStatusTracker and GridTradingStrategy
Browse files Browse the repository at this point in the history
  • Loading branch information
jordantete committed Jan 2, 2025
1 parent 25768fa commit 160f566
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 3 deletions.
114 changes: 112 additions & 2 deletions tests/order_handling/test_order_status_tracker.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import pytest
import pytest, asyncio
from unittest.mock import AsyncMock, Mock, patch
from core.order_handling.order_status_tracker import OrderStatusTracker
from core.bot_management.event_bus import Events
Expand Down Expand Up @@ -112,4 +112,114 @@ def test_handle_order_status_change_unhandled_status(self, setup_tracker):
with patch.object(tracker.logger, "warning") as mock_logger_warning:
tracker._handle_order_status_change(mock_local_order, mock_remote_order)

mock_logger_warning.assert_called_once_with("Unhandled order status 'unexpected_status' for order order_1.")
mock_logger_warning.assert_called_once_with("Unhandled order status 'unexpected_status' for order order_1.")

@pytest.mark.asyncio
async def test_start_tracking_creates_monitoring_task(self, setup_tracker):
tracker, _, _, _ = setup_tracker

tracker.start_tracking()
assert tracker._monitoring_task is not None
assert not tracker._monitoring_task.done()

await tracker.stop_tracking()

@pytest.mark.asyncio
async def test_start_tracking_warns_if_already_running(self, setup_tracker):
tracker, _, _, _ = setup_tracker

tracker.start_tracking()
with patch.object(tracker.logger, "warning") as mock_logger_warning:
tracker.start_tracking()
mock_logger_warning.assert_called_once_with("OrderStatusTracker is already running.")

await tracker.stop_tracking()

@pytest.mark.asyncio
async def test_stop_tracking_cancels_monitoring_task(self, setup_tracker):
tracker, _, _, _ = setup_tracker

tracker.start_tracking()
assert tracker._monitoring_task is not None

await tracker.stop_tracking()
assert tracker._monitoring_task is None

@pytest.mark.asyncio
async def test_track_open_order_statuses_handles_cancellation(self, setup_tracker):
tracker, _, _, _ = setup_tracker

tracker.start_tracking()
await asyncio.sleep(0.1)

await tracker.stop_tracking()

assert tracker._monitoring_task is None

@pytest.mark.asyncio
async def test_track_open_order_statuses_handles_unexpected_error(self, setup_tracker):
tracker, order_book, _, _ = setup_tracker

order_book.get_open_orders.side_effect = Exception("Unexpected error")
monitoring_task = asyncio.create_task(tracker._track_open_order_statuses())

await asyncio.sleep(0.1)

monitoring_task.cancel()
try:
await monitoring_task
except asyncio.CancelledError:
pass

@pytest.mark.asyncio
async def test_cancel_active_tasks(self, setup_tracker):
tracker, _, _, _ = setup_tracker

async def dummy_task():
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
pass

task1 = tracker._create_task(dummy_task())
task2 = tracker._create_task(dummy_task())

assert len(tracker._active_tasks) == 2

await tracker._cancel_active_tasks()

assert len(tracker._active_tasks) == 0
assert task1.cancelled()
assert task2.cancelled()

@pytest.mark.asyncio
async def test_create_task_adds_to_active_tasks(self, setup_tracker):
tracker, _, _, _ = setup_tracker

async def dummy_coro():
await asyncio.sleep(0.1)

task = tracker._create_task(dummy_coro())

assert task in tracker._active_tasks
await task
assert task not in tracker._active_tasks

@pytest.mark.asyncio
async def test_process_open_orders_with_multiple_orders(self, setup_tracker):
tracker, order_book, order_execution_strategy, _ = setup_tracker

mock_order1 = Mock(identifier="order_1", symbol="BTC/USDT", status=OrderStatus.OPEN)
mock_order2 = Mock(identifier="order_2", symbol="ETH/USDT", status=OrderStatus.OPEN)

mock_remote_order1 = Mock(identifier="order_1", symbol="BTC/USDT", status=OrderStatus.CLOSED)
mock_remote_order2 = Mock(identifier="order_2", symbol="ETH/USDT", status=OrderStatus.CANCELED)

order_book.get_open_orders.return_value = [mock_order1, mock_order2]
order_execution_strategy.get_order = AsyncMock(side_effect=[mock_remote_order1, mock_remote_order2])
tracker._handle_order_status_change = Mock()

await tracker._process_open_orders()

tracker._handle_order_status_change.assert_any_call(mock_order1, mock_remote_order1)
tracker._handle_order_status_change.assert_any_call(mock_order2, mock_remote_order2)
128 changes: 127 additions & 1 deletion tests/strategies/test_grid_trading_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,130 @@ def test_plot_results_not_available_in_live_mode(self, setup_strategy, caplog):
strategy.plot_results()

assert "Plotting is not available for live/paper trading mode." in [record.message for record in caplog.records]
plotter.plot_results.assert_not_called()
plotter.plot_results.assert_not_called()

@pytest.mark.asyncio
async def test_initialize_historical_data_live_mode(self, setup_strategy):
create_strategy, _, _, _, _, _, _, _, _ = setup_strategy
strategy = create_strategy(TradingMode.LIVE)

result = strategy._initialize_historical_data()
assert result is None

@pytest.mark.asyncio
async def test_initialize_historical_data_backtest_mode(self, setup_strategy):
create_strategy, config_manager, exchange_service, _, _, _, _, _, _ = setup_strategy

# Mock the configuration values
config_manager.get_timeframe.return_value = "1h"
config_manager.get_start_date.return_value = "2024-01-01"
config_manager.get_end_date.return_value = "2024-01-02"

# Mock the exchange service response
mock_data = pd.DataFrame({'close': [100, 200, 300]})
exchange_service.fetch_ohlcv.return_value = mock_data

# Create strategy after setting up the mocks
strategy = create_strategy(TradingMode.BACKTEST)

# Reset the mock to clear the call from initialization
exchange_service.fetch_ohlcv.reset_mock()

# Now test the method directly
result = strategy._initialize_historical_data()

assert result is not None
assert isinstance(result, pd.DataFrame)
exchange_service.fetch_ohlcv.assert_called_once_with(
"BTC/USDT", "1h", "2024-01-01", "2024-01-02"
)

@pytest.mark.asyncio
async def test_initialize_historical_data_error(self, setup_strategy, caplog):
create_strategy, _, exchange_service, _, _, _, _, _, _ = setup_strategy
strategy = create_strategy(TradingMode.BACKTEST)

# Mock the exchange service to raise an exception
exchange_service.fetch_ohlcv.side_effect = Exception("Failed to fetch data")

with caplog.at_level(logging.ERROR):
result = strategy._initialize_historical_data()

assert result is None
assert "Failed to initialize data for backtest trading mode" in caplog.text

@pytest.mark.asyncio
async def test_evaluate_tp_or_sl_no_crypto_balance(self, setup_strategy):
create_strategy, _, _, _, _, balance_tracker, _, _, _ = setup_strategy
strategy = create_strategy()

balance_tracker.crypto_balance = 0

result = await strategy._evaluate_tp_or_sl(current_price=15000)
assert result is False

@pytest.mark.asyncio
async def test_handle_take_profit_triggered(self, setup_strategy):
create_strategy, config_manager, _, _, order_manager, balance_tracker, _, _, _ = setup_strategy
strategy = create_strategy()

config_manager.is_take_profit_enabled.return_value = True
config_manager.get_take_profit_threshold.return_value = 20000
balance_tracker.crypto_balance = 1
order_manager.execute_take_profit_or_stop_loss_order = AsyncMock()

result = await strategy._handle_take_profit(current_price=21000)

assert result is True
order_manager.execute_take_profit_or_stop_loss_order.assert_called_once_with(
current_price=21000, take_profit_order=True
)

@pytest.mark.asyncio
async def test_handle_stop_loss_triggered(self, setup_strategy):
create_strategy, config_manager, _, _, order_manager, balance_tracker, _, _, _ = setup_strategy
strategy = create_strategy()

config_manager.is_stop_loss_enabled.return_value = True
config_manager.get_stop_loss_threshold.return_value = 10000
balance_tracker.crypto_balance = 1
order_manager.execute_take_profit_or_stop_loss_order = AsyncMock()

result = await strategy._handle_stop_loss(current_price=9000)

assert result is True
order_manager.execute_take_profit_or_stop_loss_order.assert_called_once_with(
current_price=9000, stop_loss_order=True
)

@pytest.mark.asyncio
async def test_initialize_grid_orders_once_first_time(self, setup_strategy):
create_strategy, _, _, grid_manager, order_manager, _, _, _, _ = setup_strategy
strategy = create_strategy()

grid_manager.get_trigger_price.return_value = 15000
order_manager.perform_initial_purchase = AsyncMock()
order_manager.initialize_grid_orders = AsyncMock()

result = await strategy._initialize_grid_orders_once(
current_price=15100,
trigger_price=15000,
grid_orders_initialized=False,
last_price=14900
)

assert result is True
order_manager.perform_initial_purchase.assert_called_once_with(15100)
order_manager.initialize_grid_orders.assert_called_once_with(15100)

def test_get_formatted_orders(self, setup_strategy):
create_strategy, _, _, _, _, _, trading_performance_analyzer, _, _ = setup_strategy
strategy = create_strategy()

mock_orders = ["Order1", "Order2"]
trading_performance_analyzer.get_formatted_orders.return_value = mock_orders

result = strategy.get_formatted_orders()

assert result == mock_orders
trading_performance_analyzer.get_formatted_orders.assert_called_once()

0 comments on commit 160f566

Please sign in to comment.