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(
38 highs: &[f64],
39 lows: &[f64],
40 closes: &[f64],
41 period: usize,
42) -> Result<BullBearPowerResult> {
43 if period == 0 {
44 return Err(IndicatorError::InvalidPeriod(
45 "Period must be greater than 0".to_string(),
46 ));
47 }
48 let len = highs.len();
49 if lows.len() != len || closes.len() != len {
50 return Err(IndicatorError::InvalidPeriod(
51 "Data lengths must match".to_string(),
52 ));
53 }
54 if len < period {
55 return Err(IndicatorError::InsufficientData {
56 need: period,
57 got: len,
58 });
59 }
60
61 let ema_values = ema(closes, period);
62
63 let mut bull_power = vec![None; len];
64 let mut bear_power = vec![None; len];
65
66 for i in 0..len {
67 if let Some(ema_val) = ema_values[i] {
68 bull_power[i] = Some(highs[i] - ema_val);
69 bear_power[i] = Some(lows[i] - ema_val);
70 }
71 }
72
73 Ok(BullBearPowerResult {
74 bull_power,
75 bear_power,
76 })
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_bull_bear_power_default_period() {
85 let highs = vec![10.0; 20];
86 let lows = vec![8.0; 20];
87 let closes = vec![9.0; 20];
88 let result = bull_bear_power(&highs, &lows, &closes, 13).unwrap();
89
90 assert_eq!(result.bull_power.len(), 20);
91 assert!(result.bull_power[11].is_none());
92 assert!(result.bull_power[12].is_some());
93 }
94
95 #[test]
96 fn test_bull_bear_power_custom_period() {
97 let highs = vec![10.0; 20];
98 let lows = vec![8.0; 20];
99 let closes = vec![9.0; 20];
100 let result = bull_bear_power(&highs, &lows, &closes, 5).unwrap();
101
102 assert_eq!(result.bull_power.len(), 20);
103 assert!(result.bull_power[3].is_none());
104 assert!(result.bull_power[4].is_some());
105 }
106
107 #[test]
108 fn test_bull_bear_power_custom_produces_different_output() {
109 let highs: Vec<f64> = (1..=30).map(|i| i as f64 + 1.0).collect();
110 let lows: Vec<f64> = (1..=30).map(|i| i as f64 - 1.0).collect();
111 let closes: Vec<f64> = (1..=30).map(|i| i as f64).collect();
112 let default = bull_bear_power(&highs, &lows, &closes, 13).unwrap();
113 let custom = bull_bear_power(&highs, &lows, &closes, 5).unwrap();
114 let idx = 14;
115 assert!(default.bull_power[idx].is_some());
116 assert!(custom.bull_power[idx].is_some());
117 assert_ne!(default.bull_power[idx], custom.bull_power[idx]);
118 }
119}