Skip to main content

egui_charts/studies/builtin/
directional_movement.rs

1use super::ADX;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::Stepper;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// Directional Movement Index (DMI / DI+ / DI-)
9/// Shows the positive and negative directional indicators and the ADX, Wilder's average of DX
10/// over the ADX smoothing period (Wilder 1978)
11///
12/// The `period` input sets both lengths, as studies saved with a single length expect;
13/// `adx_smoothing`, which follows it in `inputs()`, then sets the smoothing alone.
14#[derive(Clone)]
15pub struct DirectionalMovement {
16    /// Period of the directional indicators (typically 14)
17    period: usize,
18    /// Length of Wilder's average of DX that forms the ADX line.
19    adx_smoothing: usize,
20    values: IndicatorValues,
21    stepper: Stepper<State>,
22    adx: ADX,
23    /// Colors: [+DI, -DI, ADX]
24    colors: Vec<Color32>,
25    visible: bool,
26}
27
28#[derive(Clone, Default)]
29struct State {
30    index: usize,
31    previous: Option<[f64; 3]>,
32    count: usize,
33    plus: f64,
34    minus: f64,
35    tr: f64,
36}
37
38impl DirectionalMovement {
39    /// Create with a lookback of `period` bars, clamped to at least 1.
40    pub fn new(period: usize) -> Self {
41        Self {
42            period: period.max(1),
43            adx_smoothing: period.max(1),
44            values: IndicatorValues::new(),
45            stepper: Stepper::default(),
46            adx: ADX::new(period),
47            colors: vec![
48                palette::BLUE,                 // +DI
49                palette::ORANGE,               // -DI
50                Color32::from_rgb(245, 0, 87), // ADX
51            ],
52            visible: true,
53        }
54    }
55
56    /// Set the length of Wilder's average of DX that forms the ADX line, clamped to at least one.
57    /// `new` sets it to the DI period.
58    pub fn with_smoothing(mut self, smoothing: usize) -> Self {
59        self.adx_smoothing = smoothing.max(1);
60        self.adx = ADX::new(self.period).with_smoothing(self.adx_smoothing);
61        self
62    }
63
64    /// Set plot colors in the order returned by [`Indicator::line_names`].
65    pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
66        self.colors = colors;
67        self
68    }
69}
70
71impl Default for DirectionalMovement {
72    fn default() -> Self {
73        Self::new(14)
74    }
75}
76
77impl Indicator for DirectionalMovement {
78    crate::studies::inputs::study_inputs!(self;
79        period: length("DI period", 1, usize::MAX),
80        adx_smoothing: length("ADX smoothing period", 1, usize::MAX)
81    ; changed => {
82        // Before the smoothing input existed, `period` set both lengths; saved studies still do.
83        if changed == "period" {
84            self.adx_smoothing = self.period;
85        }
86    });
87
88    fn name(&self) -> &str {
89        "DMI"
90    }
91
92    fn desc(&self) -> &str {
93        "Directional Movement Index - +DI, -DI and ADX lines"
94    }
95
96    fn calculate(&mut self, data: &[Bar]) {
97        self.update(data, 0);
98    }
99
100    fn update(&mut self, data: &[Bar], from: usize) {
101        if from == 0 {
102            self.adx = ADX::new(self.period).with_smoothing(self.adx_smoothing);
103        }
104        self.adx.update(data, from);
105        self.stepper
106            .run(data, from, &mut self.values, |state, bar| {
107                let index = state.index;
108                state.index += 1;
109                if ![bar.high, bar.low, bar.close].iter().all(|x| x.is_finite()) {
110                    return IndicatorValue::None;
111                }
112                let Some(previous) = state.previous.replace([bar.high, bar.low, bar.close]) else {
113                    return IndicatorValue::None;
114                };
115                state.count += 1;
116                let up = bar.high - previous[0];
117                let down = previous[1] - bar.low;
118                let plus = if up > down && up > 0.0 { up } else { 0.0 };
119                let minus = if down > up && down > 0.0 { down } else { 0.0 };
120                let tr = (bar.high - bar.low)
121                    .max((bar.high - previous[2]).abs())
122                    .max((bar.low - previous[2]).abs());
123                if state.count <= self.period {
124                    state.plus += plus;
125                    state.minus += minus;
126                    state.tr += tr;
127                    if state.count < self.period {
128                        return IndicatorValue::None;
129                    }
130                } else {
131                    state.plus = state.plus - state.plus / self.period as f64 + plus;
132                    state.minus = state.minus - state.minus / self.period as f64 + minus;
133                    state.tr = state.tr - state.tr / self.period as f64 + tr;
134                }
135                let adx = self.adx.values().value(0, index).unwrap_or(f64::NAN);
136                if state.tr.is_nan() || state.tr == 0.0 {
137                    if adx.is_nan() {
138                        IndicatorValue::None
139                    } else {
140                        IndicatorValue::Multiple(vec![f64::NAN, f64::NAN, adx])
141                    }
142                } else {
143                    IndicatorValue::Multiple(vec![
144                        100.0 * state.plus / state.tr,
145                        100.0 * state.minus / state.tr,
146                        adx,
147                    ])
148                }
149            });
150    }
151
152    fn is_incremental(&self) -> bool {
153        true
154    }
155
156    fn values(&self) -> &IndicatorValues {
157        &self.values
158    }
159
160    fn colors(&self) -> Vec<Color32> {
161        self.colors.clone()
162    }
163
164    fn set_colors(&mut self, colors: Vec<Color32>) {
165        for (line, color) in self.colors.iter_mut().zip(colors) {
166            *line = color;
167        }
168    }
169
170    fn is_overlay(&self) -> bool {
171        false
172    }
173
174    fn is_visible(&self) -> bool {
175        self.visible
176    }
177
178    fn set_visible(&mut self, visible: bool) {
179        self.visible = visible;
180    }
181
182    fn clone_box(&self) -> Box<dyn Indicator> {
183        Box::new(self.clone())
184    }
185
186    fn line_names(&self) -> Vec<String> {
187        vec![
188            format!("+DI({})", self.period),
189            format!("-DI({})", self.period),
190            if self.adx_smoothing == self.period {
191                format!("ADX({})", self.period)
192            } else {
193                format!("ADX({},{})", self.period, self.adx_smoothing)
194            },
195        ]
196    }
197}