use crate::indicators::Indicator;
use crate::types::Candle;
#[derive(Debug, Clone)]
pub struct OBV {
current: f64,
prev_close: Option<f64>,
}
impl OBV {
pub fn new() -> Self {
Self {
current: 0.0,
prev_close: None,
}
}
}
impl Default for OBV {
fn default() -> Self {
Self::new()
}
}
impl Indicator for OBV {
type Output = f64;
fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
if let Some(prev) = self.prev_close {
match candle.close.partial_cmp(&prev) {
Some(std::cmp::Ordering::Greater) => self.current += candle.volume,
Some(std::cmp::Ordering::Less) => self.current -= candle.volume,
_ => {}
}
}
self.prev_close = Some(candle.close);
Some(self.current)
}
fn reset(&mut self) {
self.current = 0.0;
self.prev_close = None;
}
fn warmup_period(&self) -> usize {
0
}
fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn obv_moves_with_direction() {
let mut obv = OBV::new();
let candles: Vec<Candle> = vec![
(10.0, 100.0), (12.0, 150.0), (9.0, 80.0), (9.0, 50.0), ]
.into_iter()
.map(|(c, v)| Candle {
timestamp: 0,
open: 0.0,
high: 0.0,
low: 0.0,
close: c,
volume: v,
})
.collect();
let outputs: Vec<_> = candles.iter().map(|c| obv.next(c)).collect();
assert_eq!(outputs[0], Some(0.0)); assert_eq!(outputs[1], Some(150.0)); assert_eq!(outputs[2], Some(70.0)); assert_eq!(outputs[3], Some(70.0)); }
}