Skip to main content

finance_query/backtesting/refs/
oscillators.rs

1use crate::backtesting::strategy::StrategyContext;
2use crate::indicators::Indicator;
3
4use super::IndicatorRef;
5
6/// Relative Strength Index reference.
7#[derive(Debug, Clone)]
8pub struct RsiRef {
9    pub period: usize,
10    key: String,
11}
12
13impl IndicatorRef for RsiRef {
14    fn key(&self) -> &str {
15        &self.key
16    }
17
18    fn required_indicators(&self) -> Vec<(String, Indicator)> {
19        vec![(self.key.clone(), Indicator::Rsi(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 Relative Strength Index reference.
32///
33/// # Example
34///
35/// ```ignore
36/// use finance_query::backtesting::refs::*;
37///
38/// let oversold = rsi(14).below(30.0);
39/// let overbought = rsi(14).above(70.0);
40/// let exit_oversold = rsi(14).crosses_above(30.0);
41/// ```
42#[inline]
43pub fn rsi(period: usize) -> RsiRef {
44    RsiRef {
45        period,
46        key: format!("rsi_{period}"),
47    }
48}
49
50/// Commodity Channel Index reference.
51#[derive(Debug, Clone)]
52pub struct CciRef {
53    pub period: usize,
54    key: String,
55}
56
57impl IndicatorRef for CciRef {
58    fn key(&self) -> &str {
59        &self.key
60    }
61
62    fn required_indicators(&self) -> Vec<(String, Indicator)> {
63        vec![(self.key.clone(), Indicator::Cci(self.period))]
64    }
65
66    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
67        ctx.indicator(self.key())
68    }
69
70    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
71        ctx.indicator_prev(self.key())
72    }
73}
74
75/// Create a Commodity Channel Index reference.
76#[inline]
77pub fn cci(period: usize) -> CciRef {
78    CciRef {
79        period,
80        key: format!("cci_{period}"),
81    }
82}
83
84/// Williams %R reference.
85#[derive(Debug, Clone)]
86pub struct WilliamsRRef {
87    pub period: usize,
88    key: String,
89}
90
91impl IndicatorRef for WilliamsRRef {
92    fn key(&self) -> &str {
93        &self.key
94    }
95
96    fn required_indicators(&self) -> Vec<(String, Indicator)> {
97        vec![(self.key.clone(), Indicator::WilliamsR(self.period))]
98    }
99
100    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
101        ctx.indicator(self.key())
102    }
103
104    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
105        ctx.indicator_prev(self.key())
106    }
107}
108
109/// Create a Williams %R reference.
110#[inline]
111pub fn williams_r(period: usize) -> WilliamsRRef {
112    WilliamsRRef {
113        period,
114        key: format!("williams_r_{period}"),
115    }
116}
117
118/// Chande Momentum Oscillator reference.
119#[derive(Debug, Clone)]
120pub struct CmoRef {
121    pub period: usize,
122    key: String,
123}
124
125impl IndicatorRef for CmoRef {
126    fn key(&self) -> &str {
127        &self.key
128    }
129
130    fn required_indicators(&self) -> Vec<(String, Indicator)> {
131        vec![(self.key.clone(), Indicator::Cmo(self.period))]
132    }
133
134    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
135        ctx.indicator(self.key())
136    }
137
138    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
139        ctx.indicator_prev(self.key())
140    }
141}
142
143/// Create a Chande Momentum Oscillator reference.
144#[inline]
145pub fn cmo(period: usize) -> CmoRef {
146    CmoRef {
147        period,
148        key: format!("cmo_{period}"),
149    }
150}
151
152/// Momentum indicator reference.
153#[derive(Debug, Clone)]
154pub struct MomentumRef {
155    pub period: usize,
156    key: String,
157}
158
159impl IndicatorRef for MomentumRef {
160    fn key(&self) -> &str {
161        &self.key
162    }
163
164    fn required_indicators(&self) -> Vec<(String, Indicator)> {
165        vec![(self.key.clone(), Indicator::Momentum(self.period))]
166    }
167
168    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
169        ctx.indicator(self.key())
170    }
171
172    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
173        ctx.indicator_prev(self.key())
174    }
175}
176
177/// Create a Momentum indicator reference.
178#[inline]
179pub fn momentum(period: usize) -> MomentumRef {
180    MomentumRef {
181        period,
182        key: format!("momentum_{period}"),
183    }
184}
185
186/// Rate of Change reference.
187#[derive(Debug, Clone)]
188pub struct RocRef {
189    pub period: usize,
190    key: String,
191}
192
193impl IndicatorRef for RocRef {
194    fn key(&self) -> &str {
195        &self.key
196    }
197
198    fn required_indicators(&self) -> Vec<(String, Indicator)> {
199        vec![(self.key.clone(), Indicator::Roc(self.period))]
200    }
201
202    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
203        ctx.indicator(self.key())
204    }
205
206    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
207        ctx.indicator_prev(self.key())
208    }
209}
210
211/// Create a Rate of Change reference.
212#[inline]
213pub fn roc(period: usize) -> RocRef {
214    RocRef {
215        period,
216        key: format!("roc_{period}"),
217    }
218}
219
220/// Stochastic Oscillator configuration.
221#[derive(Debug, Clone, Copy)]
222pub struct StochasticConfig {
223    pub k_period: usize,
224    pub k_slow: usize,
225    pub d_period: usize,
226}
227
228impl StochasticConfig {
229    /// Get the %K line reference.
230    pub fn k(&self) -> StochasticKRef {
231        StochasticKRef::new(self.k_period, self.k_slow, self.d_period)
232    }
233
234    /// Get the %D line reference.
235    pub fn d(&self) -> StochasticDRef {
236        StochasticDRef::new(self.k_period, self.k_slow, self.d_period)
237    }
238}
239
240/// Create a Stochastic Oscillator configuration.
241#[inline]
242pub fn stochastic(k_period: usize, k_slow: usize, d_period: usize) -> StochasticConfig {
243    StochasticConfig {
244        k_period,
245        k_slow,
246        d_period,
247    }
248}
249
250/// Stochastic %K line reference.
251#[derive(Debug, Clone)]
252pub struct StochasticKRef {
253    pub k_period: usize,
254    pub k_slow: usize,
255    pub d_period: usize,
256    key: String,
257}
258
259impl StochasticKRef {
260    fn new(k_period: usize, k_slow: usize, d_period: usize) -> Self {
261        Self {
262            k_period,
263            k_slow,
264            d_period,
265            key: format!("stochastic_k_{k_period}_{k_slow}_{d_period}"),
266        }
267    }
268}
269
270impl IndicatorRef for StochasticKRef {
271    fn key(&self) -> &str {
272        &self.key
273    }
274
275    fn required_indicators(&self) -> Vec<(String, Indicator)> {
276        vec![(
277            self.key.clone(),
278            Indicator::Stochastic {
279                k_period: self.k_period,
280                k_slow: self.k_slow,
281                d_period: self.d_period,
282            },
283        )]
284    }
285
286    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
287        ctx.indicator(self.key())
288    }
289
290    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
291        ctx.indicator_prev(self.key())
292    }
293}
294
295/// Stochastic %D line reference.
296#[derive(Debug, Clone)]
297pub struct StochasticDRef {
298    pub k_period: usize,
299    pub k_slow: usize,
300    pub d_period: usize,
301    key: String,
302}
303
304impl StochasticDRef {
305    fn new(k_period: usize, k_slow: usize, d_period: usize) -> Self {
306        Self {
307            k_period,
308            k_slow,
309            d_period,
310            key: format!("stochastic_d_{k_period}_{k_slow}_{d_period}"),
311        }
312    }
313}
314
315impl IndicatorRef for StochasticDRef {
316    fn key(&self) -> &str {
317        &self.key
318    }
319
320    fn required_indicators(&self) -> Vec<(String, Indicator)> {
321        vec![(
322            self.key.clone(),
323            Indicator::Stochastic {
324                k_period: self.k_period,
325                k_slow: self.k_slow,
326                d_period: self.d_period,
327            },
328        )]
329    }
330
331    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
332        ctx.indicator(self.key())
333    }
334
335    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
336        ctx.indicator_prev(self.key())
337    }
338}
339
340/// Stochastic RSI configuration — entry point for building K or D line refs.
341///
342/// Use [`.k()`](StochasticRsiConfig::k) to reference the smoothed %K line and
343/// [`.d()`](StochasticRsiConfig::d) for the %D signal line.  Both resolve
344/// against the same underlying `StochasticRsi` indicator computation, so only
345/// one indicator fetch is registered regardless of which lines you use.
346///
347/// # Example
348/// ```ignore
349/// let srsi = stochastic_rsi(14, 14, 3, 3);
350///
351/// // K crosses above D — a common bullish signal
352/// StrategyBuilder::new("StochRSI K/D Cross")
353///     .entry(srsi.k().crosses_above_ref(srsi.d()))
354///     .exit(srsi.k().crosses_below_ref(srsi.d()))
355///     .build()
356/// ```
357#[derive(Debug, Clone, Copy)]
358pub struct StochasticRsiConfig {
359    pub rsi_period: usize,
360    pub stoch_period: usize,
361    pub k_period: usize,
362    pub d_period: usize,
363}
364
365impl StochasticRsiConfig {
366    /// Reference to the smoothed %K line.
367    pub fn k(&self) -> StochasticRsiRef {
368        StochasticRsiRef::new(
369            self.rsi_period,
370            self.stoch_period,
371            self.k_period,
372            self.d_period,
373        )
374    }
375
376    /// Reference to the %D signal line (SMA of %K).
377    pub fn d(&self) -> StochasticRsiDRef {
378        StochasticRsiDRef::new(
379            self.rsi_period,
380            self.stoch_period,
381            self.k_period,
382            self.d_period,
383        )
384    }
385}
386
387/// Create a Stochastic RSI configuration.
388///
389/// Returns a [`StochasticRsiConfig`] from which you can obtain
390/// [`StochasticRsiConfig::k()`] or [`StochasticRsiConfig::d()`] refs.
391/// Calling `stochastic_rsi(...).k()` is equivalent to the previous API that
392/// returned `StochasticRsiRef` directly.
393#[inline]
394pub fn stochastic_rsi(
395    rsi_period: usize,
396    stoch_period: usize,
397    k_period: usize,
398    d_period: usize,
399) -> StochasticRsiConfig {
400    StochasticRsiConfig {
401        rsi_period,
402        stoch_period,
403        k_period,
404        d_period,
405    }
406}
407
408/// Stochastic RSI %K line reference.
409#[derive(Debug, Clone)]
410pub struct StochasticRsiRef {
411    pub rsi_period: usize,
412    pub stoch_period: usize,
413    pub k_period: usize,
414    pub d_period: usize,
415    key: String,
416}
417
418impl StochasticRsiRef {
419    fn new(rsi_period: usize, stoch_period: usize, k_period: usize, d_period: usize) -> Self {
420        Self {
421            rsi_period,
422            stoch_period,
423            k_period,
424            d_period,
425            key: format!("stoch_rsi_k_{rsi_period}_{stoch_period}_{k_period}_{d_period}"),
426        }
427    }
428}
429
430impl IndicatorRef for StochasticRsiRef {
431    fn key(&self) -> &str {
432        &self.key
433    }
434
435    fn required_indicators(&self) -> Vec<(String, Indicator)> {
436        vec![(
437            self.key.clone(),
438            Indicator::StochasticRsi {
439                rsi_period: self.rsi_period,
440                stoch_period: self.stoch_period,
441                k_period: self.k_period,
442                d_period: self.d_period,
443            },
444        )]
445    }
446
447    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
448        ctx.indicator(self.key())
449    }
450
451    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
452        ctx.indicator_prev(self.key())
453    }
454}
455
456/// Stochastic RSI %D line reference (SMA of %K).
457#[derive(Debug, Clone)]
458pub struct StochasticRsiDRef {
459    pub rsi_period: usize,
460    pub stoch_period: usize,
461    pub k_period: usize,
462    pub d_period: usize,
463    key: String,
464}
465
466impl StochasticRsiDRef {
467    fn new(rsi_period: usize, stoch_period: usize, k_period: usize, d_period: usize) -> Self {
468        Self {
469            rsi_period,
470            stoch_period,
471            k_period,
472            d_period,
473            key: format!("stoch_rsi_d_{rsi_period}_{stoch_period}_{k_period}_{d_period}"),
474        }
475    }
476}
477
478impl IndicatorRef for StochasticRsiDRef {
479    fn key(&self) -> &str {
480        &self.key
481    }
482
483    fn required_indicators(&self) -> Vec<(String, Indicator)> {
484        // Registering the K key is sufficient — the engine computes both K and D
485        // from the same StochasticRsi indicator pass and stores both keys.
486        let k_key = format!(
487            "stoch_rsi_k_{}_{}_{}_{}",
488            self.rsi_period, self.stoch_period, self.k_period, self.d_period
489        );
490        vec![(
491            k_key,
492            Indicator::StochasticRsi {
493                rsi_period: self.rsi_period,
494                stoch_period: self.stoch_period,
495                k_period: self.k_period,
496                d_period: self.d_period,
497            },
498        )]
499    }
500
501    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
502        ctx.indicator(self.key())
503    }
504
505    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
506        ctx.indicator_prev(self.key())
507    }
508}
509
510/// Awesome Oscillator reference (uses default 5/34 periods).
511#[derive(Debug, Clone)]
512pub struct AwesomeOscillatorRef {
513    pub fast: usize,
514    pub slow: usize,
515    key: String,
516}
517
518/// Create an Awesome Oscillator reference.
519#[inline]
520pub fn awesome_oscillator(fast: usize, slow: usize) -> AwesomeOscillatorRef {
521    AwesomeOscillatorRef {
522        fast,
523        slow,
524        key: format!("ao_{fast}_{slow}"),
525    }
526}
527
528impl IndicatorRef for AwesomeOscillatorRef {
529    fn key(&self) -> &str {
530        &self.key
531    }
532
533    fn required_indicators(&self) -> Vec<(String, Indicator)> {
534        vec![(
535            self.key.clone(),
536            Indicator::AwesomeOscillator {
537                fast: self.fast,
538                slow: self.slow,
539            },
540        )]
541    }
542
543    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
544        ctx.indicator(self.key())
545    }
546
547    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
548        ctx.indicator_prev(self.key())
549    }
550}
551
552/// Coppock Curve reference.
553#[derive(Debug, Clone)]
554pub struct CoppockCurveRef {
555    pub wma_period: usize,
556    pub long_roc: usize,
557    pub short_roc: usize,
558    key: String,
559}
560
561/// Create a Coppock Curve reference (uses default 10/14/11 periods).
562#[inline]
563pub fn coppock_curve(wma_period: usize, long_roc: usize, short_roc: usize) -> CoppockCurveRef {
564    CoppockCurveRef {
565        wma_period,
566        long_roc,
567        short_roc,
568        key: format!("coppock_{wma_period}_{long_roc}_{short_roc}"),
569    }
570}
571
572impl IndicatorRef for CoppockCurveRef {
573    fn key(&self) -> &str {
574        &self.key
575    }
576
577    fn required_indicators(&self) -> Vec<(String, Indicator)> {
578        vec![(
579            self.key.clone(),
580            Indicator::CoppockCurve {
581                wma_period: self.wma_period,
582                long_roc: self.long_roc,
583                short_roc: self.short_roc,
584            },
585        )]
586    }
587
588    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
589        ctx.indicator(self.key())
590    }
591
592    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
593        ctx.indicator_prev(self.key())
594    }
595}
596
597/// Money Flow Index reference.
598#[derive(Debug, Clone)]
599pub struct MfiRef {
600    pub period: usize,
601    key: String,
602}
603
604impl IndicatorRef for MfiRef {
605    fn key(&self) -> &str {
606        &self.key
607    }
608
609    fn required_indicators(&self) -> Vec<(String, Indicator)> {
610        vec![(self.key.clone(), Indicator::Mfi(self.period))]
611    }
612
613    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
614        ctx.indicator(self.key())
615    }
616
617    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
618        ctx.indicator_prev(self.key())
619    }
620}
621
622/// Create a Money Flow Index reference.
623#[inline]
624pub fn mfi(period: usize) -> MfiRef {
625    MfiRef {
626        period,
627        key: format!("mfi_{period}"),
628    }
629}
630
631/// Chaikin Oscillator reference.
632#[derive(Debug, Clone, Copy)]
633pub struct ChaikinOscillatorRef;
634
635/// Create a Chaikin Oscillator reference.
636#[inline]
637pub fn chaikin_oscillator() -> ChaikinOscillatorRef {
638    ChaikinOscillatorRef
639}
640
641impl IndicatorRef for ChaikinOscillatorRef {
642    fn key(&self) -> &str {
643        "chaikin_osc"
644    }
645
646    fn required_indicators(&self) -> Vec<(String, Indicator)> {
647        vec![("chaikin_osc".to_string(), Indicator::ChaikinOscillator)]
648    }
649
650    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
651        ctx.indicator(self.key())
652    }
653
654    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
655        ctx.indicator_prev(self.key())
656    }
657}
658
659/// Balance of Power reference.
660#[derive(Debug, Clone)]
661pub struct BalanceOfPowerRef {
662    pub period: Option<usize>,
663    key: String,
664}
665
666/// Create a Balance of Power reference.
667#[inline]
668pub fn balance_of_power(period: Option<usize>) -> BalanceOfPowerRef {
669    let key = match period {
670        Some(p) => format!("bop_{p}"),
671        None => "bop".to_string(),
672    };
673    BalanceOfPowerRef { period, key }
674}
675
676impl IndicatorRef for BalanceOfPowerRef {
677    fn key(&self) -> &str {
678        &self.key
679    }
680
681    fn required_indicators(&self) -> Vec<(String, Indicator)> {
682        vec![(self.key.clone(), Indicator::BalanceOfPower(self.period))]
683    }
684
685    fn value(&self, ctx: &StrategyContext) -> Option<f64> {
686        ctx.indicator(self.key())
687    }
688
689    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
690        ctx.indicator_prev(self.key())
691    }
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697    use crate::backtesting::refs::choppiness_index;
698
699    #[test]
700    fn test_oscillator_keys() {
701        assert_eq!(rsi(14).key(), "rsi_14");
702        assert_eq!(cci(20).key(), "cci_20");
703        assert_eq!(williams_r(14).key(), "williams_r_14");
704        assert_eq!(cmo(14).key(), "cmo_14");
705        // stochastic_rsi() returns StochasticRsiConfig; .k()/.d() give line refs
706        assert_eq!(
707            stochastic_rsi(14, 14, 3, 3).k().key(),
708            "stoch_rsi_k_14_14_3_3"
709        );
710        assert_eq!(
711            stochastic_rsi(14, 14, 3, 3).d().key(),
712            "stoch_rsi_d_14_14_3_3"
713        );
714        assert_eq!(awesome_oscillator(5, 34).key(), "ao_5_34");
715        assert_eq!(choppiness_index(14).key(), "chop_14");
716    }
717
718    #[test]
719    fn test_stochastic_keys() {
720        let stoch = stochastic(14, 3, 3);
721        assert_eq!(stoch.k().key(), "stochastic_k_14_3_3");
722        assert_eq!(stoch.d().key(), "stochastic_d_14_3_3");
723    }
724}