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}