summaryrefslogtreecommitdiff
path: root/services/backtester/tests/test_engine.py
diff options
context:
space:
mode:
Diffstat (limited to 'services/backtester/tests/test_engine.py')
-rw-r--r--services/backtester/tests/test_engine.py74
1 files changed, 74 insertions, 0 deletions
diff --git a/services/backtester/tests/test_engine.py b/services/backtester/tests/test_engine.py
new file mode 100644
index 0000000..1a25e1c
--- /dev/null
+++ b/services/backtester/tests/test_engine.py
@@ -0,0 +1,74 @@
+"""Tests for the BacktestEngine."""
+from datetime import datetime, timezone
+from decimal import Decimal
+from unittest.mock import MagicMock
+
+import pytest
+
+from shared.models import Candle, Signal, OrderSide
+
+from backtester.engine import BacktestEngine, BacktestResult
+
+
+def make_candle(symbol: str, price: float, timeframe: str = "1h") -> Candle:
+ return Candle(
+ symbol=symbol,
+ timeframe=timeframe,
+ open_time=datetime.now(timezone.utc),
+ open=Decimal(str(price)),
+ high=Decimal(str(price * 1.01)),
+ low=Decimal(str(price * 0.99)),
+ close=Decimal(str(price)),
+ volume=Decimal("100"),
+ )
+
+
+def make_candles(prices: list[float], symbol: str = "BTCUSDT") -> list[Candle]:
+ return [make_candle(symbol, p) for p in prices]
+
+
+def make_signal(side: OrderSide, price: str, quantity: str = "0.1") -> Signal:
+ return Signal(
+ strategy="test",
+ symbol="BTCUSDT",
+ side=side,
+ price=Decimal(price),
+ quantity=Decimal(quantity),
+ reason="test",
+ )
+
+
+def test_backtest_engine_runs_strategy_over_candles():
+ strategy = MagicMock()
+ strategy.name = "mock_strategy"
+ strategy.on_candle.return_value = None
+
+ candles = make_candles([50000.0, 51000.0, 52000.0])
+ engine = BacktestEngine(strategy, Decimal("10000"))
+ result = engine.run(candles)
+
+ assert strategy.on_candle.call_count == 3
+ assert result.total_trades == 0
+ assert result.final_balance == Decimal("10000")
+ assert result.strategy_name == "mock_strategy"
+
+
+def test_backtest_engine_executes_signals():
+ buy_signal = make_signal(OrderSide.BUY, "50000", "0.1")
+ sell_signal = make_signal(OrderSide.SELL, "55000", "0.1")
+
+ strategy = MagicMock()
+ strategy.name = "mock_strategy"
+ strategy.on_candle.side_effect = [buy_signal, None, sell_signal]
+
+ candles = make_candles([50000.0, 52000.0, 55000.0])
+ engine = BacktestEngine(strategy, Decimal("10000"))
+ result = engine.run(candles)
+
+ assert result.total_trades == 2
+ # Initial: 10000, bought 0.1 BTC @ 50000 (cost 5000) → balance 5000
+ # Sold 0.1 BTC @ 55000 (proceeds 5500) → balance 10500
+ expected_final = Decimal("10500")
+ assert result.final_balance == expected_final
+ expected_profit = Decimal("500")
+ assert result.profit == expected_profit