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