egui_charts/studies/builtin/
trix.rs1use super::smoothing::Smoothing;
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)]
12pub struct TRIX {
13 period: usize,
14 signal_period: usize,
15 values: IndicatorValues,
16 stepper: Stepper<State>,
17 colors: Vec<Color32>,
19 visible: bool,
20}
21
22#[derive(Clone, Default)]
23struct State {
24 ema: [Smoothing; 3],
25 signal: Smoothing,
26 previous: Option<f64>,
27 count: usize,
28 bars: usize,
29 pending: Vec<(usize, [f64; 2])>,
30}
31
32impl TRIX {
33 pub fn new(period: usize) -> Self {
35 Self {
36 period: period.max(1),
37 signal_period: 9,
38 values: IndicatorValues::new(),
39 stepper: Stepper::default(),
40 colors: vec![
41 palette::BRIGHT_RED, palette::ORANGE, ],
44 visible: true,
45 }
46 }
47
48 pub fn with_signal(mut self, signal_period: usize) -> Self {
50 self.signal_period = signal_period.max(1);
51 self
52 }
53
54 pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
56 self.colors = colors;
57 self
58 }
59}
60
61impl Default for TRIX {
63 fn default() -> Self {
64 Self::new(18)
65 }
66}
67
68impl Indicator for TRIX {
69 fn source(&self) -> Option<crate::studies::StudySource> {
70 Some(crate::studies::StudySource::Price(
71 crate::model::PriceSource::Close,
72 ))
73 }
74
75 crate::studies::inputs::study_inputs!(self;
76 period: length("Period", 1, usize::MAX),
77 signal_period: length("Signal period", 1, usize::MAX)
78 ; {});
79
80 fn value_format(&self) -> crate::scales::StudyFormat {
81 crate::scales::StudyFormat::Percent
82 }
83
84 fn name(&self) -> &str {
85 "TRIX"
86 }
87
88 fn desc(&self) -> &str {
89 "TRIX - Rate of change of triple-smoothed EMA"
90 }
91
92 fn calculate(&mut self, data: &[Bar]) {
93 self.update(data, 0);
94 }
95
96 fn update(&mut self, data: &[Bar], from: usize) {
97 let required = self.period.saturating_mul(3).saturating_add(1);
98 self.stepper
99 .run(data, from, &mut self.values, |state, bar| {
100 let index = state.bars;
101 state.bars += 1;
102 if !bar.close.is_finite() {
103 return IndicatorValue::None;
104 }
105 state.count += 1;
106 let alpha = 2.0 / (self.period as f64 + 1.0);
107 let ema1 = state.ema[0].next(bar.close, self.period, alpha);
108 let ema2 = state.ema[1].next(ema1, self.period, alpha);
109 let ema3 = state.ema[2].next(ema2, self.period, alpha);
110 let previous = state.previous.replace(ema3).unwrap_or(f64::NAN);
111 let trix = if previous == 0.0 {
112 f64::NAN
113 } else {
114 (ema3 - previous) / previous * 100.0
115 };
116 let signal = state.signal.next(
117 trix,
118 self.signal_period,
119 2.0 / (self.signal_period as f64 + 1.0),
120 );
121 if state.count < required {
122 if trix.is_finite() {
123 state.pending.push((index, [trix, signal]));
124 }
125 IndicatorValue::None
126 } else {
127 IndicatorValue::Multiple(vec![trix, signal])
128 }
129 });
130 for &(index, row) in &self.stepper.state().pending {
132 self.values.set(
133 index,
134 if self.stepper.state().count < required {
135 IndicatorValue::None
136 } else {
137 IndicatorValue::Multiple(row.to_vec())
138 },
139 );
140 }
141 }
142
143 fn is_incremental(&self) -> bool {
144 true
145 }
146
147 fn values(&self) -> &IndicatorValues {
148 &self.values
149 }
150
151 fn colors(&self) -> Vec<Color32> {
152 self.colors.clone()
153 }
154
155 fn set_colors(&mut self, colors: Vec<Color32>) {
156 if colors.len() >= 2 {
157 self.colors = colors;
158 }
159 }
160
161 fn is_overlay(&self) -> bool {
162 false }
164
165 fn is_visible(&self) -> bool {
166 self.visible
167 }
168
169 fn set_visible(&mut self, visible: bool) {
170 self.visible = visible;
171 }
172
173 fn clone_box(&self) -> Box<dyn Indicator> {
174 Box::new(self.clone())
175 }
176
177 fn line_names(&self) -> Vec<String> {
178 vec![
179 format!("TRIX({})", self.period),
180 format!("Signal({})", self.signal_period),
181 ]
182 }
183}