summaryrefslogtreecommitdiff
path: root/services/backtester/tests/test_engine.py
blob: 1a25e1c1e50873fe7dc630c637474efc3a985e3d (plain)
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