Skip to main content

finance_query/backtesting/condition/
comparison.rs

1//! Comparison conditions for indicator references.
2//!
3//! This module provides conditions that compare indicator values
4//! against thresholds or other indicators.
5
6use crate::backtesting::refs::IndicatorRef;
7use crate::backtesting::strategy::StrategyContext;
8use crate::indicators::Indicator;
9
10use super::Condition;
11
12// ============================================================================
13// THRESHOLD COMPARISONS
14// ============================================================================
15
16/// Condition: indicator is above a threshold.
17#[derive(Clone)]
18pub struct Above<R: IndicatorRef> {
19    indicator: R,
20    threshold: f64,
21}
22
23impl<R: IndicatorRef> Above<R> {
24    /// Create a new Above condition.
25    pub fn new(indicator: R, threshold: f64) -> Self {
26        Self {
27            indicator,
28            threshold,
29        }
30    }
31}
32
33impl<R: IndicatorRef> Condition for Above<R> {
34    fn evaluate(&self, ctx: &StrategyContext) -> bool {
35        self.indicator
36            .value(ctx)
37            .map(|v| v > self.threshold)
38            .unwrap_or(false)
39    }
40
41    fn required_indicators(&self) -> Vec<(String, Indicator)> {
42        self.indicator.required_indicators()
43    }
44
45    fn description(&self) -> String {
46        format!("{} > {:.2}", self.indicator.key(), self.threshold)
47    }
48}
49
50/// Condition: indicator is below a threshold.
51#[derive(Clone)]
52pub struct Below<R: IndicatorRef> {
53    indicator: R,
54    threshold: f64,
55}
56
57impl<R: IndicatorRef> Below<R> {
58    /// Create a new Below condition.
59    pub fn new(indicator: R, threshold: f64) -> Self {
60        Self {
61            indicator,
62            threshold,
63        }
64    }
65}
66
67impl<R: IndicatorRef> Condition for Below<R> {
68    fn evaluate(&self, ctx: &StrategyContext) -> bool {
69        self.indicator
70            .value(ctx)
71            .map(|v| v < self.threshold)
72            .unwrap_or(false)
73    }
74
75    fn required_indicators(&self) -> Vec<(String, Indicator)> {
76        self.indicator.required_indicators()
77    }
78
79    fn description(&self) -> String {
80        format!("{} < {:.2}", self.indicator.key(), self.threshold)
81    }
82}
83
84/// Condition: indicator crosses above a threshold.
85///
86/// True when the previous value was at or below the threshold
87/// and the current value is above it.
88#[derive(Clone)]
89pub struct CrossesAbove<R: IndicatorRef> {
90    indicator: R,
91    threshold: f64,
92}
93
94impl<R: IndicatorRef> CrossesAbove<R> {
95    /// Create a new CrossesAbove condition.
96    pub fn new(indicator: R, threshold: f64) -> Self {
97        Self {
98            indicator,
99            threshold,
100        }
101    }
102}
103
104impl<R: IndicatorRef> Condition for CrossesAbove<R> {
105    fn evaluate(&self, ctx: &StrategyContext) -> bool {
106        let current = self.indicator.value(ctx);
107        let prev = self.indicator.prev_value(ctx);
108
109        match (current, prev) {
110            (Some(curr), Some(p)) => p <= self.threshold && curr > self.threshold,
111            _ => false,
112        }
113    }
114
115    fn required_indicators(&self) -> Vec<(String, Indicator)> {
116        self.indicator.required_indicators()
117    }
118
119    fn description(&self) -> String {
120        format!(
121            "{} crosses above {:.2}",
122            self.indicator.key(),
123            self.threshold
124        )
125    }
126}
127
128/// Condition: indicator crosses below a threshold.
129///
130/// True when the previous value was at or above the threshold
131/// and the current value is below it.
132#[derive(Clone)]
133pub struct CrossesBelow<R: IndicatorRef> {
134    indicator: R,
135    threshold: f64,
136}
137
138impl<R: IndicatorRef> CrossesBelow<R> {
139    /// Create a new CrossesBelow condition.
140    pub fn new(indicator: R, threshold: f64) -> Self {
141        Self {
142            indicator,
143            threshold,
144        }
145    }
146}
147
148impl<R: IndicatorRef> Condition for CrossesBelow<R> {
149    fn evaluate(&self, ctx: &StrategyContext) -> bool {
150        let current = self.indicator.value(ctx);
151        let prev = self.indicator.prev_value(ctx);
152
153        match (current, prev) {
154            (Some(curr), Some(p)) => p >= self.threshold && curr < self.threshold,
155            _ => false,
156        }
157    }
158
159    fn required_indicators(&self) -> Vec<(String, Indicator)> {
160        self.indicator.required_indicators()
161    }
162
163    fn description(&self) -> String {
164        format!(
165            "{} crosses below {:.2}",
166            self.indicator.key(),
167            self.threshold
168        )
169    }
170}
171
172/// Condition: indicator is between two thresholds.
173///
174/// True when `low < value < high`.
175#[derive(Clone)]
176pub struct Between<R: IndicatorRef> {
177    indicator: R,
178    low: f64,
179    high: f64,
180}
181
182impl<R: IndicatorRef> Between<R> {
183    /// Create a new Between condition.
184    pub fn new(indicator: R, low: f64, high: f64) -> Self {
185        Self {
186            indicator,
187            low,
188            high,
189        }
190    }
191}
192
193impl<R: IndicatorRef> Condition for Between<R> {
194    fn evaluate(&self, ctx: &StrategyContext) -> bool {
195        self.indicator
196            .value(ctx)
197            .map(|v| v > self.low && v < self.high)
198            .unwrap_or(false)
199    }
200
201    fn required_indicators(&self) -> Vec<(String, Indicator)> {
202        self.indicator.required_indicators()
203    }
204
205    fn description(&self) -> String {
206        format!(
207            "{:.2} < {} < {:.2}",
208            self.low,
209            self.indicator.key(),
210            self.high
211        )
212    }
213}
214
215/// Condition: indicator equals a value (within tolerance).
216#[derive(Clone)]
217pub struct Equals<R: IndicatorRef> {
218    indicator: R,
219    value: f64,
220    tolerance: f64,
221}
222
223impl<R: IndicatorRef> Equals<R> {
224    /// Create a new Equals condition.
225    pub fn new(indicator: R, value: f64, tolerance: f64) -> Self {
226        Self {
227            indicator,
228            value,
229            tolerance,
230        }
231    }
232}
233
234impl<R: IndicatorRef> Condition for Equals<R> {
235    fn evaluate(&self, ctx: &StrategyContext) -> bool {
236        self.indicator
237            .value(ctx)
238            .map(|v| (v - self.value).abs() <= self.tolerance)
239            .unwrap_or(false)
240    }
241
242    fn required_indicators(&self) -> Vec<(String, Indicator)> {
243        self.indicator.required_indicators()
244    }
245
246    fn description(&self) -> String {
247        format!(
248            "{} ≈ {:.2} (±{:.4})",
249            self.indicator.key(),
250            self.value,
251            self.tolerance
252        )
253    }
254}
255
256// ============================================================================
257// INDICATOR VS INDICATOR COMPARISONS
258// ============================================================================
259
260/// Condition: indicator is above another indicator.
261#[derive(Clone)]
262pub struct AboveRef<R1: IndicatorRef, R2: IndicatorRef> {
263    indicator: R1,
264    other: R2,
265}
266
267impl<R1: IndicatorRef, R2: IndicatorRef> AboveRef<R1, R2> {
268    /// Create a new AboveRef condition.
269    pub fn new(indicator: R1, other: R2) -> Self {
270        Self { indicator, other }
271    }
272}
273
274impl<R1: IndicatorRef, R2: IndicatorRef> Condition for AboveRef<R1, R2> {
275    fn evaluate(&self, ctx: &StrategyContext) -> bool {
276        let v1 = self.indicator.value(ctx);
277        let v2 = self.other.value(ctx);
278
279        match (v1, v2) {
280            (Some(a), Some(b)) => a > b,
281            _ => false,
282        }
283    }
284
285    fn required_indicators(&self) -> Vec<(String, Indicator)> {
286        let mut indicators = self.indicator.required_indicators();
287        indicators.extend(self.other.required_indicators());
288        indicators
289    }
290
291    fn description(&self) -> String {
292        format!("{} > {}", self.indicator.key(), self.other.key())
293    }
294}
295
296/// Condition: indicator is below another indicator.
297#[derive(Clone)]
298pub struct BelowRef<R1: IndicatorRef, R2: IndicatorRef> {
299    indicator: R1,
300    other: R2,
301}
302
303impl<R1: IndicatorRef, R2: IndicatorRef> BelowRef<R1, R2> {
304    /// Create a new BelowRef condition.
305    pub fn new(indicator: R1, other: R2) -> Self {
306        Self { indicator, other }
307    }
308}
309
310impl<R1: IndicatorRef, R2: IndicatorRef> Condition for BelowRef<R1, R2> {
311    fn evaluate(&self, ctx: &StrategyContext) -> bool {
312        let v1 = self.indicator.value(ctx);
313        let v2 = self.other.value(ctx);
314
315        match (v1, v2) {
316            (Some(a), Some(b)) => a < b,
317            _ => false,
318        }
319    }
320
321    fn required_indicators(&self) -> Vec<(String, Indicator)> {
322        let mut indicators = self.indicator.required_indicators();
323        indicators.extend(self.other.required_indicators());
324        indicators
325    }
326
327    fn description(&self) -> String {
328        format!("{} < {}", self.indicator.key(), self.other.key())
329    }
330}
331
332/// Condition: indicator crosses above another indicator.
333///
334/// True when the fast indicator was at or below the slow indicator
335/// and is now above it.
336#[derive(Clone)]
337pub struct CrossesAboveRef<R1: IndicatorRef, R2: IndicatorRef> {
338    fast: R1,
339    slow: R2,
340}
341
342impl<R1: IndicatorRef, R2: IndicatorRef> CrossesAboveRef<R1, R2> {
343    /// Create a new CrossesAboveRef condition.
344    pub fn new(fast: R1, slow: R2) -> Self {
345        Self { fast, slow }
346    }
347}
348
349impl<R1: IndicatorRef, R2: IndicatorRef> Condition for CrossesAboveRef<R1, R2> {
350    fn evaluate(&self, ctx: &StrategyContext) -> bool {
351        let fast_now = self.fast.value(ctx);
352        let slow_now = self.slow.value(ctx);
353        let fast_prev = self.fast.prev_value(ctx);
354        let slow_prev = self.slow.prev_value(ctx);
355
356        match (fast_now, slow_now, fast_prev, slow_prev) {
357            (Some(fn_), Some(sn), Some(fp), Some(sp)) => fp <= sp && fn_ > sn,
358            _ => false,
359        }
360    }
361
362    fn required_indicators(&self) -> Vec<(String, Indicator)> {
363        let mut indicators = self.fast.required_indicators();
364        indicators.extend(self.slow.required_indicators());
365        indicators
366    }
367
368    fn description(&self) -> String {
369        format!("{} crosses above {}", self.fast.key(), self.slow.key())
370    }
371}
372
373/// Condition: indicator crosses below another indicator.
374///
375/// True when the fast indicator was at or above the slow indicator
376/// and is now below it.
377#[derive(Clone)]
378pub struct CrossesBelowRef<R1: IndicatorRef, R2: IndicatorRef> {
379    fast: R1,
380    slow: R2,
381}
382
383impl<R1: IndicatorRef, R2: IndicatorRef> CrossesBelowRef<R1, R2> {
384    /// Create a new CrossesBelowRef condition.
385    pub fn new(fast: R1, slow: R2) -> Self {
386        Self { fast, slow }
387    }
388}
389
390impl<R1: IndicatorRef, R2: IndicatorRef> Condition for CrossesBelowRef<R1, R2> {
391    fn evaluate(&self, ctx: &StrategyContext) -> bool {
392        let fast_now = self.fast.value(ctx);
393        let slow_now = self.slow.value(ctx);
394        let fast_prev = self.fast.prev_value(ctx);
395        let slow_prev = self.slow.prev_value(ctx);
396
397        match (fast_now, slow_now, fast_prev, slow_prev) {
398            (Some(fn_), Some(sn), Some(fp), Some(sp)) => fp >= sp && fn_ < sn,
399            _ => false,
400        }
401    }
402
403    fn required_indicators(&self) -> Vec<(String, Indicator)> {
404        let mut indicators = self.fast.required_indicators();
405        indicators.extend(self.slow.required_indicators());
406        indicators
407    }
408
409    fn description(&self) -> String {
410        format!("{} crosses below {}", self.fast.key(), self.slow.key())
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    use crate::backtesting::refs::{rsi, sma};
418
419    #[test]
420    fn test_above_description() {
421        let cond = Above::new(rsi(14), 70.0);
422        assert_eq!(cond.description(), "rsi_14 > 70.00");
423    }
424
425    #[test]
426    fn test_below_description() {
427        let cond = Below::new(rsi(14), 30.0);
428        assert_eq!(cond.description(), "rsi_14 < 30.00");
429    }
430
431    #[test]
432    fn test_crosses_above_description() {
433        let cond = CrossesAbove::new(rsi(14), 30.0);
434        assert_eq!(cond.description(), "rsi_14 crosses above 30.00");
435    }
436
437    #[test]
438    fn test_crosses_below_description() {
439        let cond = CrossesBelow::new(rsi(14), 70.0);
440        assert_eq!(cond.description(), "rsi_14 crosses below 70.00");
441    }
442
443    #[test]
444    fn test_between_description() {
445        let cond = Between::new(rsi(14), 30.0, 70.0);
446        assert_eq!(cond.description(), "30.00 < rsi_14 < 70.00");
447    }
448
449    #[test]
450    fn test_above_ref_description() {
451        let cond = AboveRef::new(sma(10), sma(20));
452        assert_eq!(cond.description(), "sma_10 > sma_20");
453    }
454
455    #[test]
456    fn test_crosses_above_ref_description() {
457        let cond = CrossesAboveRef::new(sma(50), sma(200));
458        assert_eq!(cond.description(), "sma_50 crosses above sma_200");
459    }
460
461    #[test]
462    fn test_required_indicators() {
463        let cond = Above::new(rsi(14), 70.0);
464        let indicators = cond.required_indicators();
465        assert_eq!(indicators.len(), 1);
466        assert_eq!(indicators[0].0, "rsi_14");
467    }
468
469    #[test]
470    fn test_cross_ref_required_indicators() {
471        let cond = CrossesAboveRef::new(sma(10), sma(20));
472        let indicators = cond.required_indicators();
473        assert_eq!(indicators.len(), 2);
474    }
475}