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 stddev_constant_prices_is_zero() {
    let candles: Vec<Candle> = (0..5).map(|i| make_candle(dec!(100), ts(i))).collect();

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

    assert_eq!(series.len(), 3);
    for (_, value) in series.values() {
        assert_eq!(*value, dec!(0));
    }
}

#[test]
fn stddev_basic() {
    // Prices: 10, 12, 14 -> mean = 12, variance = ((10-12)^2 + (12-12)^2 + (14-12)^2) / 3 = 8/3
    // stddev = sqrt(8/3) ≈ 1.633
    let candles: Vec<Candle> = vec![
        make_candle(dec!(10), ts(0)),
        make_candle(dec!(12), ts(1)),
        make_candle(dec!(14), ts(2)),
    ];

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

    assert_eq!(series.len(), 1);
    let value = series.decimal_values()[0];
    // sqrt(8/3) ≈ 1.6329931619
    let diff = (value - dec!(1.6329931619)).abs();
    assert!(diff < dec!(0.0001), "Expected ~1.633, got {}", value);
}

#[test]
fn stddev_insufficient_data() {
    let candles: Vec<Candle> = vec![make_candle(dec!(100), ts(0)), make_candle(dec!(102), ts(1))];

    let stddev = StdDev::new(3).expect("valid StdDev period");
    let result = stddev.compute(&candles);

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

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

#[test]
fn stddev_name() {
    let stddev = StdDev::new(20).expect("valid StdDev period");
    assert_eq!(stddev.name(), "StdDev(20)");
}

#[test]
fn stddev_warmup_period() {
    let stddev = StdDev::new(14).expect("valid StdDev period");
    assert_eq!(stddev.warmup_period(), 14);
}

#[test]
fn stddev_period_accessor() {
    let stddev = StdDev::new(10).expect("valid StdDev period");
    assert_eq!(stddev.period(), 10);
}

#[test]
fn stddev_rolling_window() {
    // Prices: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] - linear with step 10
    // Each window has same stddev because differences are constant
    // For [0,10,20]: mean=10, variance=((0-10)²+(10-10)²+(20-10)²)/3 = 200/3
    // stddev = sqrt(200/3) ≈ 8.165
    let candles: Vec<Candle> = (0..10)
        .map(|i| make_candle(Decimal::from(i * 10), ts(i)))
        .collect();

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

    // 10 candles with period 3 -> 8 values
    assert_eq!(series.len(), 8);

    // All values should be approximately sqrt(200/3) ≈ 8.1649658
    let expected = dec!(8.1649658);
    for (_, v) in series.values() {
        let diff = (*v - expected).abs();
        assert!(diff < dec!(0.001), "StdDev should be ~8.165, got {}", v);
    }
}

#[test]
fn decimal_sqrt_zero() {
    assert_eq!(decimal_sqrt(dec!(0)), dec!(0));
}

#[test]
fn decimal_sqrt_perfect_square() {
    let result = decimal_sqrt(dec!(4));
    let diff = (result - dec!(2)).abs();
    assert!(
        diff < dec!(0.0000001),
        "sqrt(4) should be 2, got {}",
        result
    );
}

#[test]
fn decimal_sqrt_non_perfect() {
    let result = decimal_sqrt(dec!(2));
    // sqrt(2) ≈ 1.41421356
    let diff = (result - dec!(1.41421356)).abs();
    assert!(
        diff < dec!(0.0001),
        "sqrt(2) should be ~1.414, got {}",
        result
    );
}