use crate::error::FinError;
use crate::signals::{BarInput, Signal, SignalValue};
use rust_decimal::Decimal;
pub struct Obv {
name: String,
obv: Decimal,
prev_close: Option<Decimal>,
}
impl Obv {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
obv: Decimal::ZERO,
prev_close: None,
}
}
pub fn value(&self) -> Decimal {
self.obv
}
}
impl Signal for Obv {
fn name(&self) -> &str {
&self.name
}
fn update(&mut self, bar: &BarInput) -> Result<SignalValue, FinError> {
if let Some(prev) = self.prev_close {
if bar.close > prev {
self.obv += bar.volume;
} else if bar.close < prev {
self.obv -= bar.volume;
}
}
self.prev_close = Some(bar.close);
if self.prev_close.is_some() && !self.obv.is_zero() || self.prev_close.is_some() {
}
match self.prev_close {
None => Ok(SignalValue::Unavailable),
Some(_) => Ok(SignalValue::Scalar(self.obv)),
}
}
fn is_ready(&self) -> bool {
self.prev_close.is_some()
}
fn period(&self) -> usize {
2
}
fn reset(&mut self) {
self.obv = Decimal::ZERO;
self.prev_close = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::signals::Signal;
use rust_decimal_macros::dec;
fn bar(close: &str, vol: &str) -> BarInput {
let c: Decimal = close.parse().unwrap();
let v: Decimal = vol.parse().unwrap();
BarInput::new(c, c, c, c, v)
}
#[test]
fn test_obv_first_bar_returns_zero() {
let mut obv = Obv::new("obv");
let result = obv.update(&bar("100", "1000")).unwrap();
assert_eq!(result, SignalValue::Scalar(dec!(0)));
}
#[test]
fn test_obv_up_close_adds_volume() {
let mut obv = Obv::new("obv");
obv.update(&bar("100", "1000")).unwrap();
let result = obv.update(&bar("102", "1500")).unwrap();
assert_eq!(result, SignalValue::Scalar(dec!(1500)));
}
#[test]
fn test_obv_down_close_subtracts_volume() {
let mut obv = Obv::new("obv");
obv.update(&bar("100", "1000")).unwrap();
let result = obv.update(&bar("98", "800")).unwrap();
assert_eq!(result, SignalValue::Scalar(dec!(-800)));
}
#[test]
fn test_obv_unchanged_close_no_change() {
let mut obv = Obv::new("obv");
obv.update(&bar("100", "1000")).unwrap();
obv.update(&bar("102", "500")).unwrap(); let result = obv.update(&bar("102", "200")).unwrap(); assert_eq!(result, SignalValue::Scalar(dec!(500)));
}
#[test]
fn test_obv_reset_clears_state() {
let mut obv = Obv::new("obv");
obv.update(&bar("100", "1000")).unwrap();
obv.update(&bar("105", "2000")).unwrap();
obv.reset();
assert!(!obv.is_ready());
assert_eq!(obv.value(), dec!(0));
}
}