Skip to main content

finance_query/backtesting/refs/
moving_averages.rs

1use crate::backtesting::strategy::StrategyContext;
2use crate::indicators::Indicator;
3
4use super::IndicatorRef;
5
6/// Simple Moving Average reference.
7#[derive(Debug, Clone)]
8pub struct SmaRef {
9    pub period: usize,
10    key: String,
11}
12
13impl IndicatorRef for SmaRef {
14    fn key(&self) -> &str {
15        &self.key
16    }
17
18    fn required_indicators(&self) -> Vec<(String, Indicator)> {
19        vec![(self.key.clone(), Indicator::Sma(self.period))]
20    }
21
22    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
23        ctx.indicator(self.key())
24    }
25
26    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
27        ctx.indicator_prev(self.key())
28    }
29}
30
31/// Create a Simple Moving Average reference.
32///
33/// # Example
34///
35/// ```ignore
36/// use finance_query::backtesting::refs::*;
37///
38/// let sma_20 = sma(20);
39/// let golden_cross = sma(50).crosses_above_ref(sma(200));
40/// ```
41#[inline]
42pub fn sma(period: usize) -> SmaRef {
43    SmaRef {
44        period,
45        key: format!("sma_{period}"),
46    }
47}
48
49/// Exponential Moving Average reference.
50#[derive(Debug, Clone)]
51pub struct EmaRef {
52    pub period: usize,
53    key: String,
54}
55
56impl IndicatorRef for EmaRef {
57    fn key(&self) -> &str {
58        &self.key
59    }
60
61    fn required_indicators(&self) -> Vec<(String, Indicator)> {
62        vec![(self.key.clone(), Indicator::Ema(self.period))]
63    }
64
65    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
66        ctx.indicator(self.key())
67    }
68
69    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
70        ctx.indicator_prev(self.key())
71    }
72}
73
74/// Create an Exponential Moving Average reference.
75#[inline]
76pub fn ema(period: usize) -> EmaRef {
77    EmaRef {
78        period,
79        key: format!("ema_{period}"),
80    }
81}
82
83/// Weighted Moving Average reference.
84#[derive(Debug, Clone)]
85pub struct WmaRef {
86    pub period: usize,
87    key: String,
88}
89
90impl IndicatorRef for WmaRef {
91    fn key(&self) -> &str {
92        &self.key
93    }
94
95    fn required_indicators(&self) -> Vec<(String, Indicator)> {
96        vec![(self.key.clone(), Indicator::Wma(self.period))]
97    }
98
99    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
100        ctx.indicator(self.key())
101    }
102
103    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
104        ctx.indicator_prev(self.key())
105    }
106}
107
108/// Create a Weighted Moving Average reference.
109#[inline]
110pub fn wma(period: usize) -> WmaRef {
111    WmaRef {
112        period,
113        key: format!("wma_{period}"),
114    }
115}
116
117/// Double Exponential Moving Average reference.
118#[derive(Debug, Clone)]
119pub struct DemaRef {
120    pub period: usize,
121    key: String,
122}
123
124impl IndicatorRef for DemaRef {
125    fn key(&self) -> &str {
126        &self.key
127    }
128
129    fn required_indicators(&self) -> Vec<(String, Indicator)> {
130        vec![(self.key.clone(), Indicator::Dema(self.period))]
131    }
132
133    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
134        ctx.indicator(self.key())
135    }
136
137    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
138        ctx.indicator_prev(self.key())
139    }
140}
141
142/// Create a Double Exponential Moving Average reference.
143#[inline]
144pub fn dema(period: usize) -> DemaRef {
145    DemaRef {
146        period,
147        key: format!("dema_{period}"),
148    }
149}
150
151/// Triple Exponential Moving Average reference.
152#[derive(Debug, Clone)]
153pub struct TemaRef {
154    pub period: usize,
155    key: String,
156}
157
158impl IndicatorRef for TemaRef {
159    fn key(&self) -> &str {
160        &self.key
161    }
162
163    fn required_indicators(&self) -> Vec<(String, Indicator)> {
164        vec![(self.key.clone(), Indicator::Tema(self.period))]
165    }
166
167    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
168        ctx.indicator(self.key())
169    }
170
171    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
172        ctx.indicator_prev(self.key())
173    }
174}
175
176/// Create a Triple Exponential Moving Average reference.
177#[inline]
178pub fn tema(period: usize) -> TemaRef {
179    TemaRef {
180        period,
181        key: format!("tema_{period}"),
182    }
183}
184
185/// Hull Moving Average reference.
186#[derive(Debug, Clone)]
187pub struct HmaRef {
188    pub period: usize,
189    key: String,
190}
191
192impl IndicatorRef for HmaRef {
193    fn key(&self) -> &str {
194        &self.key
195    }
196
197    fn required_indicators(&self) -> Vec<(String, Indicator)> {
198        vec![(self.key.clone(), Indicator::Hma(self.period))]
199    }
200
201    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
202        ctx.indicator(self.key())
203    }
204
205    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
206        ctx.indicator_prev(self.key())
207    }
208}
209
210/// Create a Hull Moving Average reference.
211#[inline]
212pub fn hma(period: usize) -> HmaRef {
213    HmaRef {
214        period,
215        key: format!("hma_{period}"),
216    }
217}
218
219/// Volume Weighted Moving Average reference.
220#[derive(Debug, Clone)]
221pub struct VwmaRef {
222    pub period: usize,
223    key: String,
224}
225
226impl IndicatorRef for VwmaRef {
227    fn key(&self) -> &str {
228        &self.key
229    }
230
231    fn required_indicators(&self) -> Vec<(String, Indicator)> {
232        vec![(self.key.clone(), Indicator::Vwma(self.period))]
233    }
234
235    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
236        ctx.indicator(self.key())
237    }
238
239    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
240        ctx.indicator_prev(self.key())
241    }
242}
243
244/// Create a Volume Weighted Moving Average reference.
245#[inline]
246pub fn vwma(period: usize) -> VwmaRef {
247    VwmaRef {
248        period,
249        key: format!("vwma_{period}"),
250    }
251}
252
253/// McGinley Dynamic indicator reference.
254#[derive(Debug, Clone)]
255pub struct McginleyDynamicRef {
256    pub period: usize,
257    key: String,
258}
259
260impl IndicatorRef for McginleyDynamicRef {
261    fn key(&self) -> &str {
262        &self.key
263    }
264
265    fn required_indicators(&self) -> Vec<(String, Indicator)> {
266        vec![(self.key.clone(), Indicator::McginleyDynamic(self.period))]
267    }
268
269    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
270        ctx.indicator(self.key())
271    }
272
273    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
274        ctx.indicator_prev(self.key())
275    }
276}
277
278/// Create a McGinley Dynamic indicator reference.
279#[inline]
280pub fn mcginley(period: usize) -> McginleyDynamicRef {
281    McginleyDynamicRef {
282        period,
283        key: format!("mcginley_{period}"),
284    }
285}
286
287/// ALMA (Arnaud Legoux Moving Average) configuration.
288#[derive(Debug, Clone, Copy)]
289pub struct AlmaConfig {
290    pub period: usize,
291    pub offset: f64,
292    pub sigma: f64,
293}
294
295/// Create an ALMA configuration.
296#[inline]
297pub fn alma(period: usize, offset: f64, sigma: f64) -> AlmaRef {
298    AlmaRef::new(period, offset, sigma)
299}
300
301/// ALMA reference.
302#[derive(Debug, Clone)]
303pub struct AlmaRef {
304    pub period: usize,
305    pub offset: f64,
306    pub sigma: f64,
307    key: String,
308}
309
310impl AlmaRef {
311    fn new(period: usize, offset: f64, sigma: f64) -> Self {
312        Self {
313            period,
314            offset,
315            sigma,
316            key: format!("alma_{period}_{offset}_{sigma}"),
317        }
318    }
319}
320
321impl IndicatorRef for AlmaRef {
322    fn key(&self) -> &str {
323        &self.key
324    }
325
326    fn required_indicators(&self) -> Vec<(String, Indicator)> {
327        vec![(
328            self.key.clone(),
329            Indicator::Alma {
330                period: self.period,
331                offset: self.offset,
332                sigma: self.sigma,
333            },
334        )]
335    }
336
337    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
338        ctx.indicator(self.key())
339    }
340
341    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
342        ctx.indicator_prev(self.key())
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_moving_average_keys() {
352        assert_eq!(sma(20).key(), "sma_20");
353        assert_eq!(ema(12).key(), "ema_12");
354        assert_eq!(wma(14).key(), "wma_14");
355        assert_eq!(dema(21).key(), "dema_21");
356        assert_eq!(tema(21).key(), "tema_21");
357        assert_eq!(hma(9).key(), "hma_9");
358        assert_eq!(vwma(20).key(), "vwma_20");
359        assert_eq!(mcginley(14).key(), "mcginley_14");
360        assert_eq!(alma(9, 0.85, 6.0).key(), "alma_9_0.85_6");
361    }
362
363    #[test]
364    fn test_required_indicators() {
365        let sma_ref = sma(20);
366        let indicators = sma_ref.required_indicators();
367        assert_eq!(indicators.len(), 1);
368        assert_eq!(indicators[0].0, "sma_20");
369        assert!(matches!(indicators[0].1, Indicator::Sma(20)));
370    }
371}