pub fn obv(real: &[f64], volume: &[f64]) -> Vec<f64> {
let n = real.len();
if n == 0 {
return Vec::new();
}
let mut out = Vec::with_capacity(n);
let mut obv = volume[0];
let mut prev = real[0];
out.push(obv);
for i in 1..n {
let dir = ((real[i] > prev) as i8 - (real[i] < prev) as i8) as f64;
obv += dir * volume[i];
out.push(obv);
prev = real[i];
}
out
}
pub fn obv_final_state(real: &[f64], volume: &[f64]) -> Option<Vec<f64>> {
let n = real.len();
if n == 0 {
return None;
}
let mut obv = volume[0];
let mut prev = real[0];
for i in 1..n {
let dir = ((real[i] > prev) as i8 - (real[i] < prev) as i8) as f64;
obv += dir * volume[i];
prev = real[i];
}
Some(vec![obv, prev])
}
pub fn ad_final_state(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
) -> Option<Vec<f64>> {
let n = close.len();
if n == 0 {
return None;
}
let mut ad = 0.0;
for i in 0..n {
ad += money_flow_volume(high[i], low[i], close[i], volume[i]);
}
Some(vec![ad])
}
pub fn adosc_final_state(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
fast: usize,
slow: usize,
) -> Option<Vec<f64>> {
let n = close.len();
if n == 0 {
return None;
}
let fastk = 2.0 / (fast as f64 + 1.0);
let slowk = 2.0 / (slow as f64 + 1.0);
let mut ad_line = money_flow_volume(high[0], low[0], close[0], volume[0]);
let mut fast_ema = ad_line;
let mut slow_ema = ad_line;
for i in 1..n {
ad_line += money_flow_volume(high[i], low[i], close[i], volume[i]);
fast_ema = (ad_line - fast_ema).mul_add(fastk, fast_ema);
slow_ema = (ad_line - slow_ema).mul_add(slowk, slow_ema);
}
Some(vec![ad_line, fast_ema, slow_ema])
}
pub fn obv_resume(
real: &[f64],
volume: &[f64],
from: usize,
state: &[f64],
) -> (Vec<f64>, Vec<f64>) {
let n = real.len();
let mut obv = state[0];
let mut prev = state[1];
let mut out = Vec::with_capacity(n.saturating_sub(from));
for i in from..n {
let dir = ((real[i] > prev) as i8 - (real[i] < prev) as i8) as f64;
obv += dir * volume[i];
out.push(obv);
prev = real[i];
}
(out, vec![obv, prev])
}
pub fn ad_resume(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
from: usize,
state: &[f64],
) -> (Vec<f64>, Vec<f64>) {
let n = close.len();
let mut ad = state[0];
let mut out = Vec::with_capacity(n.saturating_sub(from));
for i in from..n {
ad += money_flow_volume(high[i], low[i], close[i], volume[i]);
out.push(ad);
}
(out, vec![ad])
}
pub fn adosc_resume(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
fast: usize,
slow: usize,
from: usize,
state: &[f64],
) -> (Vec<f64>, Vec<f64>) {
let n = close.len();
let fastk = 2.0 / (fast as f64 + 1.0);
let slowk = 2.0 / (slow as f64 + 1.0);
let mut ad_line = state[0];
let mut fast_ema = state[1];
let mut slow_ema = state[2];
let mut out = Vec::with_capacity(n.saturating_sub(from));
for i in from..n {
ad_line += money_flow_volume(high[i], low[i], close[i], volume[i]);
fast_ema = (ad_line - fast_ema).mul_add(fastk, fast_ema);
slow_ema = (ad_line - slow_ema).mul_add(slowk, slow_ema);
out.push(fast_ema - slow_ema);
}
(out, vec![ad_line, fast_ema, slow_ema])
}
#[inline]
fn money_flow_volume(high: f64, low: f64, close: f64, volume: f64) -> f64 {
let range = high - low;
if range > 0.0 {
((close - low) - (high - close)) / range * volume
} else {
0.0
}
}
pub fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec<f64> {
let n = close.len();
let mut out = Vec::with_capacity(n);
let mut ad = 0.0;
for i in 0..n {
ad += money_flow_volume(high[i], low[i], close[i], volume[i]);
out.push(ad);
}
out
}
pub fn adosc(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
fast: usize,
slow: usize,
) -> Vec<f64> {
let n = close.len();
let mut out = vec![f64::NAN; n];
if n == 0 {
return out;
}
let lookback = fast.max(slow).saturating_sub(1);
let fastk = 2.0 / (fast as f64 + 1.0);
let slowk = 2.0 / (slow as f64 + 1.0);
let mut ad_line = money_flow_volume(high[0], low[0], close[0], volume[0]);
let mut fast_ema = ad_line;
let mut slow_ema = ad_line;
if lookback == 0 {
out[0] = fast_ema - slow_ema;
}
for i in 1..n {
ad_line += money_flow_volume(high[i], low[i], close[i], volume[i]);
fast_ema = (ad_line - fast_ema).mul_add(fastk, fast_ema);
slow_ema = (ad_line - slow_ema).mul_add(slowk, slow_ema);
if i >= lookback {
out[i] = fast_ema - slow_ema;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::test_support::*;
#[test]
fn final_state_declines_on_empty() {
assert!(obv_final_state(&[], &[]).is_none()); assert!(ad_final_state(&[], &[], &[], &[]).is_none()); assert!(adosc_final_state(&[], &[], &[], &[], 3, 10).is_none()); }
#[test]
fn resume_is_bit_identical_to_full() {
let (high, low, close) = ohlc(120);
let vol: Vec<f64> = (0..120)
.map(|i| 1000.0 + 300.0 * ((i as f64) * 0.27).sin())
.collect();
let (fast, slow) = (3usize, 10usize);
let obv_full = obv(&close, &vol);
let ad_full = ad(&high, &low, &close, &vol);
let adosc_full = adosc(&high, &low, &close, &vol, fast, slow);
for &from in &[1usize, 2, 30, 60, 119] {
let head = &close[..from];
let st = obv_final_state(head, &vol[..from]).unwrap();
let (tail, _) = obv_resume(&close, &vol, from, &st);
assert_bits(&tail, &obv_full[from..], "obv");
let st = ad_final_state(&high[..from], &low[..from], head, &vol[..from]).unwrap();
let (tail, _) = ad_resume(&high, &low, &close, &vol, from, &st);
assert_bits(&tail, &ad_full[from..], "ad");
}
let lookback = fast.max(slow) - 1;
for &from in &[lookback + 1, lookback + 5, 60, 119] {
let st = adosc_final_state(
&high[..from],
&low[..from],
&close[..from],
&vol[..from],
fast,
slow,
)
.unwrap();
let (tail, _) = adosc_resume(&high, &low, &close, &vol, fast, slow, from, &st);
assert_bits(&tail, &adosc_full[from..], "adosc");
}
}
#[test]
fn flat_bar_adds_zero_money_flow() {
let high = [10.0, 10.0];
let low = [10.0, 10.0];
let close = [10.0, 10.0];
let vol = [5.0, 5.0];
assert_eq!(ad(&high, &low, &close, &vol), vec![0.0, 0.0]);
}
}