Skip to main content

finance_query/backtesting/condition/
composite.rs

1//! Composite conditions for combining multiple conditions.
2//!
3//! This module provides AND, OR, and NOT operations for combining conditions.
4
5use crate::backtesting::strategy::StrategyContext;
6use crate::indicators::Indicator;
7
8use super::{Condition, HtfIndicatorSpec};
9
10/// Condition: both conditions must be true (AND logic).
11#[derive(Clone)]
12pub struct And<C1: Condition, C2: Condition> {
13    left: C1,
14    right: C2,
15}
16
17impl<C1: Condition, C2: Condition> And<C1, C2> {
18    /// Create a new And condition.
19    pub fn new(left: C1, right: C2) -> Self {
20        Self { left, right }
21    }
22}
23
24impl<C1: Condition, C2: Condition> Condition for And<C1, C2> {
25    fn evaluate(&self, ctx: &StrategyContext) -> bool {
26        self.left.evaluate(ctx) && self.right.evaluate(ctx)
27    }
28
29    fn required_indicators(&self) -> Vec<(String, Indicator)> {
30        let mut indicators = self.left.required_indicators();
31        indicators.extend(self.right.required_indicators());
32        // Deduplicate by key
33        indicators.sort_by(|a, b| a.0.cmp(&b.0));
34        indicators.dedup_by(|a, b| a.0 == b.0);
35        indicators
36    }
37
38    fn htf_requirements(&self) -> Vec<HtfIndicatorSpec> {
39        let mut reqs = self.left.htf_requirements();
40        reqs.extend(self.right.htf_requirements());
41        reqs.sort_by(|a, b| a.htf_key.cmp(&b.htf_key));
42        reqs.dedup_by(|a, b| a.htf_key == b.htf_key);
43        reqs
44    }
45
46    fn description(&self) -> String {
47        format!(
48            "({} AND {})",
49            self.left.description(),
50            self.right.description()
51        )
52    }
53}
54
55/// Condition: at least one condition must be true (OR logic).
56#[derive(Clone)]
57pub struct Or<C1: Condition, C2: Condition> {
58    left: C1,
59    right: C2,
60}
61
62impl<C1: Condition, C2: Condition> Or<C1, C2> {
63    /// Create a new Or condition.
64    pub fn new(left: C1, right: C2) -> Self {
65        Self { left, right }
66    }
67}
68
69impl<C1: Condition, C2: Condition> Condition for Or<C1, C2> {
70    fn evaluate(&self, ctx: &StrategyContext) -> bool {
71        self.left.evaluate(ctx) || self.right.evaluate(ctx)
72    }
73
74    fn required_indicators(&self) -> Vec<(String, Indicator)> {
75        let mut indicators = self.left.required_indicators();
76        indicators.extend(self.right.required_indicators());
77        // Deduplicate by key
78        indicators.sort_by(|a, b| a.0.cmp(&b.0));
79        indicators.dedup_by(|a, b| a.0 == b.0);
80        indicators
81    }
82
83    fn htf_requirements(&self) -> Vec<HtfIndicatorSpec> {
84        let mut reqs = self.left.htf_requirements();
85        reqs.extend(self.right.htf_requirements());
86        reqs.sort_by(|a, b| a.htf_key.cmp(&b.htf_key));
87        reqs.dedup_by(|a, b| a.htf_key == b.htf_key);
88        reqs
89    }
90
91    fn description(&self) -> String {
92        format!(
93            "({} OR {})",
94            self.left.description(),
95            self.right.description()
96        )
97    }
98}
99
100/// Condition: negation of a condition (NOT logic).
101#[derive(Clone)]
102pub struct Not<C: Condition> {
103    inner: C,
104}
105
106impl<C: Condition> Not<C> {
107    /// Create a new Not condition.
108    pub fn new(inner: C) -> Self {
109        Self { inner }
110    }
111}
112
113impl<C: Condition> Condition for Not<C> {
114    fn evaluate(&self, ctx: &StrategyContext) -> bool {
115        !self.inner.evaluate(ctx)
116    }
117
118    fn required_indicators(&self) -> Vec<(String, Indicator)> {
119        self.inner.required_indicators()
120    }
121
122    fn htf_requirements(&self) -> Vec<HtfIndicatorSpec> {
123        self.inner.htf_requirements()
124    }
125
126    fn description(&self) -> String {
127        format!("NOT ({})", self.inner.description())
128    }
129}
130
131/// Builder for creating complex multi-condition combinations.
132///
133/// # Example
134///
135/// ```ignore
136/// use finance_query::backtesting::condition::*;
137/// use finance_query::backtesting::refs::*;
138///
139/// let conditions = ConditionBuilder::new()
140///     .with_condition(rsi(14).below(30.0))
141///     .with_condition(price().above_ref(sma(200)))
142///     .with_condition(adx(14).above(25.0))
143///     .all();  // All conditions must be true
144///
145/// // Or use any() for OR logic
146/// let exit = ConditionBuilder::new()
147///     .with_condition(rsi(14).above(70.0))
148///     .with_condition(stop_loss(0.05))
149///     .any();  // Any condition can be true
150/// ```
151#[derive(Clone)]
152pub struct ConditionBuilder<C: Condition> {
153    conditions: Vec<C>,
154}
155
156impl<C: Condition> Default for ConditionBuilder<C> {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162impl<C: Condition> ConditionBuilder<C> {
163    /// Create a new condition builder.
164    pub fn new() -> Self {
165        Self {
166            conditions: Vec::new(),
167        }
168    }
169
170    /// Add a condition to the builder.
171    pub fn with_condition(mut self, condition: C) -> Self {
172        self.conditions.push(condition);
173        self
174    }
175}
176
177/// A condition that evaluates to true when ALL inner conditions are true.
178#[derive(Clone)]
179pub struct All<C: Condition> {
180    conditions: Vec<C>,
181}
182
183impl<C: Condition> Condition for All<C> {
184    fn evaluate(&self, ctx: &StrategyContext) -> bool {
185        self.conditions.iter().all(|c| c.evaluate(ctx))
186    }
187
188    fn required_indicators(&self) -> Vec<(String, Indicator)> {
189        let mut indicators = Vec::new();
190        for c in &self.conditions {
191            indicators.extend(c.required_indicators());
192        }
193        // Deduplicate by key
194        indicators.sort_by(|a, b| a.0.cmp(&b.0));
195        indicators.dedup_by(|a, b| a.0 == b.0);
196        indicators
197    }
198
199    fn description(&self) -> String {
200        let descs: Vec<_> = self.conditions.iter().map(|c| c.description()).collect();
201        format!("ALL({})", descs.join(" AND "))
202    }
203}
204
205/// A condition that evaluates to true when ANY inner condition is true.
206#[derive(Clone)]
207pub struct Any<C: Condition> {
208    conditions: Vec<C>,
209}
210
211impl<C: Condition> Condition for Any<C> {
212    fn evaluate(&self, ctx: &StrategyContext) -> bool {
213        self.conditions.iter().any(|c| c.evaluate(ctx))
214    }
215
216    fn required_indicators(&self) -> Vec<(String, Indicator)> {
217        let mut indicators = Vec::new();
218        for c in &self.conditions {
219            indicators.extend(c.required_indicators());
220        }
221        // Deduplicate by key
222        indicators.sort_by(|a, b| a.0.cmp(&b.0));
223        indicators.dedup_by(|a, b| a.0 == b.0);
224        indicators
225    }
226
227    fn description(&self) -> String {
228        let descs: Vec<_> = self.conditions.iter().map(|c| c.description()).collect();
229        format!("ANY({})", descs.join(" OR "))
230    }
231}
232
233impl<C: Condition> ConditionBuilder<C> {
234    /// Build a condition that requires ALL conditions to be true.
235    pub fn all(self) -> All<C> {
236        All {
237            conditions: self.conditions,
238        }
239    }
240
241    /// Build a condition that requires ANY condition to be true.
242    pub fn any(self) -> Any<C> {
243        Any {
244            conditions: self.conditions,
245        }
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use crate::backtesting::condition::{always_false, always_true};
253
254    #[test]
255    fn test_and_description() {
256        let cond = And::new(always_true(), always_false());
257        assert_eq!(cond.description(), "(always true AND always false)");
258    }
259
260    #[test]
261    fn test_or_description() {
262        let cond = Or::new(always_true(), always_false());
263        assert_eq!(cond.description(), "(always true OR always false)");
264    }
265
266    #[test]
267    fn test_not_description() {
268        let cond = Not::new(always_true());
269        assert_eq!(cond.description(), "NOT (always true)");
270    }
271
272    #[test]
273    fn test_all_description() {
274        let all = ConditionBuilder::new()
275            .with_condition(always_true())
276            .with_condition(always_false())
277            .all();
278        assert_eq!(all.description(), "ALL(always true AND always false)");
279    }
280
281    #[test]
282    fn test_any_description() {
283        let any = ConditionBuilder::new()
284            .with_condition(always_true())
285            .with_condition(always_false())
286            .any();
287        assert_eq!(any.description(), "ANY(always true OR always false)");
288    }
289}