summaryrefslogtreecommitdiff
path: root/services/strategy-engine/tests/test_macd_strategy.py
blob: e1ae2a334aea60592cff56706abe63343254ce87 (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
75
76
77
78
79
80
"""Tests for the MACD strategy."""
from datetime import datetime, timezone
from decimal import Decimal

import pytest

from shared.models import Candle, OrderSide
from strategies.macd_strategy import MacdStrategy


def _candle(price: float) -> Candle:
    return Candle(
        symbol="BTC/USDT",
        timeframe="1m",
        open_time=datetime(2024, 1, 1, tzinfo=timezone.utc),
        open=Decimal(str(price)),
        high=Decimal(str(price)),
        low=Decimal(str(price)),
        close=Decimal(str(price)),
        volume=Decimal("1.0"),
    )


def _make_strategy(**kwargs) -> MacdStrategy:
    params = {"fast_period": 3, "slow_period": 6, "signal_period": 3, "quantity": "0.01"}
    params.update(kwargs)
    s = MacdStrategy()
    s.configure(params)
    return s


def test_macd_warmup_period():
    s = _make_strategy()
    assert s.warmup_period == 6 + 3  # slow_period + signal_period


def test_macd_no_signal_insufficient_data():
    s = _make_strategy()
    # Feed fewer candles than warmup_period
    for price in [100.0, 101.0, 102.0]:
        result = s.on_candle(_candle(price))
        assert result is None


def test_macd_buy_signal_on_bullish_crossover():
    s = _make_strategy()
    # Declining prices drive MACD histogram negative, then rising prices cross positive
    prices = [100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88]
    prices += [89, 91, 94, 98, 103, 109, 116, 124, 133, 143]
    signal = None
    for p in prices:
        result = s.on_candle(_candle(float(p)))
        if result is not None:
            signal = result
    assert signal is not None, "Expected a BUY signal from bullish crossover"
    assert signal.side == OrderSide.BUY


def test_macd_sell_signal_on_bearish_crossover():
    s = _make_strategy()
    # Rising prices drive MACD histogram positive, then declining prices cross negative
    prices = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112]
    prices += [111, 109, 106, 102, 97, 91, 84, 76, 67, 57]
    signal = None
    for p in prices:
        result = s.on_candle(_candle(float(p)))
        if result is not None:
            signal = result
    assert signal is not None, "Expected a SELL signal from bearish crossover"
    assert signal.side == OrderSide.SELL


def test_macd_reset_clears_state():
    s = _make_strategy()
    for p in [100, 101, 102, 103, 104, 105, 106, 107, 108]:
        s.on_candle(_candle(float(p)))
    assert len(s._closes) > 0
    s.reset()
    assert len(s._closes) == 0
    assert s._prev_histogram is None