mantis_ta/indicators/
obv.rs1use crate::indicators::Indicator;
2use crate::types::Candle;
3
4#[derive(Debug, Clone)]
36pub struct OBV {
37 current: f64,
38 prev_close: Option<f64>,
39}
40
41impl OBV {
42 pub fn new() -> Self {
43 Self {
44 current: 0.0,
45 prev_close: None,
46 }
47 }
48}
49
50impl Default for OBV {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl Indicator for OBV {
57 type Output = f64;
58
59 fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
60 if let Some(prev) = self.prev_close {
61 match candle.close.partial_cmp(&prev) {
62 Some(std::cmp::Ordering::Greater) => self.current += candle.volume,
63 Some(std::cmp::Ordering::Less) => self.current -= candle.volume,
64 _ => {}
65 }
66 }
67 self.prev_close = Some(candle.close);
68 Some(self.current)
69 }
70
71 fn reset(&mut self) {
72 self.current = 0.0;
73 self.prev_close = None;
74 }
75
76 fn warmup_period(&self) -> usize {
77 0
78 }
79
80 fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
81 Box::new(self.clone())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn obv_moves_with_direction() {
91 let mut obv = OBV::new();
92 let candles: Vec<Candle> = vec![
94 (10.0, 100.0), (12.0, 150.0), (9.0, 80.0), (9.0, 50.0), ]
99 .into_iter()
100 .map(|(c, v)| Candle {
101 timestamp: 0,
102 open: 0.0,
103 high: 0.0,
104 low: 0.0,
105 close: c,
106 volume: v,
107 })
108 .collect();
109
110 let outputs: Vec<_> = candles.iter().map(|c| obv.next(c)).collect();
111 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)); }
116}