"""Tests for the EMA Crossover strategy.""" from datetime import datetime, timezone from decimal import Decimal import pytest from shared.models import Candle, OrderSide from strategies.ema_crossover_strategy import EmaCrossoverStrategy def make_candle(close: float) -> Candle: return Candle( symbol="BTC/USDT", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(close)), high=Decimal(str(close)), low=Decimal(str(close)), close=Decimal(str(close)), volume=Decimal("1.0"), ) def _make_strategy(short: int = 3, long: int = 6) -> EmaCrossoverStrategy: s = EmaCrossoverStrategy() s.configure({"short_period": short, "long_period": long, "quantity": "0.01"}) return s def test_ema_warmup_period(): strategy = _make_strategy(short=3, long=6) assert strategy.warmup_period == 6 def test_ema_no_signal_insufficient_data(): strategy = _make_strategy(short=3, long=6) # Feed fewer candles than warmup_period for price in [100, 101, 102, 103, 104]: result = strategy.on_candle(make_candle(price)) assert result is None def test_ema_buy_signal_golden_cross(): strategy = _make_strategy(short=3, long=6) # Declining prices so short EMA stays below long EMA declining = [100, 98, 96, 94, 92, 90, 88, 86, 84, 82] for price in declining: strategy.on_candle(make_candle(price)) # Sharp rise to force short EMA above long EMA (golden cross) rising = [120, 140, 160] signal = None for price in rising: result = strategy.on_candle(make_candle(price)) if result is not None: signal = result assert signal is not None assert signal.side == OrderSide.BUY assert "Golden Cross" in signal.reason def test_ema_sell_signal_death_cross(): strategy = _make_strategy(short=3, long=6) # Rising prices so short EMA stays above long EMA rising = [80, 82, 84, 86, 88, 90, 92, 94, 96, 98] for price in rising: strategy.on_candle(make_candle(price)) # Sharp decline to force short EMA below long EMA (death cross) declining = [60, 40, 20] signal = None for price in declining: result = strategy.on_candle(make_candle(price)) if result is not None: signal = result assert signal is not None assert signal.side == OrderSide.SELL assert "Death Cross" in signal.reason def test_ema_reset_clears_state(): strategy = _make_strategy(short=3, long=6) # Feed enough data to produce a signal for price in [100, 98, 96, 94, 92, 90, 88, 86, 84, 82]: strategy.on_candle(make_candle(price)) strategy.reset() # After reset, insufficient data again — no signal result = strategy.on_candle(make_candle(100)) assert result is None # Internal state should be cleared assert len(strategy._closes) == 1 assert strategy._prev_short_above is None