egui_charts/studies/builtin/
smi_ergodic.rs1use super::TrueStrengthIndex;
34use crate::model::Bar;
35use crate::studies::palette;
36use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
37use egui::Color32;
38
39pub type SMIErgodicIndicator = SMIErgodic<false>;
41
42pub type SMIErgodicOscillator = SMIErgodic<true>;
44
45#[derive(Clone)]
47pub struct SMIErgodic<const OSCILLATOR: bool> {
48 long_length: usize,
49 short_length: usize,
50 signal_length: usize,
51 values: IndicatorValues,
52 tsi: TrueStrengthIndex,
53 colors: Vec<Color32>,
55 visible: bool,
56}
57
58impl<const OSCILLATOR: bool> SMIErgodic<OSCILLATOR> {
59 pub fn new(long_length: usize, short_length: usize, signal_length: usize) -> Self {
61 Self {
62 long_length: long_length.max(1),
63 short_length: short_length.max(1),
64 signal_length: signal_length.max(1),
65 values: IndicatorValues::new(),
66 tsi: TrueStrengthIndex::new(long_length, short_length, signal_length),
67 colors: if OSCILLATOR {
68 vec![Color32::from_rgb(255, 82, 82)]
69 } else {
70 vec![palette::BLUE, palette::ORANGE]
71 },
72 visible: true,
73 }
74 }
75}
76
77impl<const OSCILLATOR: bool> Default for SMIErgodic<OSCILLATOR> {
78 fn default() -> Self {
79 Self::new(20, 5, 5)
80 }
81}
82
83impl<const OSCILLATOR: bool> Indicator for SMIErgodic<OSCILLATOR> {
84 fn source(&self) -> Option<crate::studies::StudySource> {
85 Some(crate::studies::StudySource::Price(
86 crate::model::PriceSource::Close,
87 ))
88 }
89
90 crate::studies::inputs::study_inputs!(self;
91 long_length: length("Long length", 1, usize::MAX),
92 short_length: length("Short length", 1, usize::MAX),
93 signal_length: length("Signal line length", 1, usize::MAX)
94 ; {});
95
96 fn plot_style(&self, line: usize) -> crate::studies::PlotStyle {
97 crate::studies::PlotStyle {
98 color: self.colors[line.min(self.colors.len() - 1)],
99 width: 1.0,
100 pattern: crate::model::LineStyle::Solid,
101 plot_type: if OSCILLATOR {
102 crate::studies::PlotType::Histogram
103 } else {
104 crate::studies::PlotType::Line
105 },
106 visible: true,
107 }
108 }
109
110 fn value_format(&self) -> crate::scales::StudyFormat {
111 crate::scales::StudyFormat::Fixed(4)
112 }
113
114 fn name(&self) -> &str {
115 if OSCILLATOR {
116 "SMI Ergodic Oscillator"
117 } else {
118 "SMI Ergodic Indicator"
119 }
120 }
121
122 fn desc(&self) -> &str {
123 if OSCILLATOR {
124 "SMI Ergodic Oscillator - SMI minus its signal line"
125 } else {
126 "SMI Ergodic Indicator - True strength ratio with a signal line"
127 }
128 }
129
130 fn calculate(&mut self, data: &[Bar]) {
131 self.update(data, 0);
132 }
133
134 fn update(&mut self, data: &[Bar], from: usize) {
135 if from == 0 {
136 self.tsi =
137 TrueStrengthIndex::new(self.long_length, self.short_length, self.signal_length);
138 }
139 self.tsi.update(data, from);
140 let start = from.min(self.values.len()).min(data.len());
141 self.values.truncate(start);
142 for bar in start..data.len() {
143 let (Some(smi), Some(signal)) = (
144 self.tsi.values().value(0, bar),
145 self.tsi.values().value(1, bar),
146 ) else {
147 self.values.push(IndicatorValue::None);
148 continue;
149 };
150 let (smi, signal) = (smi / 100.0, signal / 100.0);
151 if OSCILLATOR {
152 self.values.push(IndicatorValue::Single(smi - signal));
153 } else {
154 self.values.push_lines(&[smi, signal]);
155 }
156 }
157 }
158
159 fn is_incremental(&self) -> bool {
160 true
161 }
162
163 fn values(&self) -> &IndicatorValues {
164 &self.values
165 }
166
167 fn colors(&self) -> Vec<Color32> {
168 self.colors.clone()
169 }
170
171 fn set_colors(&mut self, colors: Vec<Color32>) {
172 if colors.len() >= self.line_count() {
173 self.colors = colors;
174 }
175 }
176
177 fn is_overlay(&self) -> bool {
178 false
179 }
180
181 fn is_visible(&self) -> bool {
182 self.visible
183 }
184
185 fn set_visible(&mut self, visible: bool) {
186 self.visible = visible;
187 }
188
189 fn clone_box(&self) -> Box<dyn Indicator> {
190 Box::new(self.clone())
191 }
192
193 fn line_count(&self) -> usize {
194 if OSCILLATOR { 1 } else { 2 }
195 }
196
197 fn line_names(&self) -> Vec<String> {
198 let smi = format!("SMI({},{})", self.long_length, self.short_length);
199 if OSCILLATOR {
200 vec![format!("{smi} - Signal({})", self.signal_length)]
201 } else {
202 vec![smi, format!("Signal({})", self.signal_length)]
203 }
204 }
205}