quant-indicators 0.7.0

Pure indicator math library for trading — MA, RSI, Bollinger, MACD, ATR, HRP
Documentation
use super::*;
use crate::test_helpers::helpers::{make_candle, ts};
use rust_decimal_macros::dec;

#[test]
fn rsi_overbought() {
    // Strong upward movement should give high RSI
    let candles: Vec<Candle> = (0..20)
        .map(|i| make_candle(Decimal::from(100 + i * 2), ts(i)))
        .collect();

    let rsi = Rsi::new(14).expect("valid RSI period");
    let series = rsi.compute(&candles).expect("sufficient data for RSI");
    let values = series.decimal_values();

    // All values should be high (approaching 100) for consistent gains
    for value in &values {
        assert!(
            *value > dec!(70),
            "RSI {} should be > 70 for uptrend",
            value
        );
    }
}

#[test]
fn rsi_oversold() {
    // Strong downward movement should give low RSI
    let candles: Vec<Candle> = (0..20)
        .map(|i| make_candle(Decimal::from(200 - i * 2), ts(i)))
        .collect();

    let rsi = Rsi::new(14).expect("valid RSI period");
    let series = rsi.compute(&candles).expect("sufficient data for RSI");
    let values = series.decimal_values();

    // All values should be low (approaching 0) for consistent losses
    for value in &values {
        assert!(
            *value < dec!(30),
            "RSI {} should be < 30 for downtrend",
            value
        );
    }
}

#[test]
fn rsi_neutral() {
    // Flat prices should give RSI around 50
    let candles: Vec<Candle> = (0..20).map(|i| make_candle(dec!(100), ts(i))).collect();

    let rsi = Rsi::new(14).expect("valid RSI period");
    let series = rsi.compute(&candles).expect("sufficient data for RSI");
    let values = series.decimal_values();

    for value in &values {
        assert_eq!(*value, dec!(50), "RSI should be 50 for no movement");
    }
}

#[test]
fn rsi_range() {
    // RSI should always be between 0 and 100
    let candles: Vec<Candle> = vec![
        make_candle(dec!(100), ts(0)),
        make_candle(dec!(105), ts(1)),
        make_candle(dec!(98), ts(2)),
        make_candle(dec!(110), ts(3)),
        make_candle(dec!(95), ts(4)),
        make_candle(dec!(115), ts(5)),
        make_candle(dec!(90), ts(6)),
    ];

    let rsi = Rsi::new(3).expect("valid RSI period");
    let series = rsi.compute(&candles).expect("sufficient data for RSI");

    for (_, value) in series.values() {
        assert!(
            *value >= Decimal::ZERO && *value <= Decimal::from(100),
            "RSI {} should be in [0, 100]",
            value
        );
    }
}

#[test]
fn rsi_insufficient_data() {
    let candles: Vec<Candle> = (0..5).map(|i| make_candle(dec!(100), ts(i))).collect();

    let rsi = Rsi::new(14).expect("valid RSI period");
    let result = rsi.compute(&candles);

    assert!(matches!(
        result,
        Err(IndicatorError::InsufficientData {
            required: 15,
            actual: 5
        })
    ));
}

#[test]
fn rsi_period_zero() {
    let result = Rsi::new(0);
    assert!(matches!(
        result,
        Err(IndicatorError::InvalidParameter { .. })
    ));
}

#[test]
fn rsi_name() {
    let rsi = Rsi::new(14).expect("valid RSI period");
    assert_eq!(rsi.name(), "RSI(14)");
}

#[test]
fn rsi_warmup_period() {
    let rsi = Rsi::new(14).expect("valid RSI period");
    assert_eq!(rsi.warmup_period(), 15); // period + 1
}