libitofin 0.52.1

A ground-up Rust port of QuantLib: quantitative-finance primitives for pricing, risk, and numerical methods.
Documentation
use super::*;

fn rational(value: &str) -> f64 {
    let (n, d) = value.split_once('/').unwrap_or((value, "1"));
    n.parse::<f64>().unwrap() / d.parse::<f64>().unwrap()
}

#[test]
fn adx_independent_step_fixture_and_transition_seed() {
    let rows: Vec<Vec<f64>> = include_str!("../../../tests/fixtures/chart_adx.csv")
        .lines()
        .skip(1)
        .map(|line| line.split(',').map(rational).collect())
        .collect();
    let high: Vec<_> = rows.iter().map(|row| row[0]).collect();
    let low: Vec<_> = rows.iter().map(|row| row[1]).collect();
    let close: Vec<_> = rows.iter().map(|row| row[2]).collect();
    let raw = true_range(&high, &low, &close).unwrap();
    let result = adx(&high, &low, &close, 2).unwrap();
    for (channel, series) in [&result.plus_di, &result.minus_di, &result.dx, &result.adx]
        .into_iter()
        .enumerate()
    {
        assert_eq!(series.first_valid, if channel == 3 { 3 } else { 2 });
        for (i, row) in rows.iter().enumerate() {
            assert!((series.values[i] - row[9 + channel]).abs() < 1e-12);
            assert_eq!(series.get(i).is_some(), i >= series.first_valid);
        }
    }
    let plus: Vec<_> = rows.iter().map(|row| row[4]).collect();
    let minus: Vec<_> = rows.iter().map(|row| row[5]).collect();
    for (column, values) in [(6, raw.values), (7, plus), (8, minus)] {
        let mean = wilder(&values, 2, 1).unwrap();
        for (i, row) in rows.iter().enumerate() {
            assert!((mean.values[i] - row[column]).abs() < 1e-12);
        }
    }
    assert_eq!(rows[4][4..6], [0.0, 0.0]);
    assert_ne!(
        super::super::atr(&high, &low, &close, 2).unwrap().values[2],
        rows[2][6]
    );
}

#[test]
fn adx_rising_falling_flat_and_zero_sum() {
    for sign in [1.0, -1.0] {
        let close: Vec<_> = (0..8).map(|i| sign * i as f64).collect();
        let result = adx(&close, &close, &close, 2).unwrap();
        assert_eq!(&result.dx.values[2..], &[100.0; 6]);
        assert_eq!(&result.adx.values[3..], &[100.0; 5]);
        let (active, other) = if sign > 0.0 {
            (result.plus_di, result.minus_di)
        } else {
            (result.minus_di, result.plus_di)
        };
        assert_eq!(&active.values[2..], &[100.0; 6]);
        assert_eq!(other.values, [0.0; 8]);
    }
    for (high, low) in [(-3.0, -3.0), (2.0, -4.0)] {
        let result = adx(&[high; 8], &[low; 8], &[-3.0; 8], 2).unwrap();
        for series in [result.plus_di, result.minus_di, result.dx, result.adx] {
            assert_eq!(series.values, [0.0; 8]);
        }
    }
}

#[test]
fn adx_default_period_one_empty_short_and_large_period() {
    let close: Vec<_> = (0..30).map(|i| i as f64).collect();
    let default = adx_default(&close, &close, &close).unwrap();
    assert_eq!(default, adx(&close, &close, &close, 14).unwrap());
    assert_eq!(default.dx.first_valid, 14);
    assert_eq!(default.adx.first_valid, 27);
    for n in 0..5 {
        let result = adx(&close[..n], &close[..n], &close[..n], 2).unwrap();
        assert_eq!(result.dx.first_valid, 2.min(n));
        assert_eq!(result.adx.first_valid, 3.min(n));
    }
    let huge = adx(&close, &close, &close, usize::MAX).unwrap();
    assert_eq!(huge.adx.first_valid, 30);
    assert_eq!(huge.dx.values, [0.0; 30]);
    let one = adx(&[0.0, f64::MAX, f64::MIN_POSITIVE], &[0.0; 3], &[0.0; 3], 1).unwrap();
    assert_eq!(one.dx.values, [0.0, 100.0, 0.0]);
    assert_eq!(one.adx, one.dx);
    let max = adx(&[0.0, f64::MAX, f64::MAX], &[0.0; 3], &[0.0; 3], 2).unwrap();
    assert!(max.plus_di.values[2].is_finite());
}

#[test]
fn adx_rejects_all_invalid_differences_even_during_warmup() {
    assert!(adx(&[], &[], &[], 0).is_err());
    for (h, l, c) in [
        (vec![], vec![0.0], vec![1.0]),
        (vec![2.0], vec![], vec![1.0]),
        (vec![2.0], vec![0.0], vec![]),
        (vec![f64::NAN], vec![0.0], vec![1.0]),
        (vec![2.0], vec![f64::NEG_INFINITY], vec![1.0]),
        (vec![2.0], vec![0.0], vec![f64::INFINITY]),
        (vec![2.0], vec![3.0], vec![1.0]),
        (vec![f64::MAX], vec![-f64::MAX], vec![0.0]),
        (
            vec![f64::MAX, -f64::MAX],
            vec![0.0, -f64::MAX],
            vec![0.0, -f64::MAX],
        ),
        (
            vec![f64::MAX, 0.0],
            vec![f64::MAX, -f64::MAX],
            vec![f64::MAX, 0.0],
        ),
    ] {
        assert!(adx(&h, &l, &c, 14).is_err());
    }
}