finance_query/indicators/
bull_bear_power.rs1use super::{IndicatorError, Result, ema::ema};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct BullBearPowerResult {
9 pub bull_power: Vec<Option<f64>>,
11 pub bear_power: Vec<Option<f64>>,
13}
14
15pub fn bull_bear_power(highs: &[f64], lows: &[f64], closes: &[f64]) -> Result<BullBearPowerResult> {
37 let len = highs.len();
38 if lows.len() != len || closes.len() != len {
39 return Err(IndicatorError::InvalidPeriod(
40 "Data lengths must match".to_string(),
41 ));
42 }
43 if len < 13 {
44 return Err(IndicatorError::InsufficientData { need: 13, got: len });
45 }
46
47 let ema13 = ema(closes, 13);
48
49 let mut bull_power = vec![None; len];
50 let mut bear_power = vec![None; len];
51
52 for i in 0..len {
53 if let Some(ema_val) = ema13[i] {
54 bull_power[i] = Some(highs[i] - ema_val);
55 bear_power[i] = Some(lows[i] - ema_val);
56 }
57 }
58
59 Ok(BullBearPowerResult {
60 bull_power,
61 bear_power,
62 })
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn test_bull_bear_power() {
71 let highs = vec![10.0; 20];
72 let lows = vec![8.0; 20];
73 let closes = vec![9.0; 20];
74 let result = bull_bear_power(&highs, &lows, &closes).unwrap();
75
76 assert_eq!(result.bull_power.len(), 20);
77 assert!(result.bull_power[11].is_none());
78 assert!(result.bull_power[12].is_some());
79 }
80}