use ndarray::ArrayView1;
#[inline]
pub(crate) fn av(s: &[f64]) -> ArrayView1<'_, f64> {
ArrayView1::from(s)
}
mod bands;
mod candles;
mod directional;
mod group_a;
mod group_b;
mod hilbert;
mod math_ops;
mod momentum;
mod oscillators;
mod kdj;
mod statistic;
mod stochastic;
mod tools;
mod transform;
mod trend;
mod volume;
pub use bands::*;
pub use candles::*;
pub use directional::*;
pub use group_a::*;
pub use group_b::*;
pub use hilbert::*;
pub use math_ops::*;
pub use momentum::*;
pub use oscillators::*;
pub use kdj::*;
pub use statistic::*;
pub use stochastic::*;
pub use tools::*;
pub use transform::*;
pub use trend::*;
pub use volume::*;
#[cfg(test)]
pub(crate) mod test_support {
pub fn series(n: usize) -> Vec<f64> {
(0..n)
.map(|i| {
let t = i as f64;
100.0 + 0.05 * t + 6.0 * (t * 0.30).sin() + 2.0 * (t * 0.11).cos()
})
.collect()
}
pub fn ohlc(n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let mut high = Vec::with_capacity(n);
let mut low = Vec::with_capacity(n);
let mut close = Vec::with_capacity(n);
for i in 0..n {
let t = i as f64;
let mid = 100.0 + 0.04 * t + 8.0 * (t * 0.25).sin() + 3.0 * (t * 0.13).cos();
let span = 1.5 + 0.8 * (t * 0.37).sin().abs();
high.push(mid + span);
low.push(mid - span);
close.push(mid + span * 0.6 * (t * 0.41).sin());
}
(high, low, close)
}
pub fn assert_bits(a: &[f64], b: &[f64], what: &str) {
assert_eq!(
a.len(),
b.len(),
"{what}: length {} != {}",
a.len(),
b.len()
);
for (i, (x, y)) in a.iter().zip(b).enumerate() {
assert!(
x.to_bits() == y.to_bits() || (x.is_nan() && y.is_nan()),
"{what}: bar {i}: resume {x:?} != full {y:?}",
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ma_basic() {
let r = ma(&[1.0, 2.0, 3.0, 4.0, 5.0], 3);
assert!(r[0].is_nan() && r[1].is_nan());
assert!((r[2] - 2.0).abs() < 1e-10);
assert!((r[4] - 4.0).abs() < 1e-10);
}
#[test]
fn tr_atr_basic() {
let high = [10.0, 12.0, 11.0];
let low = [8.0, 9.0, 10.0];
let close = [9.0, 11.0, 10.5];
let t = tr(&high, &low, &close);
assert!(t[0].is_nan()); assert!((t[1] - 3.0).abs() < 1e-10);
assert!((t[2] - 1.0).abs() < 1e-10);
let a = atr(&high, &low, &close, 2);
assert!(a[0].is_nan() && a[1].is_nan());
assert!((a[2] - 2.0).abs() < 1e-10);
}
#[test]
fn rsi_bounds() {
let close: Vec<f64> = (1..=30).map(|x| x as f64).collect();
let r = rsi(&close, 14);
assert!((r[29] - 100.0).abs() < 1e-9);
}
#[test]
fn style_and_repeat() {
assert_eq!(
style(Style::Bullish, &[1.0, 2.0], &[2.0, 1.0]),
vec![true, false]
);
assert_eq!(
style(Style::Bearish, &[1.0, 2.0], &[2.0, 1.0]),
vec![false, true]
);
let r = repeat(&[true, true, true, false], 2);
assert_eq!(r, vec![false, true, true, false]);
}
#[test]
fn nan_delta_and_oversized_windows() {
let r = rsi(&[1.0, f64::NAN, 3.0, 4.0, 5.0], 2);
assert_eq!(r.len(), 5);
assert_eq!(increase(&[1.0, 2.0], 5, 1), vec![false, false]);
assert_eq!(repeat(&[true, true], 5), vec![false, false]);
}
#[test]
fn flat_data_zero_denominator_branches() {
const N: usize = 40;
let f = vec![100.0; N]; let vol = vec![1000.0; N];
assert!(willr(&f, &f, &f, 14).contains(&0.0));
assert!(bop(&f, &f, &f, &f).iter().all(|x| *x == 0.0));
assert!(cci(&f, &f, &f, 14).contains(&0.0));
assert!(mfi(&f, &f, &f, &vol, 14).contains(&0.0));
assert!(plus_di(&f, &f, &f, 14).contains(&0.0));
assert!(minus_di(&f, &f, &f, 14).contains(&0.0));
assert!(dx(&f, &f, &f, 14).contains(&0.0));
assert!(stddev(&f, 5, 1.0).contains(&0.0));
assert!(correl(&f, &f, 30).contains(&0.0));
assert!(beta(&f, &f, 5).contains(&0.0));
assert!(stoch_fastk(&f, &f, &f, 14).contains(&0.0));
assert!(cmo(&f, 14).contains(&0.0));
assert!(ad(&f, &f, &f, &vol).contains(&0.0));
assert_eq!(kama(&f, 30).len(), N);
assert_eq!(cmo(&[100.0, f64::NAN, 102.0, 103.0, 104.0], 2).len(), 5);
let hi: Vec<f64> = (0..N).map(|i| 110.0 - i as f64 * 0.3).collect();
let lo: Vec<f64> = (0..N).map(|i| 90.0 + i as f64 * 0.3).collect();
assert!(dx(&hi, &lo, &f, 14).contains(&0.0));
let z = [0.0, 0.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0];
assert_eq!(roc(&z, 2)[2], 0.0);
assert!(beta(&z, &f[..z.len()], 5).contains(&0.0));
let pos = vec![5.0; N];
let neg = vec![-5.0; N];
assert!(accbands_upper(&pos, &neg, 20)
.iter()
.any(|x| (*x - 5.0).abs() < 1e-9));
assert!(accbands_lower(&pos, &neg, 20)
.iter()
.any(|x| (*x + 5.0).abs() < 1e-9));
assert!(!adosc(&f, &f, &f, &vol, 1, 1)[0].is_nan());
}
#[test]
fn sar_initial_direction_branches() {
let hi = [110.0, 105.0, 104.0, 106.0, 108.0, 107.0];
let lo = [100.0, 95.0, 94.0, 96.0, 98.0, 97.0];
assert_eq!(sar(&hi, &lo, 0.02, 0.2).len(), 6);
assert_eq!(
sarext(&hi, &lo, 1.0, 0.0, 0.02, 0.02, 0.2, 0.02, 0.02, 0.2).len(),
6
);
assert_eq!(
sarext(&hi, &lo, -1.0, 0.0, 0.02, 0.02, 0.2, 0.02, 0.02, 0.2).len(),
6
);
}
#[test]
fn assert_bits_reports_length_mismatch() {
let err = std::panic::catch_unwind(|| {
test_support::assert_bits(&[1.0], &[], "length-check");
});
assert!(err.is_err());
}
#[test]
fn warmup_guards() {
assert!(mom(&[1.0, 2.0], 5).iter().all(|x| x.is_nan())); assert!(roc(&[1.0, 2.0], 5).iter().all(|x| x.is_nan()));
assert!(rocp(&[1.0, 2.0], 5).iter().all(|x| x.is_nan()));
assert_eq!(rocr(&[0.0, 2.0], 1)[1], 0.0);
assert!(imi(&[1.0, 2.0], &[1.0, 2.0], 0).iter().all(|x| x.is_nan()));
assert!(aroon_up(&[1.0], &[1.0], 0).iter().all(|x| x.is_nan())); let f = vec![100.0; 5];
assert!(ultosc(&f, &f, &f, 0, 14, 28).iter().all(|x| x.is_nan())); assert!(adx(&f, &f, &f, 14).iter().all(|x| x.is_nan())); assert!(t3(&f, 10, 0.7).iter().all(|x| x.is_nan())); }
}