pub fn obv(close: &[f64], volume: &[f64]) -> Vec<f64> {
let n = close.len();
let mut result = vec![0.0_f64; n];
if n == 0 {
return result;
}
for i in 1..n {
result[i] = result[i - 1]
+ if close[i] > close[i - 1] {
volume[i]
} else if close[i] < close[i - 1] {
-volume[i]
} else {
0.0
};
}
result
}
pub fn mfi(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
timeperiod: usize,
) -> Vec<f64> {
let n = high.len();
let mut result = vec![f64::NAN; n];
if timeperiod < 1 || n <= timeperiod {
return result;
}
let mut pos_flow = vec![0.0_f64; n];
let mut neg_flow = vec![0.0_f64; n];
let mut tp_prev = (high[0] + low[0] + close[0]) / 3.0;
for i in 1..n {
let tp_cur = (high[i] + low[i] + close[i]) / 3.0;
let rmf = tp_cur * volume[i];
if tp_cur > tp_prev {
pos_flow[i] = rmf;
} else if tp_cur < tp_prev {
neg_flow[i] = rmf;
}
tp_prev = tp_cur;
}
let mut pos_sum: f64 = pos_flow[1..=timeperiod].iter().sum();
let mut neg_sum: f64 = neg_flow[1..=timeperiod].iter().sum();
let mfr = if neg_sum == 0.0 {
f64::MAX
} else {
pos_sum / neg_sum
};
result[timeperiod] = 100.0 - 100.0 / (1.0 + mfr);
for i in (timeperiod + 1)..n {
pos_sum += pos_flow[i] - pos_flow[i - timeperiod];
neg_sum += neg_flow[i] - neg_flow[i - timeperiod];
let mfr = if neg_sum == 0.0 {
f64::MAX
} else {
pos_sum / neg_sum
};
result[i] = 100.0 - 100.0 / (1.0 + mfr);
}
result
}
pub fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec<f64> {
let n = high.len();
let mut result = vec![0.0_f64; n];
let mut ad_val = 0.0_f64;
for i in 0..n {
let hl = high[i] - low[i];
let clv = if hl != 0.0 {
((close[i] - low[i]) - (high[i] - close[i])) / hl
} else {
0.0
};
ad_val += clv * volume[i];
result[i] = ad_val;
}
result
}
pub fn adosc(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
fastperiod: usize,
slowperiod: usize,
) -> Vec<f64> {
let n = high.len();
let ad_vals = ad(high, low, close, volume);
let fast_ema = crate::overlap::ema(&ad_vals, fastperiod);
let slow_ema = crate::overlap::ema(&ad_vals, slowperiod);
let warmup = slowperiod - 1;
let mut result = vec![f64::NAN; n];
for i in warmup..n {
if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
result[i] = fast_ema[i] - slow_ema[i];
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn obv_up_trend() {
let c = vec![1.0, 2.0, 3.0];
let v = vec![100.0, 200.0, 300.0];
let result = obv(&c, &v);
assert!((result[0] - 0.0).abs() < 1e-10);
assert!((result[1] - 200.0).abs() < 1e-10);
assert!((result[2] - 500.0).abs() < 1e-10);
}
#[test]
fn ad_basic() {
let h = vec![10.0, 12.0, 11.0];
let l = vec![8.0, 9.0, 9.0];
let c = vec![9.0, 11.0, 10.0];
let v = vec![1000.0, 2000.0, 1500.0];
let result = ad(&h, &l, &c, &v);
assert_eq!(result.len(), 3);
assert!((result[0] - 0.0).abs() < 1e-10);
}
#[test]
fn adosc_basic() {
let n = 30;
let h: Vec<f64> = (1..=n).map(|i| i as f64 + 1.0).collect();
let l: Vec<f64> = (1..=n).map(|i| i as f64 - 1.0).collect();
let c: Vec<f64> = (1..=n).map(|i| i as f64).collect();
let v: Vec<f64> = vec![1000.0; n];
let result = adosc(&h, &l, &c, &v, 3, 10);
assert_eq!(result.len(), n);
for i in 0..9 {
assert!(result[i].is_nan());
}
}
#[test]
fn mfi_range() {
let n = 50;
let high: Vec<f64> = (1..=n).map(|i| i as f64 + 0.5).collect();
let low: Vec<f64> = (1..=n).map(|i| i as f64 - 0.5).collect();
let close: Vec<f64> = (1..=n).map(|i| i as f64).collect();
let volume: Vec<f64> = vec![1_000_000.0; n];
let result = mfi(&high, &low, &close, &volume, 14);
for v in result.iter().filter(|v| !v.is_nan()) {
assert!(*v >= 0.0 && *v <= 100.0, "MFI out of range: {v}");
}
}
}