1use crate::backtesting::strategy::StrategyContext;
2use crate::indicators::Indicator;
3
4use super::IndicatorRef;
5
6#[derive(Debug, Clone, Copy)]
8pub struct IchimokuConfig {
9 pub conversion: usize,
10 pub base: usize,
11 pub lagging: usize,
12 pub displacement: usize,
13}
14
15impl IchimokuConfig {
16 pub fn conversion_line(&self) -> IchimokuConversionRef {
18 IchimokuConversionRef::new(self.conversion, self.base, self.lagging, self.displacement)
19 }
20
21 pub fn base_line(&self) -> IchimokuBaseRef {
23 IchimokuBaseRef::new(self.conversion, self.base, self.lagging, self.displacement)
24 }
25
26 pub fn leading_span_a(&self) -> IchimokuLeadingARef {
28 IchimokuLeadingARef::new(self.conversion, self.base, self.lagging, self.displacement)
29 }
30
31 pub fn leading_span_b(&self) -> IchimokuLeadingBRef {
33 IchimokuLeadingBRef::new(self.conversion, self.base, self.lagging, self.displacement)
34 }
35
36 pub fn lagging_span(&self) -> IchimokuLaggingRef {
38 IchimokuLaggingRef::new(self.conversion, self.base, self.lagging, self.displacement)
39 }
40}
41
42#[inline]
46pub fn ichimoku() -> IchimokuConfig {
47 IchimokuConfig {
48 conversion: 9,
49 base: 26,
50 lagging: 26,
51 displacement: 26,
52 }
53}
54
55#[inline]
61pub fn ichimoku_custom(
62 conversion: usize,
63 base: usize,
64 lagging: usize,
65 displacement: usize,
66) -> IchimokuConfig {
67 IchimokuConfig {
68 conversion,
69 base,
70 lagging,
71 displacement,
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct IchimokuConversionRef {
78 pub conversion: usize,
79 pub base: usize,
80 pub lagging: usize,
81 pub displacement: usize,
82 key: String,
83}
84
85impl IchimokuConversionRef {
86 fn new(conversion: usize, base: usize, lagging: usize, displacement: usize) -> Self {
87 Self {
88 conversion,
89 base,
90 lagging,
91 displacement,
92 key: format!("ichimoku_conversion_{conversion}_{base}_{lagging}_{displacement}"),
93 }
94 }
95}
96
97impl IndicatorRef for IchimokuConversionRef {
98 fn key(&self) -> &str {
99 &self.key
100 }
101
102 fn required_indicators(&self) -> Vec<(String, Indicator)> {
103 vec![(
104 self.key.clone(),
105 Indicator::Ichimoku {
106 conversion: self.conversion,
107 base: self.base,
108 lagging: self.lagging,
109 displacement: self.displacement,
110 },
111 )]
112 }
113
114 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
115 ctx.indicator(self.key())
116 }
117
118 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
119 ctx.indicator_prev(self.key())
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct IchimokuBaseRef {
126 pub conversion: usize,
127 pub base: usize,
128 pub lagging: usize,
129 pub displacement: usize,
130 key: String,
131}
132
133impl IchimokuBaseRef {
134 fn new(conversion: usize, base: usize, lagging: usize, displacement: usize) -> Self {
135 Self {
136 conversion,
137 base,
138 lagging,
139 displacement,
140 key: format!("ichimoku_base_{conversion}_{base}_{lagging}_{displacement}"),
141 }
142 }
143}
144
145impl IndicatorRef for IchimokuBaseRef {
146 fn key(&self) -> &str {
147 &self.key
148 }
149
150 fn required_indicators(&self) -> Vec<(String, Indicator)> {
151 vec![(
152 self.key.clone(),
153 Indicator::Ichimoku {
154 conversion: self.conversion,
155 base: self.base,
156 lagging: self.lagging,
157 displacement: self.displacement,
158 },
159 )]
160 }
161
162 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
163 ctx.indicator(self.key())
164 }
165
166 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
167 ctx.indicator_prev(self.key())
168 }
169}
170
171#[derive(Debug, Clone)]
173pub struct IchimokuLeadingARef {
174 pub conversion: usize,
175 pub base: usize,
176 pub lagging: usize,
177 pub displacement: usize,
178 key: String,
179}
180
181impl IchimokuLeadingARef {
182 fn new(conversion: usize, base: usize, lagging: usize, displacement: usize) -> Self {
183 Self {
184 conversion,
185 base,
186 lagging,
187 displacement,
188 key: format!("ichimoku_leading_a_{conversion}_{base}_{lagging}_{displacement}"),
189 }
190 }
191}
192
193impl IndicatorRef for IchimokuLeadingARef {
194 fn key(&self) -> &str {
195 &self.key
196 }
197
198 fn required_indicators(&self) -> Vec<(String, Indicator)> {
199 vec![(
200 self.key.clone(),
201 Indicator::Ichimoku {
202 conversion: self.conversion,
203 base: self.base,
204 lagging: self.lagging,
205 displacement: self.displacement,
206 },
207 )]
208 }
209
210 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
211 ctx.indicator(self.key())
212 }
213
214 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
215 ctx.indicator_prev(self.key())
216 }
217}
218
219#[derive(Debug, Clone)]
221pub struct IchimokuLeadingBRef {
222 pub conversion: usize,
223 pub base: usize,
224 pub lagging: usize,
225 pub displacement: usize,
226 key: String,
227}
228
229impl IchimokuLeadingBRef {
230 fn new(conversion: usize, base: usize, lagging: usize, displacement: usize) -> Self {
231 Self {
232 conversion,
233 base,
234 lagging,
235 displacement,
236 key: format!("ichimoku_leading_b_{conversion}_{base}_{lagging}_{displacement}"),
237 }
238 }
239}
240
241impl IndicatorRef for IchimokuLeadingBRef {
242 fn key(&self) -> &str {
243 &self.key
244 }
245
246 fn required_indicators(&self) -> Vec<(String, Indicator)> {
247 vec![(
248 self.key.clone(),
249 Indicator::Ichimoku {
250 conversion: self.conversion,
251 base: self.base,
252 lagging: self.lagging,
253 displacement: self.displacement,
254 },
255 )]
256 }
257
258 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
259 ctx.indicator(self.key())
260 }
261
262 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
263 ctx.indicator_prev(self.key())
264 }
265}
266
267#[derive(Debug, Clone)]
277pub struct IchimokuLaggingRef {
278 pub conversion: usize,
279 pub base: usize,
280 pub lagging: usize,
281 pub displacement: usize,
282 key: String,
283}
284
285impl IchimokuLaggingRef {
286 fn new(conversion: usize, base: usize, lagging: usize, displacement: usize) -> Self {
287 Self {
288 conversion,
289 base,
290 lagging,
291 displacement,
292 key: format!("ichimoku_lagging_{conversion}_{base}_{lagging}_{displacement}"),
293 }
294 }
295}
296
297impl IndicatorRef for IchimokuLaggingRef {
298 fn key(&self) -> &str {
299 &self.key
300 }
301
302 fn required_indicators(&self) -> Vec<(String, Indicator)> {
303 vec![] }
305
306 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
307 let idx = ctx.index.checked_sub(self.lagging)?;
308 Some(ctx.candles[idx].close)
309 }
310
311 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
312 let idx = ctx.index.checked_sub(self.lagging + 1)?;
313 Some(ctx.candles[idx].close)
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::backtesting::config::BacktestConfig;
321 use crate::backtesting::engine::BacktestEngine;
322 use crate::backtesting::refs::{IndicatorRefExt, price};
323 use crate::backtesting::strategy::StrategyBuilder;
324 use crate::models::chart::Candle;
325 use std::collections::HashMap;
326
327 #[test]
328 fn test_ichimoku_keys() {
329 let ich = ichimoku();
330 assert_eq!(
331 ich.conversion_line().key(),
332 "ichimoku_conversion_9_26_26_26"
333 );
334 assert_eq!(ich.base_line().key(), "ichimoku_base_9_26_26_26");
335 assert_eq!(ich.leading_span_a().key(), "ichimoku_leading_a_9_26_26_26");
336 assert_eq!(ich.leading_span_b().key(), "ichimoku_leading_b_9_26_26_26");
337 assert_eq!(ich.lagging_span().key(), "ichimoku_lagging_9_26_26_26");
338 }
339
340 fn flat_candles(closes: &[f64]) -> Vec<Candle> {
341 closes
342 .iter()
343 .enumerate()
344 .map(|(i, &c)| Candle {
345 timestamp: i as i64 * 86_400,
346 open: c,
347 high: c,
348 low: c,
349 close: c,
350 volume: 1_000,
351 adj_close: Some(c),
352 provider_id: None,
353 })
354 .collect()
355 }
356
357 #[test]
358 fn test_lagging_ref_reads_close_from_lagging_bars_ago() {
359 let closes: Vec<f64> = (0..30).map(|i| i as f64).collect();
360 let candles = flat_candles(&closes);
361 let indicators = HashMap::new();
362 let lagging_ref = ichimoku_custom(9, 26, 5, 26).lagging_span();
363
364 let ctx_at = |index: usize| StrategyContext {
365 candles: &candles,
366 index,
367 position: None,
368 equity: 0.0,
369 indicators: &indicators,
370 extremes: None,
371 indicator_index: None,
372 };
373
374 assert_eq!(lagging_ref.value(&ctx_at(4)), None);
375 assert_eq!(lagging_ref.value(&ctx_at(5)), Some(closes[0]));
376 assert_eq!(lagging_ref.value(&ctx_at(10)), Some(closes[5]));
377 assert_eq!(lagging_ref.prev_value(&ctx_at(5)), None);
378 assert_eq!(lagging_ref.prev_value(&ctx_at(10)), Some(closes[4]));
379 }
380
381 #[test]
382 fn test_lagging_span_condition_signals_in_final_lagging_bars() {
383 let lagging = 5;
386 let n = 40;
387 let mut closes = vec![100.0; n];
388 for (offset, close) in closes.iter_mut().enumerate().skip(36) {
389 *close = 100.0 + (offset - 35) as f64 * 5.0;
390 }
391 let candles = flat_candles(&closes);
392
393 let ich = ichimoku_custom(9, 26, lagging, 26);
394 let strategy = StrategyBuilder::new("Chikou Cross")
395 .entry(price().above_ref(ich.lagging_span()))
396 .exit(price().below_ref(ich.lagging_span()))
397 .warmup(lagging + 1)
398 .build();
399
400 let result = BacktestEngine::new(BacktestConfig::zero_cost())
401 .run("TEST", &candles, strategy)
402 .unwrap();
403
404 let last_lagging_bars_start = (n - lagging) as i64 * 86_400;
405 assert!(
406 result
407 .trades
408 .iter()
409 .any(|t| t.entry_timestamp >= last_lagging_bars_start)
410 );
411 }
412}