Skip to main content

egui_charts/studies/builtin/
force_index.rs

1use super::smoothing::Smoothing;
2use crate::model::Bar;
3use crate::studies::stepper::Stepper;
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7/// Force Index
8/// Measures the force behind price movements using price change and volume
9/// Developed by Alexander Elder
10#[derive(Clone)]
11pub struct ForceIndex {
12    /// EMA period for smoothing (typically 13)
13    period: usize,
14    values: IndicatorValues,
15    stepper: Stepper<(Option<f64>, Smoothing)>,
16    color: Color32,
17    visible: bool,
18}
19
20impl ForceIndex {
21    /// Create with a lookback of `period` bars, clamped to at least 1.
22    pub fn new(period: usize) -> Self {
23        Self {
24            period: period.max(1),
25            values: IndicatorValues::new(),
26            stepper: Stepper::default(),
27            color: crate::studies::palette::BRIGHT_RED,
28            visible: true,
29        }
30    }
31
32    /// Set the plot color.
33    pub fn with_color(mut self, color: Color32) -> Self {
34        self.color = color;
35        self
36    }
37}
38
39/// Construct with the conventional default parameters.
40impl Default for ForceIndex {
41    fn default() -> Self {
42        Self::new(13)
43    }
44}
45
46impl Indicator for ForceIndex {
47    fn source(&self) -> Option<crate::studies::StudySource> {
48        Some(crate::studies::StudySource::Price(
49            crate::model::PriceSource::Close,
50        ))
51    }
52
53    crate::studies::inputs::study_inputs!(self;
54        period: length("Period", 1, usize::MAX)
55    ; {});
56
57    fn name(&self) -> &str {
58        "Force Index"
59    }
60
61    fn desc(&self) -> &str {
62        "Force Index - Measures force behind price movements"
63    }
64
65    fn calculate(&mut self, data: &[Bar]) {
66        self.update(data, 0);
67    }
68
69    fn update(&mut self, data: &[Bar], from: usize) {
70        self.stepper
71            .run(data, from, &mut self.values, |state, bar| {
72                if !bar.close.is_finite() || !bar.volume.is_finite() {
73                    return IndicatorValue::None;
74                }
75                let raw = state
76                    .0
77                    .map_or(f64::NAN, |previous| (bar.close - previous) * bar.volume);
78                state.0 = Some(bar.close);
79                IndicatorValue::Single(state.1.next(
80                    raw,
81                    self.period,
82                    2.0 / (self.period as f64 + 1.0),
83                ))
84            });
85    }
86
87    fn is_incremental(&self) -> bool {
88        true
89    }
90
91    fn values(&self) -> &IndicatorValues {
92        &self.values
93    }
94
95    fn colors(&self) -> Vec<Color32> {
96        vec![self.color]
97    }
98
99    fn set_colors(&mut self, colors: Vec<Color32>) {
100        if !colors.is_empty() {
101            self.color = colors[0];
102        }
103    }
104
105    fn is_overlay(&self) -> bool {
106        false // Separate pane
107    }
108
109    fn is_visible(&self) -> bool {
110        self.visible
111    }
112
113    fn set_visible(&mut self, visible: bool) {
114        self.visible = visible;
115    }
116
117    fn clone_box(&self) -> Box<dyn Indicator> {
118        Box::new(self.clone())
119    }
120
121    fn line_names(&self) -> Vec<String> {
122        vec![format!("Force({})", self.period)]
123    }
124}