nautilus_indicators/average/
vidya.rs1use std::fmt::{Display, Formatter};
17
18use nautilus_model::{
19 data::{Bar, QuoteTick, TradeTick},
20 enums::PriceType,
21};
22
23use crate::{
24 average::MovingAverageType,
25 indicator::{Indicator, MovingAverage},
26 momentum::cmo::ChandeMomentumOscillator,
27};
28
29#[repr(C)]
30#[derive(Debug)]
31#[cfg_attr(
32 feature = "python",
33 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators", unsendable)
34)]
35pub struct VariableIndexDynamicAverage {
36 pub period: usize,
37 pub alpha: f64,
38 pub price_type: PriceType,
39 pub value: f64,
40 pub count: usize,
41 pub initialized: bool,
42 has_inputs: bool,
43 pub cmo: ChandeMomentumOscillator,
44 pub cmo_pct: f64,
45}
46
47impl Display for VariableIndexDynamicAverage {
48 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}({})", self.name(), self.period)
50 }
51}
52
53impl Indicator for VariableIndexDynamicAverage {
54 fn name(&self) -> String {
55 stringify!(VariableIndexDynamicAverage).to_string()
56 }
57
58 fn has_inputs(&self) -> bool {
59 self.has_inputs
60 }
61
62 fn initialized(&self) -> bool {
63 self.initialized
64 }
65
66 fn handle_quote(&mut self, quote: &QuoteTick) {
67 self.update_raw(quote.extract_price(self.price_type).into());
68 }
69
70 fn handle_trade(&mut self, trade: &TradeTick) {
71 self.update_raw((&trade.price).into());
72 }
73
74 fn handle_bar(&mut self, bar: &Bar) {
75 self.update_raw((&bar.close).into());
76 }
77
78 fn reset(&mut self) {
79 self.value = 0.0;
80 self.count = 0;
81 self.cmo_pct = 0.0;
82 self.alpha = 0.0;
83 self.has_inputs = false;
84 self.initialized = false;
85 }
86}
87
88impl VariableIndexDynamicAverage {
89 #[must_use]
91 pub fn new(
92 period: usize,
93 price_type: Option<PriceType>,
94 cmo_ma_type: Option<MovingAverageType>,
95 ) -> Self {
96 Self {
97 period,
98 price_type: price_type.unwrap_or(PriceType::Last),
99 value: 0.0,
100 count: 0,
101 has_inputs: false,
102 initialized: false,
103 alpha: 2.0 / (period as f64 + 1.0),
104 cmo: ChandeMomentumOscillator::new(period, cmo_ma_type),
105 cmo_pct: 0.0,
106 }
107 }
108}
109
110impl MovingAverage for VariableIndexDynamicAverage {
111 fn value(&self) -> f64 {
112 self.value
113 }
114
115 fn count(&self) -> usize {
116 self.count
117 }
118
119 fn update_raw(&mut self, value: f64) {
120 self.cmo.update_raw(value);
121 self.cmo_pct = (self.cmo.value / 100.0).abs();
122
123 if self.initialized {
124 self.value = (self.alpha * self.cmo_pct)
125 .mul_add(value, self.alpha.mul_add(-self.cmo_pct, 1.0) * self.value);
126 }
127
128 if !self.initialized && self.cmo.initialized {
129 self.initialized = true;
130 }
131
132 self.count += 1;
133 }
134}
135
136#[cfg(test)]
140mod tests {
141 use nautilus_model::data::{Bar, QuoteTick, TradeTick};
142 use rstest::rstest;
143
144 use crate::{
145 average::vidya::VariableIndexDynamicAverage,
146 indicator::{Indicator, MovingAverage},
147 stubs::*,
148 };
149
150 #[rstest]
151 fn test_vidya_initialized(indicator_vidya_10: VariableIndexDynamicAverage) {
152 let display_st = format!("{indicator_vidya_10}");
153 assert_eq!(display_st, "VariableIndexDynamicAverage(10)");
154 assert_eq!(indicator_vidya_10.period, 10);
155 assert!(!indicator_vidya_10.initialized());
156 assert!(!indicator_vidya_10.has_inputs());
157 }
158
159 #[rstest]
160 fn test_initialized_with_required_input(mut indicator_vidya_10: VariableIndexDynamicAverage) {
161 for i in 1..10 {
162 indicator_vidya_10.update_raw(f64::from(i));
163 }
164 assert!(!indicator_vidya_10.initialized);
165 indicator_vidya_10.update_raw(10.0);
166 assert!(indicator_vidya_10.initialized);
167 }
168
169 #[rstest]
170 fn test_value_with_one_input(mut indicator_vidya_10: VariableIndexDynamicAverage) {
171 indicator_vidya_10.update_raw(1.0);
172 assert_eq!(indicator_vidya_10.value, 0.0);
173 }
174
175 #[rstest]
176 fn test_value_with_three_inputs(mut indicator_vidya_10: VariableIndexDynamicAverage) {
177 indicator_vidya_10.update_raw(1.0);
178 indicator_vidya_10.update_raw(2.0);
179 indicator_vidya_10.update_raw(3.0);
180 assert_eq!(indicator_vidya_10.value, 0.0);
181 }
182
183 #[rstest]
184 fn test_value_with_ten_inputs(mut indicator_vidya_10: VariableIndexDynamicAverage) {
185 indicator_vidya_10.update_raw(1.00000);
186 indicator_vidya_10.update_raw(1.00010);
187 indicator_vidya_10.update_raw(1.00020);
188 indicator_vidya_10.update_raw(1.00030);
189 indicator_vidya_10.update_raw(1.00040);
190 indicator_vidya_10.update_raw(1.00050);
191 indicator_vidya_10.update_raw(1.00040);
192 indicator_vidya_10.update_raw(1.00030);
193 indicator_vidya_10.update_raw(1.00020);
194 indicator_vidya_10.update_raw(1.00010);
195 indicator_vidya_10.update_raw(1.00000);
196 assert_eq!(indicator_vidya_10.value, 0.046_813_474_863_949_87);
197 }
198
199 #[rstest]
200 fn test_handle_quote_tick(
201 mut indicator_vidya_10: VariableIndexDynamicAverage,
202 stub_quote: QuoteTick,
203 ) {
204 indicator_vidya_10.handle_quote(&stub_quote);
205 assert_eq!(indicator_vidya_10.value, 0.0);
206 }
207
208 #[rstest]
209 fn test_handle_trade_tick(
210 mut indicator_vidya_10: VariableIndexDynamicAverage,
211 stub_trade: TradeTick,
212 ) {
213 indicator_vidya_10.handle_trade(&stub_trade);
214 assert_eq!(indicator_vidya_10.value, 0.0);
215 }
216
217 #[rstest]
218 fn test_handle_bar(
219 mut indicator_vidya_10: VariableIndexDynamicAverage,
220 bar_ethusdt_binance_minute_bid: Bar,
221 ) {
222 indicator_vidya_10.handle_bar(&bar_ethusdt_binance_minute_bid);
223 assert_eq!(indicator_vidya_10.value, 0.0);
224 assert!(!indicator_vidya_10.initialized);
225 }
226
227 #[rstest]
228 fn test_reset(mut indicator_vidya_10: VariableIndexDynamicAverage) {
229 indicator_vidya_10.update_raw(1.0);
230 assert_eq!(indicator_vidya_10.count, 1);
231 assert_eq!(indicator_vidya_10.value, 0.0);
232 indicator_vidya_10.reset();
233 assert_eq!(indicator_vidya_10.value, 0.0);
234 assert_eq!(indicator_vidya_10.count, 0);
235 assert!(!indicator_vidya_10.has_inputs);
236 assert!(!indicator_vidya_10.initialized);
237 }
238}