from __future__ import annotations import pytest from app.engine.indicators import atr, ema, macd, roc, rolling_std, rsi, sma from app.schemas import Candle def candle(high: float, low: float, close: float, open_: float | None = None) -> Candle: return Candle( ts=0, open=open_ if open_ is not None else close, high=high, low=low, close=close, volume=1, ) def test_sma() -> None: assert sma([1, 2, 3, 4, 5], 3) == [None, None, 2.0, 3.0, 4.0] def test_ema() -> None: assert ema([1, 2, 3, 4, 5], 3) == [None, None, 2.0, 3.0, 4.0] def test_rsi_monotonic_is_100() -> None: values = [float(i) for i in range(1, 20)] assert rsi(values, 14)[-1] == pytest.approx(100.0) def test_atr_flat_market_is_zero() -> None: candles = [candle(100, 100, 100) for _ in range(20)] assert atr(candles, 5)[-1] == pytest.approx(0.0) def test_rolling_std() -> None: result = rolling_std([1, 1, 1, 1], 2) assert result[-1] == pytest.approx(0.0) def test_roc() -> None: values = [100.0, 110.0, 121.0, 100.0] out = roc(values, 2) assert out[0] is None and out[1] is None assert out[2] == pytest.approx(0.21) assert out[3] == pytest.approx(-0.09090909, rel=1e-4) def test_macd_shapes_and_crossover_sign() -> None: rising = [float(i) for i in range(1, 80)] macd_line, signal_line, histogram = macd(rising, 12, 26, 9) assert len(macd_line) == len(rising) assert macd_line[-1] is not None and signal_line[-1] is not None # In a steady uptrend MACD is positive (but can be below its signal after lag). assert macd_line[-1] > 0 assert histogram[-1] == pytest.approx(macd_line[-1] - signal_line[-1]) falling = [float(i) for i in range(80, 0, -1)] macd_falling, _, _ = macd(falling, 12, 26, 9) assert macd_falling[-1] < 0