1use chrono::{DateTime, Utc};
10use quant_primitives::Candle;
11use rust_decimal::Decimal;
12
13use crate::error::IndicatorError;
14use crate::indicator::Indicator;
15use crate::series::Series;
16
17struct SeriesAlignment {
19 output_len: usize,
20 offset_a: usize,
21 offset_b: usize,
22}
23
24fn align_series_from_end(len_a: usize, len_b: usize) -> SeriesAlignment {
28 let output_len = len_a.min(len_b);
29 SeriesAlignment {
30 output_len,
31 offset_a: len_a - output_len,
32 offset_b: len_b - output_len,
33 }
34}
35
36fn apply_binary_op<F>(
38 values_a: &[(DateTime<Utc>, Decimal)],
39 values_b: &[(DateTime<Utc>, Decimal)],
40 op: F,
41) -> Vec<(DateTime<Utc>, Decimal)>
42where
43 F: Fn(Decimal, Decimal) -> Decimal,
44{
45 let align = align_series_from_end(values_a.len(), values_b.len());
46 let mut result = Vec::with_capacity(align.output_len);
47
48 for i in 0..align.output_len {
49 let (ts, val_a) = values_a[align.offset_a + i];
50 let val_b = values_b[align.offset_b + i].1;
51 result.push((ts, op(val_a, val_b)));
52 }
53
54 result
55}
56
57#[derive(Debug, Clone)]
59pub struct Diff<A, B> {
60 a: A,
61 b: B,
62 name: String,
63}
64
65impl<A: Indicator, B: Indicator> Diff<A, B> {
66 pub fn new(a: A, b: B) -> Self {
68 let name = format!("Diff({},{})", a.name(), b.name());
69 Self { a, b, name }
70 }
71}
72
73impl<A: Indicator, B: Indicator> Indicator for Diff<A, B> {
74 fn name(&self) -> &str {
75 &self.name
76 }
77
78 fn warmup_period(&self) -> usize {
79 self.a.warmup_period().max(self.b.warmup_period())
80 }
81
82 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
83 let series_a = self.a.compute(candles)?;
84 let series_b = self.b.compute(candles)?;
85
86 let values = apply_binary_op(series_a.values(), series_b.values(), |a, b| a - b);
87
88 Ok(Series::new(values))
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct Ratio<A, B> {
95 a: A,
96 b: B,
97 name: String,
98}
99
100impl<A: Indicator, B: Indicator> Ratio<A, B> {
101 pub fn new(a: A, b: B) -> Self {
103 let name = format!("Ratio({},{})", a.name(), b.name());
104 Self { a, b, name }
105 }
106}
107
108impl<A: Indicator, B: Indicator> Indicator for Ratio<A, B> {
109 fn name(&self) -> &str {
110 &self.name
111 }
112
113 fn warmup_period(&self) -> usize {
114 self.a.warmup_period().max(self.b.warmup_period())
115 }
116
117 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
118 let series_a = self.a.compute(candles)?;
119 let series_b = self.b.compute(candles)?;
120
121 let values = apply_binary_op(series_a.values(), series_b.values(), |a, b| {
122 if b.is_zero() {
124 Decimal::ZERO
125 } else {
126 a / b
127 }
128 });
129
130 Ok(Series::new(values))
131 }
132}
133
134#[derive(Debug, Clone)]
138pub struct Lag<I> {
139 inner: I,
140 periods: usize,
141 name: String,
142}
143
144impl<I: Indicator> Lag<I> {
145 pub fn new(inner: I, periods: usize) -> Result<Self, IndicatorError> {
152 if periods == 0 {
153 return Err(IndicatorError::InvalidParameter {
154 message: "Lag periods must be > 0".to_string(),
155 });
156 }
157 let name = format!("Lag({},{})", inner.name(), periods);
158 Ok(Self {
159 inner,
160 periods,
161 name,
162 })
163 }
164}
165
166impl<I: Indicator> Indicator for Lag<I> {
167 fn name(&self) -> &str {
168 &self.name
169 }
170
171 fn warmup_period(&self) -> usize {
172 self.inner.warmup_period() + self.periods
173 }
174
175 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
176 let inner_series = self.inner.compute(candles)?;
177 let values = inner_series.values();
178
179 if values.len() <= self.periods {
180 return Err(IndicatorError::InsufficientData {
181 required: self.inner.warmup_period() + self.periods,
182 actual: candles.len(),
183 });
184 }
185
186 let output_len = values.len() - self.periods;
188 let mut lagged_values = Vec::with_capacity(output_len);
189
190 for i in 0..output_len {
191 let value = values[i].1;
193 let ts = values[i + self.periods].0;
194 lagged_values.push((ts, value));
195 }
196
197 Ok(Series::new(lagged_values))
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct Scale<I> {
204 inner: I,
205 factor: Decimal,
206 name: String,
207}
208
209impl<I: Indicator> Scale<I> {
210 pub fn new(inner: I, factor: Decimal) -> Self {
212 let name = format!("Scale({},{})", inner.name(), factor);
213 Self {
214 inner,
215 factor,
216 name,
217 }
218 }
219}
220
221impl<I: Indicator> Indicator for Scale<I> {
222 fn name(&self) -> &str {
223 &self.name
224 }
225
226 fn warmup_period(&self) -> usize {
227 self.inner.warmup_period()
228 }
229
230 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
231 let inner_series = self.inner.compute(candles)?;
232 let values: Vec<_> = inner_series
233 .values()
234 .iter()
235 .map(|(ts, v)| (*ts, *v * self.factor))
236 .collect();
237 Ok(Series::new(values))
238 }
239}
240
241#[cfg(test)]
242#[path = "combinators_tests.rs"]
243mod tests;