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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
"""Tests for extreme value edge cases."""
import sys
from datetime import datetime, timezone
from decimal import Decimal
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "strategy-engine"))
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "backtester" / "src"))
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "order-executor" / "src"))
from shared.models import Candle, Signal, OrderSide
from strategies.rsi_strategy import RsiStrategy
from strategies.vwap_strategy import VwapStrategy
from strategies.bollinger_strategy import BollingerStrategy
from backtester.engine import BacktestEngine
from backtester.simulator import OrderSimulator
from order_executor.risk_manager import RiskManager
def _candle(close: str, volume: str = "1000", idx: int = 0) -> Candle:
from datetime import timedelta
base = datetime(2025, 1, 1, tzinfo=timezone.utc)
return Candle(
symbol="BTCUSDT",
timeframe="1h",
open_time=base + timedelta(hours=idx),
open=Decimal(close),
high=Decimal(close),
low=Decimal(close),
close=Decimal(close),
volume=Decimal(volume),
)
class TestZeroPriceCandle:
"""Strategy should handle Decimal('0') price without crashing."""
def test_rsi_zero_price(self):
strategy = RsiStrategy()
for i in range(20):
strategy.on_candle(_candle("0", idx=i))
# Should not crash; RSI is undefined with constant price
result = strategy.on_candle(_candle("0", idx=20))
# With all-zero prices, diffs are zero, RSI is NaN -> returns None
assert result is None
def test_vwap_zero_price(self):
strategy = VwapStrategy()
for i in range(40):
result = strategy.on_candle(_candle("0", "100", idx=i))
# VWAP of zero-price candles is 0; deviation calc is 0/0 -> should handle gracefully
# This tests the division by vwap when vwap == 0
assert result is None
class TestVeryLargePrice:
"""Strategy should handle extremely large prices."""
def test_rsi_large_price(self):
strategy = RsiStrategy()
for i in range(20):
strategy.on_candle(_candle("999999999", idx=i))
result = strategy.on_candle(_candle("999999999", idx=20))
# Constant large price -> no signal
assert result is None
def test_bollinger_large_price(self):
strategy = BollingerStrategy()
for i in range(25):
strategy.on_candle(_candle("999999999", idx=i))
# Should not overflow; constant price -> std=0, bandwidth=0 -> skip
result = strategy.on_candle(_candle("999999999", idx=25))
assert result is None
class TestZeroInitialBalance:
"""Backtester with Decimal('0') initial balance."""
def test_backtest_zero_balance(self):
strategy = RsiStrategy()
engine = BacktestEngine(strategy, initial_balance=Decimal("0"))
candles = [_candle(str(100 - i), idx=i) for i in range(30)]
result = engine.run(candles)
# Cannot buy with 0 balance, so 0 trades
assert result.total_trades == 0
assert result.final_balance == Decimal("0")
assert result.profit_pct == Decimal("0")
class TestOrderQuantityZero:
"""Order with quantity=0."""
def test_simulator_zero_quantity_buy(self):
sim = OrderSimulator(initial_balance=Decimal("10000"))
signal = Signal(
strategy="test",
symbol="BTCUSDT",
side=OrderSide.BUY,
price=Decimal("50000"),
quantity=Decimal("0"),
reason="test zero qty",
)
result = sim.execute(signal)
# cost = 50000 * 0 = 0; this is technically valid (no cost)
assert result is True
# Balance unchanged
assert sim.balance == Decimal("10000")
def test_simulator_zero_quantity_sell(self):
sim = OrderSimulator(initial_balance=Decimal("10000"))
signal = Signal(
strategy="test",
symbol="BTCUSDT",
side=OrderSide.SELL,
price=Decimal("50000"),
quantity=Decimal("0"),
reason="test zero qty sell",
)
result = sim.execute(signal)
# No position to sell -> rejected
assert result is False
class TestRiskManagerZeroDailyLossLimit:
"""RiskManager with 0% daily loss limit should reject everything on any loss."""
def test_zero_daily_loss_limit_rejects_on_loss(self):
rm = RiskManager(
max_position_size=Decimal("0.5"),
stop_loss_pct=Decimal("5"),
daily_loss_limit_pct=Decimal("0"),
)
signal = Signal(
strategy="test",
symbol="BTCUSDT",
side=OrderSide.BUY,
price=Decimal("50000"),
quantity=Decimal("0.01"),
reason="test",
)
# Any negative pnl with 0% limit -> should reject
result = rm.check(
signal=signal,
balance=Decimal("10000"),
positions={},
daily_pnl=Decimal("-1"), # any loss at all
)
assert result.allowed is False
def test_zero_daily_loss_limit_allows_no_loss(self):
rm = RiskManager(
max_position_size=Decimal("0.5"),
stop_loss_pct=Decimal("5"),
daily_loss_limit_pct=Decimal("0"),
)
signal = Signal(
strategy="test",
symbol="BTCUSDT",
side=OrderSide.BUY,
price=Decimal("100"),
quantity=Decimal("0.01"),
reason="test",
)
# No loss -> should allow
result = rm.check(
signal=signal,
balance=Decimal("10000"),
positions={},
daily_pnl=Decimal("0"),
)
assert result.allowed is True
|