Skip to main content

egui_charts/studies/builtin/
bull_bear_power.rs

1//! Bull Bear Power
2//!
3//! Alexander Elder's bull and bear power, summed into one histogram: how far the bar's high and
4//! low reach, together, above or below the exponential average of the close.
5//!
6//! # Formula
7//! ```text
8//! Bull Power = high - EMA(close, length)
9//! Bear Power = low - EMA(close, length)
10//! Bull Bear Power = Bull Power + Bear Power
11//! ```
12//! with conventional length 13. The EMA is that of [`ElderRay`]: alpha 2 / (length + 1), seeded
13//! with the mean of the first `length` closes, so the first value is on bar `length - 1`.
14//!
15//! # Presentation
16//! Columns, green at or above zero and red below.
17//!
18//! # Example
19//! ```
20//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
21//! use egui_charts::studies::{BullBearPower, Indicator};
22//!
23//! let mut power = BullBearPower::new(13);
24//! power.calculate(&bars);
25//! assert!(power.values().value(0, 12).is_some());
26//! ```
27
28use super::smoothing::Smoothing;
29use crate::model::Bar;
30use crate::studies::palette;
31use crate::studies::stepper::Stepper;
32use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
33use egui::Color32;
34
35/// Bull Power plus Bear Power.
36#[derive(Clone)]
37pub struct BullBearPower {
38    period: usize,
39    values: IndicatorValues,
40    stepper: Stepper<Smoothing>,
41    positive_color: Color32,
42    negative_color: Color32,
43    visible: bool,
44}
45
46impl BullBearPower {
47    /// Create a Bull Bear Power over an EMA of `period` closes (conventionally 13).
48    pub fn new(period: usize) -> Self {
49        Self {
50            period: period.max(1),
51            values: IndicatorValues::new(),
52            stepper: Stepper::default(),
53            positive_color: palette::TEAL,
54            negative_color: palette::RED,
55            visible: true,
56        }
57    }
58}
59
60impl Default for BullBearPower {
61    fn default() -> Self {
62        Self::new(13)
63    }
64}
65
66impl Indicator for BullBearPower {
67    crate::studies::inputs::study_inputs!(self;
68        period: length("Length", 1, usize::MAX)
69    ; {});
70
71    fn plot_style(&self, _line: usize) -> crate::studies::PlotStyle {
72        crate::studies::PlotStyle {
73            color: self.positive_color,
74            width: 1.0,
75            pattern: crate::model::LineStyle::Solid,
76            plot_type: crate::studies::PlotType::Columns,
77            visible: true,
78        }
79    }
80
81    fn plot_color(&self, _line: usize, bar: usize) -> Option<Color32> {
82        let value = self.values.value(0, bar)?;
83        Some(if value < 0.0 {
84            self.negative_color
85        } else {
86            self.positive_color
87        })
88    }
89
90    fn value_format(&self) -> crate::scales::StudyFormat {
91        crate::scales::StudyFormat::Price
92    }
93
94    fn name(&self) -> &str {
95        "Bull Bear Power"
96    }
97
98    fn desc(&self) -> &str {
99        "Bull Bear Power - High and low against the EMA of the close"
100    }
101
102    fn calculate(&mut self, data: &[Bar]) {
103        self.update(data, 0);
104    }
105
106    fn update(&mut self, data: &[Bar], from: usize) {
107        self.stepper
108            .run(data, from, &mut self.values, |state, bar| {
109                if ![bar.high, bar.low, bar.close].iter().all(|x| x.is_finite()) {
110                    return IndicatorValue::None;
111                }
112                let ema = state.next(bar.close, self.period, 2.0 / (self.period as f64 + 1.0));
113                let (bull, bear) = (bar.high - ema, bar.low - ema);
114                if bull.is_finite() && bear.is_finite() {
115                    IndicatorValue::Single(bull + bear)
116                } else {
117                    IndicatorValue::None
118                }
119            });
120    }
121
122    fn is_incremental(&self) -> bool {
123        true
124    }
125
126    fn values(&self) -> &IndicatorValues {
127        &self.values
128    }
129
130    fn colors(&self) -> Vec<Color32> {
131        vec![self.positive_color, self.negative_color]
132    }
133
134    fn set_colors(&mut self, colors: Vec<Color32>) {
135        if let Some(&color) = colors.first() {
136            self.positive_color = color;
137        }
138        if let Some(&color) = colors.get(1) {
139            self.negative_color = color;
140        }
141    }
142
143    fn is_overlay(&self) -> bool {
144        false
145    }
146
147    fn line_count(&self) -> usize {
148        1
149    }
150
151    fn is_visible(&self) -> bool {
152        self.visible
153    }
154
155    fn set_visible(&mut self, visible: bool) {
156        self.visible = visible;
157    }
158
159    fn clone_box(&self) -> Box<dyn Indicator> {
160        Box::new(self.clone())
161    }
162
163    fn line_names(&self) -> Vec<String> {
164        vec![format!("BBP({})", self.period)]
165    }
166}