Skip to main content

egui_charts/studies/builtin/
balance_of_power.rs

1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::{update_window, usable_behind};
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// Balance of Power (BoP): the strength of buyers against sellers,
9/// `(close - open) / (high - low)` per bar, smoothed with a simple average of
10/// `smooth_period` bars.
11///
12/// - A bar without range (`high == low`) counts as 0.
13/// - With smoothing of length `n`, bars 0 to `n - 2` have no value, and a series shorter
14///   than `n` has none on any bar. Length 1 (or 0) keeps the unsmoothed ratio on every bar.
15/// - A bar with a non-finite price has no value, and the average is taken as if it were not
16///   in the series.
17#[derive(Clone)]
18pub struct BalanceOfPower {
19    /// Length of the simple average (0 or 1 = no smoothing)
20    smooth_period: usize,
21    values: IndicatorValues,
22    color: Color32,
23    visible: bool,
24}
25
26impl BalanceOfPower {
27    /// Create with `smooth_period` in bars, each clamped to at least one.
28    pub fn new(smooth_period: usize) -> Self {
29        Self {
30            smooth_period: smooth_period.max(1),
31            values: IndicatorValues::new(),
32            color: palette::RED, // Teal
33            visible: true,
34        }
35    }
36
37    /// Set the plot color.
38    pub fn with_color(mut self, color: Color32) -> Self {
39        self.color = color;
40        self
41    }
42
43    /// The values over `data`, every bar of which has finite prices.
44    fn compute(&self, data: &[Bar]) -> IndicatorValues {
45        let raw: Vec<f64> = data
46            .iter()
47            .map(|bar| {
48                let range = bar.high - bar.low;
49                if range == 0.0 {
50                    0.0
51                } else {
52                    (bar.close - bar.open) / range
53                }
54            })
55            .collect();
56        (0..raw.len())
57            .map(|i| match i + 1 {
58                n if n < self.smooth_period => IndicatorValue::None,
59                n => IndicatorValue::Single(
60                    raw[n - self.smooth_period..n].iter().sum::<f64>() / self.smooth_period as f64,
61                ),
62            })
63            .collect()
64    }
65}
66
67/// Construct with the conventional default parameters.
68impl Default for BalanceOfPower {
69    fn default() -> Self {
70        Self::new(14)
71    }
72}
73
74impl Indicator for BalanceOfPower {
75    crate::studies::inputs::study_inputs!(self;
76        smooth_period: length("Smooth period", 1, usize::MAX)
77    ; {});
78
79    fn name(&self) -> &str {
80        "BoP"
81    }
82
83    fn desc(&self) -> &str {
84        "Balance of Power - Measures buyer/seller strength"
85    }
86
87    fn calculate(&mut self, data: &[Bar]) {
88        self.update(data, 0);
89    }
90
91    fn update(&mut self, data: &[Bar], from: usize) {
92        let mut values = std::mem::take(&mut self.values);
93        update_window(
94            &mut values,
95            data,
96            from,
97            usable_behind(self.smooth_period - 1, |b| {
98                [b.open, b.high, b.low, b.close]
99                    .iter()
100                    .all(|p| p.is_finite())
101            }),
102            |data| {
103                over_finite_bars(
104                    data,
105                    |b| {
106                        [b.open, b.high, b.low, b.close]
107                            .iter()
108                            .all(|p| p.is_finite())
109                    },
110                    |data| self.compute(data),
111                )
112            },
113        );
114        self.values = values;
115    }
116
117    fn is_incremental(&self) -> bool {
118        true
119    }
120
121    fn values(&self) -> &IndicatorValues {
122        &self.values
123    }
124
125    fn colors(&self) -> Vec<Color32> {
126        vec![self.color]
127    }
128
129    fn set_colors(&mut self, colors: Vec<Color32>) {
130        if !colors.is_empty() {
131            self.color = colors[0];
132        }
133    }
134
135    fn is_overlay(&self) -> bool {
136        false
137    }
138
139    fn is_visible(&self) -> bool {
140        self.visible
141    }
142
143    fn set_visible(&mut self, visible: bool) {
144        self.visible = visible;
145    }
146
147    fn clone_box(&self) -> Box<dyn Indicator> {
148        Box::new(self.clone())
149    }
150
151    fn line_names(&self) -> Vec<String> {
152        if self.smooth_period > 1 {
153            vec![format!("BoP({})", self.smooth_period)]
154        } else {
155            vec!["BoP".to_string()]
156        }
157    }
158}