Skip to main content

finance_query/backtesting/condition/
mod.rs

1//! Condition system for building strategy entry/exit rules.
2//!
3//! This module provides a composable way to define trading conditions
4//! using indicator references and comparison operations.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use finance_query::backtesting::refs::*;
10//! use finance_query::backtesting::condition::*;
11//!
12//! // Simple condition
13//! let oversold = rsi(14).below(30.0);
14//!
15//! // Compound conditions
16//! let entry = rsi(14).crosses_below(30.0)
17//!     .and(price().above_ref(sma(200)));
18//!
19//! let exit = rsi(14).crosses_above(70.0)
20//!     .or(stop_loss(0.05));
21//! ```
22
23mod comparison;
24mod composite;
25mod threshold;
26
27pub use comparison::*;
28pub use composite::*;
29pub use threshold::*;
30
31use crate::indicators::Indicator;
32
33use super::strategy::StrategyContext;
34
35/// A condition that can be evaluated on each candle.
36///
37/// Conditions are the building blocks of trading strategies.
38/// They can be combined using `and()`, `or()`, and `not()` operations.
39///
40/// # Example
41///
42/// ```ignore
43/// use finance_query::backtesting::condition::Condition;
44///
45/// fn my_custom_condition(ctx: &StrategyContext) -> bool {
46///     // Custom logic here
47///     true
48/// }
49/// ```
50pub trait Condition: Clone + Send + Sync + 'static {
51    /// Evaluate the condition with the current strategy context.
52    ///
53    /// Returns `true` if the condition is met, `false` otherwise.
54    fn evaluate(&self, ctx: &StrategyContext) -> bool;
55
56    /// Get the indicators required by this condition.
57    ///
58    /// The backtest engine will pre-compute these indicators
59    /// before running the strategy.
60    fn required_indicators(&self) -> Vec<(String, Indicator)>;
61
62    /// Get a human-readable description of this condition.
63    ///
64    /// This is used for logging, debugging, and signal reporting.
65    fn description(&self) -> String;
66
67    /// Combine this condition with another using AND logic.
68    ///
69    /// The resulting condition is true only when both conditions are true.
70    ///
71    /// # Example
72    ///
73    /// ```ignore
74    /// let entry = rsi(14).below(30.0).and(price().above_ref(sma(200)));
75    /// ```
76    fn and<C: Condition>(self, other: C) -> And<Self, C>
77    where
78        Self: Sized,
79    {
80        And::new(self, other)
81    }
82
83    /// Combine this condition with another using OR logic.
84    ///
85    /// The resulting condition is true when either condition is true.
86    ///
87    /// # Example
88    ///
89    /// ```ignore
90    /// let exit = rsi(14).above(70.0).or(stop_loss(0.05));
91    /// ```
92    fn or<C: Condition>(self, other: C) -> Or<Self, C>
93    where
94        Self: Sized,
95    {
96        Or::new(self, other)
97    }
98
99    /// Negate this condition.
100    ///
101    /// The resulting condition is true when this condition is false.
102    ///
103    /// # Example
104    ///
105    /// ```ignore
106    /// let not_overbought = rsi(14).above(70.0).not();
107    /// ```
108    fn not(self) -> Not<Self>
109    where
110        Self: Sized,
111    {
112        Not::new(self)
113    }
114}
115
116/// A condition that always returns the same value.
117///
118/// Useful for testing or as a placeholder.
119#[derive(Debug, Clone, Copy)]
120pub struct ConstantCondition(bool);
121
122impl ConstantCondition {
123    /// Create a condition that always returns true.
124    pub fn always_true() -> Self {
125        Self(true)
126    }
127
128    /// Create a condition that always returns false.
129    pub fn always_false() -> Self {
130        Self(false)
131    }
132}
133
134impl Condition for ConstantCondition {
135    fn evaluate(&self, _ctx: &StrategyContext) -> bool {
136        self.0
137    }
138
139    fn required_indicators(&self) -> Vec<(String, Indicator)> {
140        vec![]
141    }
142
143    fn description(&self) -> String {
144        if self.0 {
145            "always true".to_string()
146        } else {
147            "always false".to_string()
148        }
149    }
150}
151
152/// Convenience function to create a condition that always returns true.
153#[inline]
154pub fn always_true() -> ConstantCondition {
155    ConstantCondition::always_true()
156}
157
158/// Convenience function to create a condition that always returns false.
159#[inline]
160pub fn always_false() -> ConstantCondition {
161    ConstantCondition::always_false()
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_constant_conditions() {
170        assert_eq!(always_true().description(), "always true");
171        assert_eq!(always_false().description(), "always false");
172    }
173}