use crate::indicators::utils::validate_data_length;
use crate::indicators::{Candle, Indicator, IndicatorError};
#[derive(Debug)]
pub struct Obv {
prev_close: Option<f64>,
current_obv: f64,
}
impl Obv {
pub fn new() -> Self {
Self {
prev_close: None,
current_obv: 0.0,
}
}
}
impl Default for Obv {
fn default() -> Self {
Self::new()
}
}
impl Indicator<Candle, f64> for Obv {
fn calculate(&mut self, data: &[Candle]) -> Result<Vec<f64>, IndicatorError> {
validate_data_length(data, 1)?;
let n = data.len();
let mut result = Vec::with_capacity(n);
self.reset();
self.current_obv = 0.0;
result.push(self.current_obv);
self.prev_close = Some(data[0].close);
for candle in data.iter().take(n).skip(1) {
let close = candle.close;
let prev_close = self.prev_close.unwrap();
let volume = candle.volume;
if close > prev_close {
self.current_obv += volume;
} else if close < prev_close {
self.current_obv -= volume;
}
result.push(self.current_obv);
self.prev_close = Some(close);
}
Ok(result)
}
fn next(&mut self, value: Candle) -> Result<Option<f64>, IndicatorError> {
if let Some(prev_close) = self.prev_close {
let close = value.close;
let volume = value.volume;
if close > prev_close {
self.current_obv += volume;
} else if close < prev_close {
self.current_obv -= volume;
}
self.prev_close = Some(close);
Ok(Some(self.current_obv))
} else {
self.prev_close = Some(value.close);
self.current_obv = 0.0;
Ok(Some(self.current_obv))
}
}
fn reset(&mut self) {
self.prev_close = None;
self.current_obv = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::Candle;
#[test]
fn test_obv_new() {
let obv = Obv::new();
assert!(obv.current_obv == 0.0);
}
#[test]
fn test_obv_calculation() {
let mut obv = Obv::new();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.5,
volume: 1000.0,
}, Candle {
timestamp: 2,
open: 10.5,
high: 12.0,
low: 10.0,
close: 11.0,
volume: 1200.0,
}, Candle {
timestamp: 3,
open: 11.0,
high: 11.5,
low: 10.0,
close: 10.2,
volume: 800.0,
}, Candle {
timestamp: 4,
open: 10.2,
high: 11.0,
low: 10.0,
close: 10.8,
volume: 900.0,
}, Candle {
timestamp: 5,
open: 10.8,
high: 11.0,
low: 10.0,
close: 10.8,
volume: 700.0,
}, ];
let result = obv.calculate(&candles).unwrap();
assert_eq!(result.len(), 5);
assert_eq!(result[0], 0.0);
assert_eq!(result[1], 1200.0);
assert_eq!(result[2], 400.0);
assert_eq!(result[3], 1300.0);
assert_eq!(result[4], 1300.0);
}
#[test]
fn test_obv_next() {
let mut obv = Obv::new();
let candle1 = Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.5,
volume: 1000.0,
};
assert_eq!(obv.next(candle1).unwrap(), Some(0.0));
let candle2 = Candle {
timestamp: 2,
open: 10.5,
high: 12.0,
low: 10.0,
close: 11.0,
volume: 1200.0,
};
assert_eq!(obv.next(candle2).unwrap(), Some(1200.0));
let candle3 = Candle {
timestamp: 3,
open: 11.0,
high: 11.5,
low: 10.0,
close: 10.2,
volume: 800.0,
};
assert_eq!(obv.next(candle3).unwrap(), Some(400.0));
}
#[test]
fn test_obv_reset() {
let mut obv = Obv::new();
let candle1 = Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.5,
volume: 1000.0,
};
obv.next(candle1).unwrap();
obv.reset();
assert_eq!(obv.current_obv, 0.0);
assert_eq!(obv.prev_close, None);
let candle2 = Candle {
timestamp: 2,
open: 10.5,
high: 12.0,
low: 10.0,
close: 11.0,
volume: 1200.0,
};
assert_eq!(obv.next(candle2).unwrap(), Some(0.0));
}
#[test]
fn test_obv_zero_volume() {
let mut obv = Obv::new();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.5,
volume: 1000.0,
}, Candle {
timestamp: 2,
open: 10.5,
high: 12.0,
low: 10.0,
close: 11.0,
volume: 0.0,
}, Candle {
timestamp: 3,
open: 11.0,
high: 11.5,
low: 10.0,
close: 10.0,
volume: 0.0,
}, ];
let result = obv.calculate(&candles).unwrap();
assert_eq!(result[0], 0.0);
assert_eq!(result[1], 0.0);
assert_eq!(result[2], 0.0);
obv.reset();
assert_eq!(obv.next(candles[0]).unwrap(), Some(0.0));
assert_eq!(obv.next(candles[1]).unwrap(), Some(0.0)); assert_eq!(obv.next(candles[2]).unwrap(), Some(0.0)); }
#[test]
fn test_obv_extreme_volume_values() {
let mut obv = Obv::new();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.5,
volume: 1000.0,
}, Candle {
timestamp: 2,
open: 10.5,
high: 12.0,
low: 10.0,
close: 11.0,
volume: 1_000_000_000.0, }, Candle {
timestamp: 3,
open: 11.0,
high: 11.5,
low: 10.0,
close: 10.0,
volume: 500_000_000.0, }, ];
let result = obv.calculate(&candles).unwrap();
assert_eq!(result[0], 0.0);
assert_eq!(result[1], 1_000_000_000.0);
assert_eq!(result[2], 1_000_000_000.0 - 500_000_000.0);
}
#[test]
fn test_obv_identical_closing_prices() {
let mut obv = Obv::new();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.5,
volume: 1000.0,
}, Candle {
timestamp: 2,
open: 10.5,
high: 12.0,
low: 10.0,
close: 10.5, volume: 1200.0,
},
Candle {
timestamp: 3,
open: 10.5,
high: 11.5,
low: 10.0,
close: 10.5, volume: 800.0,
},
Candle {
timestamp: 4,
open: 10.5,
high: 11.0,
low: 10.0,
close: 10.5, volume: 900.0,
},
];
let result = obv.calculate(&candles).unwrap();
assert_eq!(result[0], 0.0);
assert_eq!(result[1], 0.0);
assert_eq!(result[2], 0.0);
assert_eq!(result[3], 0.0);
}
#[test]
fn test_obv_consecutive_up_down_sequences() {
let mut obv = Obv::new();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
}, Candle {
timestamp: 2,
open: 10.0,
high: 12.0,
low: 10.0,
close: 11.0, volume: 500.0,
},
Candle {
timestamp: 3,
open: 11.0,
high: 11.5,
low: 9.5,
close: 10.0, volume: 300.0,
},
Candle {
timestamp: 4,
open: 10.0,
high: 10.5,
low: 9.0,
close: 10.5, volume: 700.0,
},
Candle {
timestamp: 5,
open: 10.5,
high: 11.0,
low: 9.5,
close: 9.5, volume: 400.0,
},
Candle {
timestamp: 6,
open: 9.5,
high: 10.5,
low: 9.0,
close: 10.0, volume: 600.0,
},
];
let result = obv.calculate(&candles).unwrap();
assert_eq!(result[0], 0.0);
assert_eq!(result[1], 500.0); assert_eq!(result[2], 200.0); assert_eq!(result[3], 900.0); assert_eq!(result[4], 500.0); assert_eq!(result[5], 1100.0);
let mut streaming_obv = Obv::new();
for (i, candle) in candles.iter().enumerate() {
let obv_value = streaming_obv.next(*candle).unwrap().unwrap();
assert_eq!(
obv_value, result[i],
"Streaming calculation mismatch at index {}",
i
);
}
}
#[test]
fn test_obv_insufficient_data() {
let mut obv = Obv::new();
let empty: Vec<Candle> = vec![];
let result = obv.calculate(&empty);
assert!(result.is_err());
if let Err(IndicatorError::InsufficientData(_)) = result {
} else {
panic!("Expected InsufficientData error");
}
}
#[test]
fn test_obv_batch_vs_streaming_consistency() {
let mut batch_obv = Obv::new();
let mut streaming_obv = Obv::new();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 10.0,
high: 12.0,
low: 10.0,
close: 11.0,
volume: 1500.0,
},
Candle {
timestamp: 3,
open: 11.0,
high: 11.5,
low: 9.5,
close: 10.0,
volume: 800.0,
},
Candle {
timestamp: 4,
open: 10.0,
high: 10.5,
low: 9.0,
close: 10.0,
volume: 1200.0,
},
Candle {
timestamp: 5,
open: 10.0,
high: 11.0,
low: 9.5,
close: 10.5,
volume: 2000.0,
},
];
let batch_result = batch_obv.calculate(&candles).unwrap();
let mut streaming_result = Vec::with_capacity(candles.len());
for candle in &candles {
let value = streaming_obv.next(*candle).unwrap().unwrap();
streaming_result.push(value);
}
assert_eq!(batch_result.len(), streaming_result.len());
for i in 0..batch_result.len() {
assert_eq!(
batch_result[i], streaming_result[i],
"Batch and streaming results differ at index {}",
i
);
}
}
}