use crate::core::defaults::{ADOSC_FAST, ADOSC_SLOW};
use crate::core::check_eq_len;
use crate::error::{check_period, TaError};
use crate::indicator::indicator;
indicator! {
fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec<f64> with ad_with_output init zero;
}
pub fn ad_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
out: &mut [f64],
) -> Result<(), TaError> {
check_eq_len(&[high, low, close, volume], "ad")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"ad_with_output: out length must equal close length".into(),
));
}
let n = close.len();
let mut prev = 0.0;
for i in 0..n {
let clv = if high[i] == low[i] {
0.0
} else {
(2.0 * close[i] - high[i] - low[i]) / (high[i] - low[i])
};
out[i] = prev + volume[i] * clv;
prev = out[i];
}
Ok(())
}
indicator! {
fn adosc(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
fast_period: usize,
slow_period: usize,
) -> Vec<f64> with adosc_with_output;
}
pub fn adosc_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
fast_period: usize,
slow_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
if out.len() != close.len() {
return Err(TaError::BadParam(
"adosc_with_output: out length must equal close length".into(),
));
}
check_period(fast_period)?;
check_period(slow_period)?;
if fast_period >= slow_period {
return Err(TaError::BadParam("fast_period must be < slow_period".into()));
}
let ad_line = ad(high, low, close, volume)?;
let n = ad_line.len();
if n == 0 {
return Ok(());
}
let fast_k = 2.0 / (fast_period as f64 + 1.0);
let slow_k = 2.0 / (slow_period as f64 + 1.0);
let mut fast_ema = ad_line[0];
let mut slow_ema = ad_line[0];
for i in 1..n {
fast_ema = (ad_line[i] - fast_ema).mul_add(fast_k, fast_ema);
slow_ema = (ad_line[i] - slow_ema).mul_add(slow_k, slow_ema);
if i >= slow_period - 1 {
out[i] = fast_ema - slow_ema;
}
}
Ok(())
}
pub fn adosc_default(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
) -> Result<Vec<f64>, TaError> {
adosc(high, low, close, volume, ADOSC_FAST, ADOSC_SLOW)
}
indicator! {
fn obv(close: &[f64], volume: &[f64]) -> Vec<f64> with obv_with_output init zero;
}
pub fn obv_with_output(
close: &[f64],
volume: &[f64],
out: &mut [f64],
) -> Result<(), TaError> {
check_eq_len(&[close, volume], "obv")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"obv_with_output: out length must equal close length".into(),
));
}
let n = close.len();
if n == 0 {
return Ok(());
}
out[0] = volume[0];
for i in 1..n {
out[i] = if close[i] > close[i - 1] {
out[i - 1] + volume[i]
} else if close[i] < close[i - 1] {
out[i - 1] - volume[i]
} else {
out[i - 1]
};
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ad_cumulative() {
let high = [10.0, 11.0, 12.0];
let low = [9.0, 9.5, 11.0];
let close = [9.5, 10.5, 11.5];
let vol = [100.0, 200.0, 300.0];
let out = ad(&high, &low, &close, &vol).unwrap();
assert!(out[0].abs() < 1e-12);
assert!((out[1] - 200.0 / 3.0).abs() < 1e-12);
assert!((out[2] - 200.0 / 3.0).abs() < 1e-12);
}
#[test]
fn adosc_shape() {
let high: Vec<f64> = (0..60).map(|i| 10.0 + i as f64 * 0.1 + 0.5).collect();
let low: Vec<f64> = (0..60).map(|i| 9.0 + i as f64 * 0.1 - 0.5).collect();
let close: Vec<f64> = (0..60).map(|i| 9.5 + i as f64 * 0.1).collect();
let vol: Vec<f64> = (0..60).map(|i| 1000.0 + 100.0 * (i as f64 * 0.7).sin()).collect();
let out = adosc(&high, &low, &close, &vol, 3, 10).unwrap();
assert!(out[8].is_nan());
assert!(!out[20].is_nan());
}
#[test]
fn obv_sign_tracking() {
let close = [10.0, 11.0, 10.5, 9.0];
let vol = [100.0, 200.0, 50.0, 80.0];
let out = obv(&close, &vol).unwrap();
assert!((out[0] - 100.0).abs() < 1e-12);
assert!((out[1] - 300.0).abs() < 1e-12); assert!((out[2] - 250.0).abs() < 1e-12); assert!((out[3] - 170.0).abs() < 1e-12); }
}