nautilus_indicators/momentum/
amat.rs1use std::{
17 collections::VecDeque,
18 fmt::{Debug, Display},
19};
20
21use nautilus_model::data::Bar;
22
23use crate::{
24 average::{MovingAverageFactory, MovingAverageType},
25 indicator::{Indicator, MovingAverage},
26};
27
28#[repr(C)]
29#[derive(Debug)]
30#[cfg_attr(
31 feature = "python",
32 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators", unsendable)
33)]
34pub struct ArcherMovingAveragesTrends {
35 pub fast_period: usize,
36 pub slow_period: usize,
37 pub signal_period: usize,
38 pub ma_type: MovingAverageType,
39 pub long_run: bool,
40 pub short_run: bool,
41 pub initialized: bool,
42 fast_ma: Box<dyn MovingAverage + Send + 'static>,
43 slow_ma: Box<dyn MovingAverage + Send + 'static>,
44 fast_ma_price: VecDeque<f64>,
45 slow_ma_price: VecDeque<f64>,
46 has_inputs: bool,
47}
48
49impl Display for ArcherMovingAveragesTrends {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(
52 f,
53 "{}({},{},{},{})",
54 self.name(),
55 self.fast_period,
56 self.slow_period,
57 self.signal_period,
58 self.ma_type,
59 )
60 }
61}
62
63impl Indicator for ArcherMovingAveragesTrends {
64 fn name(&self) -> String {
65 stringify!(ArcherMovingAveragesTrends).to_string()
66 }
67
68 fn has_inputs(&self) -> bool {
69 self.has_inputs
70 }
71
72 fn initialized(&self) -> bool {
73 self.initialized
74 }
75
76 fn handle_bar(&mut self, bar: &Bar) {
77 self.update_raw((&bar.close).into());
78 }
79
80 fn reset(&mut self) {
81 self.fast_ma.reset();
82 self.slow_ma.reset();
83 self.long_run = false;
84 self.short_run = false;
85 self.fast_ma_price.clear();
86 self.slow_ma_price.clear();
87 self.has_inputs = false;
88 self.initialized = false;
89 }
90}
91
92impl ArcherMovingAveragesTrends {
93 #[must_use]
95 pub fn new(
96 fast_period: usize,
97 slow_period: usize,
98 signal_period: usize,
99 ma_type: Option<MovingAverageType>,
100 ) -> Self {
101 Self {
102 fast_period,
103 slow_period,
104 signal_period,
105 ma_type: ma_type.unwrap_or(MovingAverageType::Simple),
106 long_run: false,
107 short_run: false,
108 fast_ma: MovingAverageFactory::create(
109 ma_type.unwrap_or(MovingAverageType::Simple),
110 fast_period,
111 ),
112 slow_ma: MovingAverageFactory::create(
113 ma_type.unwrap_or(MovingAverageType::Simple),
114 slow_period,
115 ),
116 fast_ma_price: VecDeque::with_capacity(signal_period + 1),
117 slow_ma_price: VecDeque::with_capacity(signal_period + 1),
118 has_inputs: false,
119 initialized: false,
120 }
121 }
122
123 pub fn update_raw(&mut self, close: f64) {
124 self.fast_ma.update_raw(close);
125 self.slow_ma.update_raw(close);
126
127 if self.slow_ma.initialized() {
128 self.fast_ma_price.push_back(self.fast_ma.value());
129 self.slow_ma_price.push_back(self.slow_ma.value());
130
131 let fast_back = self.fast_ma.value();
132 let slow_back = self.slow_ma.value();
133 let fast_front = self.fast_ma_price.front().unwrap();
135 let slow_front = self.slow_ma_price.front().unwrap();
136
137 self.long_run = fast_back - fast_front > 0.0 && slow_back - slow_front < 0.0;
138
139 self.long_run =
140 fast_back - fast_front > 0.0 && slow_back - slow_front > 0.0 || self.long_run;
141
142 self.short_run = fast_back - fast_front < 0.0 && slow_back - slow_front > 0.0;
143
144 self.short_run =
145 fast_back - fast_front < 0.0 && slow_back - slow_front < 0.0 || self.short_run;
146 }
147
148 if !self.initialized {
150 self.has_inputs = true;
151 if self.slow_ma_price.len() > self.signal_period && self.slow_ma.initialized() {
152 self.initialized = true;
153 }
154 }
155 }
156}
157
158#[cfg(test)]
162mod tests {
163 use rstest::rstest;
164
165 use super::*;
166 use crate::stubs::amat_345;
167
168 #[rstest]
169 fn test_name_returns_expected_string(amat_345: ArcherMovingAveragesTrends) {
170 assert_eq!(amat_345.name(), "ArcherMovingAveragesTrends");
171 }
172
173 #[rstest]
174 fn test_str_repr_returns_expected_string(amat_345: ArcherMovingAveragesTrends) {
175 assert_eq!(
176 format!("{amat_345}"),
177 "ArcherMovingAveragesTrends(3,4,5,SIMPLE)"
178 );
179 }
180
181 #[rstest]
182 fn test_period_returns_expected_value(amat_345: ArcherMovingAveragesTrends) {
183 assert_eq!(amat_345.fast_period, 3);
184 assert_eq!(amat_345.slow_period, 4);
185 assert_eq!(amat_345.signal_period, 5);
186 }
187
188 #[rstest]
189 fn test_initialized_without_inputs_returns_false(amat_345: ArcherMovingAveragesTrends) {
190 assert!(!amat_345.initialized());
191 }
192
193 #[rstest]
194 fn test_value_with_all_higher_inputs_returns_expected_value(
195 mut amat_345: ArcherMovingAveragesTrends,
196 ) {
197 let closes = [
198 0.9, 1.9, 2.9, 3.9, 4.9, 3.2, 6.9, 7.9, 8.9, 9.9, 1.1, 3.2, 10.3, 11.1, 11.4,
199 ];
200
201 for close in &closes {
202 amat_345.update_raw(*close);
203 }
204
205 assert!(amat_345.initialized());
206 assert!(amat_345.long_run);
207 assert!(!amat_345.short_run);
208 }
209
210 #[rstest]
211 fn test_reset_successfully_returns_indicator_to_fresh_state(
212 mut amat_345: ArcherMovingAveragesTrends,
213 ) {
214 amat_345.update_raw(1.00020);
215 amat_345.update_raw(1.00030);
216 amat_345.update_raw(1.00070);
217
218 amat_345.reset();
219
220 assert!(!amat_345.initialized());
221 assert!(!amat_345.long_run);
222 assert!(!amat_345.short_run);
223 }
224}