Skip to main content

finance_query/backtesting/refs/
mod.rs

1//! Indicator reference system for building strategy conditions.
2//!
3//! This module provides a type-safe way to reference indicator values
4//! that can be used to build trading conditions.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use finance_query::backtesting::refs::*;
10//!
11//! // Reference RSI indicator
12//! let rsi_ref = rsi(14);
13//!
14//! // Build conditions
15//! let oversold = rsi_ref.below(30.0);
16//! let overbought = rsi_ref.above(70.0);
17//!
18//! // Compose conditions
19//! let entry = rsi(14).crosses_below(30.0).and(price().above_ref(sma(200)));
20//! ```
21
22mod computed;
23mod htf;
24mod price;
25
26pub use computed::*;
27pub use htf::*;
28pub use price::*;
29
30use crate::indicators::Indicator;
31
32use super::strategy::StrategyContext;
33
34/// A reference to a value that can be compared in conditions.
35///
36/// This is the building block for creating conditions. Each indicator
37/// reference knows:
38/// - Its unique key for storing computed values
39/// - What indicators it requires
40/// - How to retrieve its value from the strategy context
41///
42/// # Implementing Custom References
43///
44/// ```ignore
45/// use finance_query::backtesting::refs::IndicatorRef;
46///
47/// #[derive(Clone)]
48/// struct MyCustomRef {
49///     period: usize,
50/// }
51///
52/// impl IndicatorRef for MyCustomRef {
53///     fn key(&self) -> String {
54///         format!("my_custom_{}", self.period)
55///     }
56///
57///     fn required_indicators(&self) -> Vec<(String, Indicator)> {
58///         vec![(self.key(), Indicator::Sma(self.period))]
59///     }
60///
61///     fn value(&self, ctx: &StrategyContext) -> Option<f64> {
62///         ctx.indicator(&self.key())
63///     }
64///
65///     fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
66///         ctx.indicator_prev(&self.key())
67///     }
68/// }
69/// ```
70pub trait IndicatorRef: Clone + Send + Sync + 'static {
71    /// Unique key for storing computed values in the context.
72    ///
73    /// This key is used to look up pre-computed indicator values
74    /// in the `StrategyContext::indicators` map.
75    fn key(&self) -> String;
76
77    /// Required indicators to compute this reference.
78    ///
79    /// Returns a list of (key, Indicator) pairs that must be
80    /// pre-computed by the backtest engine before the strategy runs.
81    fn required_indicators(&self) -> Vec<(String, Indicator)>;
82
83    /// Get the value at current candle index from context.
84    fn value(&self, ctx: &StrategyContext) -> Option<f64>;
85
86    /// Get the value at the previous candle index.
87    fn prev_value(&self, ctx: &StrategyContext) -> Option<f64>;
88}
89
90/// Extension trait that adds condition-building methods to all indicator references.
91///
92/// This trait provides a fluent API for building conditions from indicator values.
93/// It is automatically implemented for all types that implement `IndicatorRef`.
94///
95/// # Example
96///
97/// ```ignore
98/// use finance_query::backtesting::refs::*;
99///
100/// // All these methods are available on any IndicatorRef
101/// let cond1 = rsi(14).above(70.0);
102/// let cond2 = rsi(14).below(30.0);
103/// let cond3 = rsi(14).crosses_above(30.0);
104/// let cond4 = rsi(14).crosses_below(70.0);
105/// let cond5 = rsi(14).between(30.0, 70.0);
106/// let cond6 = sma(10).above_ref(sma(20));
107/// let cond7 = sma(10).crosses_above_ref(sma(20));
108/// ```
109pub trait IndicatorRefExt: IndicatorRef + Sized {
110    /// Create a condition that checks if this indicator is above a threshold.
111    ///
112    /// # Example
113    ///
114    /// ```ignore
115    /// let overbought = rsi(14).above(70.0);
116    /// ```
117    fn above(self, threshold: f64) -> super::condition::Above<Self> {
118        super::condition::Above::new(self, threshold)
119    }
120
121    /// Create a condition that checks if this indicator is above another indicator.
122    ///
123    /// # Example
124    ///
125    /// ```ignore
126    /// let uptrend = price().above_ref(sma(200));
127    /// ```
128    fn above_ref<R: IndicatorRef>(self, other: R) -> super::condition::AboveRef<Self, R> {
129        super::condition::AboveRef::new(self, other)
130    }
131
132    /// Create a condition that checks if this indicator is below a threshold.
133    ///
134    /// # Example
135    ///
136    /// ```ignore
137    /// let oversold = rsi(14).below(30.0);
138    /// ```
139    fn below(self, threshold: f64) -> super::condition::Below<Self> {
140        super::condition::Below::new(self, threshold)
141    }
142
143    /// Create a condition that checks if this indicator is below another indicator.
144    ///
145    /// # Example
146    ///
147    /// ```ignore
148    /// let downtrend = price().below_ref(sma(200));
149    /// ```
150    fn below_ref<R: IndicatorRef>(self, other: R) -> super::condition::BelowRef<Self, R> {
151        super::condition::BelowRef::new(self, other)
152    }
153
154    /// Create a condition that checks if this indicator crosses above a threshold.
155    ///
156    /// A crossover occurs when the previous value was at or below the threshold
157    /// and the current value is above it.
158    ///
159    /// # Example
160    ///
161    /// ```ignore
162    /// let rsi_exit_oversold = rsi(14).crosses_above(30.0);
163    /// ```
164    fn crosses_above(self, threshold: f64) -> super::condition::CrossesAbove<Self> {
165        super::condition::CrossesAbove::new(self, threshold)
166    }
167
168    /// Create a condition that checks if this indicator crosses above another indicator.
169    ///
170    /// # Example
171    ///
172    /// ```ignore
173    /// let golden_cross = sma(50).crosses_above_ref(sma(200));
174    /// ```
175    fn crosses_above_ref<R: IndicatorRef>(
176        self,
177        other: R,
178    ) -> super::condition::CrossesAboveRef<Self, R> {
179        super::condition::CrossesAboveRef::new(self, other)
180    }
181
182    /// Create a condition that checks if this indicator crosses below a threshold.
183    ///
184    /// A crossover occurs when the previous value was at or above the threshold
185    /// and the current value is below it.
186    ///
187    /// # Example
188    ///
189    /// ```ignore
190    /// let rsi_enter_overbought = rsi(14).crosses_below(70.0);
191    /// ```
192    fn crosses_below(self, threshold: f64) -> super::condition::CrossesBelow<Self> {
193        super::condition::CrossesBelow::new(self, threshold)
194    }
195
196    /// Create a condition that checks if this indicator crosses below another indicator.
197    ///
198    /// # Example
199    ///
200    /// ```ignore
201    /// let death_cross = sma(50).crosses_below_ref(sma(200));
202    /// ```
203    fn crosses_below_ref<R: IndicatorRef>(
204        self,
205        other: R,
206    ) -> super::condition::CrossesBelowRef<Self, R> {
207        super::condition::CrossesBelowRef::new(self, other)
208    }
209
210    /// Create a condition that checks if this indicator is between two thresholds.
211    ///
212    /// Returns true when `low < value < high`.
213    ///
214    /// # Example
215    ///
216    /// ```ignore
217    /// let neutral_rsi = rsi(14).between(40.0, 60.0);
218    /// ```
219    fn between(self, low: f64, high: f64) -> super::condition::Between<Self> {
220        super::condition::Between::new(self, low, high)
221    }
222
223    /// Create a condition that checks if this indicator equals a value (within tolerance).
224    ///
225    /// # Example
226    ///
227    /// ```ignore
228    /// let at_zero = macd(12, 26, 9).histogram().equals(0.0, 0.001);
229    /// ```
230    fn equals(self, value: f64, tolerance: f64) -> super::condition::Equals<Self> {
231        super::condition::Equals::new(self, value, tolerance)
232    }
233}
234
235// Auto-implement IndicatorRefExt for all types that implement IndicatorRef
236impl<T: IndicatorRef + Sized> IndicatorRefExt for T {}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_indicator_ref_ext_methods_exist() {
244        // This test just verifies the trait methods compile
245        let _sma = sma(20);
246        let _ema = ema(12);
247        let _rsi = rsi(14);
248
249        // Verify key() works
250        assert_eq!(_sma.key(), "sma_20");
251        assert_eq!(_ema.key(), "ema_12");
252        assert_eq!(_rsi.key(), "rsi_14");
253    }
254}