indexes_rs/v2/obv/
main.rs

1use crate::v2::obv::types::{OBVConfig, OBVError, OBVInput, OBVOutput, OBVState};
2
3/// On Balance Volume (OBV) Indicator
4///
5/// OBV is a momentum indicator that uses volume flow to predict changes in stock price.
6/// It adds volume on up days and subtracts volume on down days.
7///
8/// Formula:
9/// - If Close > Previous Close: OBV = Previous OBV + Volume
10/// - If Close < Previous Close: OBV = Previous OBV - Volume
11/// - If Close = Previous Close: OBV = Previous OBV
12#[derive(Default)]
13pub struct OBV {
14    state: OBVState,
15}
16
17impl OBV {
18    /// Create a new OBV calculator with default configuration
19    pub fn new() -> Self {
20        Self::with_config(OBVConfig::default())
21    }
22
23    /// Create a new OBV calculator with custom configuration
24    pub fn with_config(config: OBVConfig) -> Self {
25        Self { state: OBVState::new(config) }
26    }
27
28    /// Calculate OBV for the given input
29    pub fn calculate(&mut self, input: OBVInput) -> Result<OBVOutput, OBVError> {
30        // Validate input
31        self.validate_input(&input)?;
32
33        let flow_direction = if self.state.is_first {
34            // First calculation - no direction yet
35            self.state.previous_close = Some(input.close);
36            self.state.cumulative_obv = input.volume;
37            self.state.is_first = false;
38            0.0
39        } else {
40            let prev_close = self.state.previous_close.unwrap();
41            let direction = self.determine_flow_direction(input.close, prev_close);
42
43            // Update OBV based on price direction
44            match direction {
45                d if d > 0.0 => {
46                    // Price went up - add volume
47                    self.state.cumulative_obv += input.volume;
48                }
49                d if d < 0.0 => {
50                    // Price went down - subtract volume
51                    self.state.cumulative_obv -= input.volume;
52                }
53                _ => {
54                    // Price unchanged - OBV stays the same
55                }
56            }
57
58            self.state.previous_close = Some(input.close);
59            direction
60        };
61
62        Ok(OBVOutput {
63            obv: self.state.cumulative_obv,
64            flow_direction,
65        })
66    }
67
68    /// Calculate OBV for a batch of inputs
69    pub fn calculate_batch(&mut self, inputs: &[OBVInput]) -> Result<Vec<OBVOutput>, OBVError> {
70        inputs.iter().map(|input| self.calculate(*input)).collect()
71    }
72
73    /// Reset the calculator state
74    pub fn reset(&mut self) {
75        self.state = OBVState::new(self.state.config);
76    }
77
78    /// Get current state (for serialization/debugging)
79    pub fn get_state(&self) -> &OBVState {
80        &self.state
81    }
82
83    /// Restore state (for deserialization)
84    pub fn set_state(&mut self, state: OBVState) {
85        self.state = state;
86    }
87
88    // Private helper methods
89
90    fn validate_input(&self, input: &OBVInput) -> Result<(), OBVError> {
91        if input.volume < 0.0 {
92            return Err(OBVError::NegativeVolume);
93        }
94
95        if !input.close.is_finite() {
96            return Err(OBVError::InvalidPrice);
97        }
98
99        Ok(())
100    }
101
102    fn determine_flow_direction(&self, current_close: f64, previous_close: f64) -> f64 {
103        if current_close > previous_close {
104            1.0 // Up
105        } else if current_close < previous_close {
106            -1.0 // Down
107        } else {
108            0.0 // Unchanged
109        }
110    }
111}
112
113/// Convenience function to calculate OBV for a single input without maintaining state
114pub fn calculate_obv_simple(close_prices: &[f64], volumes: &[f64]) -> Result<Vec<f64>, OBVError> {
115    if close_prices.len() != volumes.len() {
116        return Err(OBVError::InvalidInput("Close prices and volumes must have same length".to_string()));
117    }
118
119    if close_prices.is_empty() {
120        return Ok(Vec::new());
121    }
122
123    let mut obv_calculator = OBV::new();
124    let mut results = Vec::with_capacity(close_prices.len());
125
126    for (close, volume) in close_prices.iter().zip(volumes.iter()) {
127        let input = OBVInput { close: *close, volume: *volume };
128        let output = obv_calculator.calculate(input)?;
129        results.push(output.obv);
130    }
131
132    Ok(results)
133}