egui_charts/studies/builtin/
directional_movement.rs1use 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#[derive(Clone)]
15pub struct DirectionalMovement {
16 period: usize,
18 adx_smoothing: usize,
20 values: IndicatorValues,
21 stepper: Stepper<State>,
22 adx: ADX,
23 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 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, palette::ORANGE, Color32::from_rgb(245, 0, 87), ],
52 visible: true,
53 }
54 }
55
56 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 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 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}