use super::{IndicatorError, Result, ema::ema_raw};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BullBearPowerResult {
pub bull_power: Vec<Option<f64>>,
pub bear_power: Vec<Option<f64>>,
}
pub fn bull_bear_power(
highs: &[f64],
lows: &[f64],
closes: &[f64],
period: usize,
) -> Result<BullBearPowerResult> {
if period == 0 {
return Err(IndicatorError::InvalidPeriod(
"Period must be greater than 0".to_string(),
));
}
let len = highs.len();
if lows.len() != len || closes.len() != len {
return Err(IndicatorError::InvalidPeriod(
"Data lengths must match".to_string(),
));
}
if len < period {
return Err(IndicatorError::InsufficientData {
need: period,
got: len,
});
}
let ema_vals = ema_raw(closes, period); let off = period - 1;
let mut bull_power = vec![None; len];
let mut bear_power = vec![None; len];
for (k, &ev) in ema_vals.iter().enumerate() {
let i = k + off;
bull_power[i] = Some(highs[i] - ev);
bear_power[i] = Some(lows[i] - ev);
}
Ok(BullBearPowerResult {
bull_power,
bear_power,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bull_bear_power_default_period() {
let highs = vec![10.0; 20];
let lows = vec![8.0; 20];
let closes = vec![9.0; 20];
let result = bull_bear_power(&highs, &lows, &closes, 13).unwrap();
assert_eq!(result.bull_power.len(), 20);
assert!(result.bull_power[11].is_none());
assert!(result.bull_power[12].is_some());
}
#[test]
fn test_bull_bear_power_custom_period() {
let highs = vec![10.0; 20];
let lows = vec![8.0; 20];
let closes = vec![9.0; 20];
let result = bull_bear_power(&highs, &lows, &closes, 5).unwrap();
assert_eq!(result.bull_power.len(), 20);
assert!(result.bull_power[3].is_none());
assert!(result.bull_power[4].is_some());
}
#[test]
fn test_bull_bear_power_custom_produces_different_output() {
let highs: Vec<f64> = (1..=30).map(|i| i as f64 + 1.0).collect();
let lows: Vec<f64> = (1..=30).map(|i| i as f64 - 1.0).collect();
let closes: Vec<f64> = (1..=30).map(|i| i as f64).collect();
let default = bull_bear_power(&highs, &lows, &closes, 13).unwrap();
let custom = bull_bear_power(&highs, &lows, &closes, 5).unwrap();
let idx = 14;
assert!(default.bull_power[idx].is_some());
assert!(custom.bull_power[idx].is_some());
assert_ne!(default.bull_power[idx], custom.bull_power[idx]);
}
}