1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
|