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