finance_query/indicators/
ichimoku.rs1use super::{IndicatorError, Result};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct IchimokuResult {
9 pub conversion_line: Vec<Option<f64>>,
11 pub base_line: Vec<Option<f64>>,
13 pub leading_span_a: Vec<Option<f64>>,
15 pub leading_span_b: Vec<Option<f64>>,
17 pub lagging_span: Vec<Option<f64>>,
19}
20
21pub fn ichimoku(highs: &[f64], lows: &[f64], closes: &[f64]) -> Result<IchimokuResult> {
42 let len = highs.len();
43 if lows.len() != len || closes.len() != len {
44 return Err(IndicatorError::InvalidPeriod(
45 "Data lengths must match".to_string(),
46 ));
47 }
48 if len < 52 {
49 return Err(IndicatorError::InsufficientData { need: 52, got: len });
50 }
51
52 let mut conversion_line = vec![None; len];
53 let mut base_line = vec![None; len];
54 let mut leading_span_a = vec![None; len];
55 let mut leading_span_b = vec![None; len];
56 let mut lagging_span = vec![None; len];
57
58 let midpoint = |h: &[f64], l: &[f64]| -> f64 {
59 let highest = h.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
60 let lowest = l.iter().fold(f64::INFINITY, |a, &b| a.min(b));
61 (highest + lowest) / 2.0
62 };
63
64 for i in 0..len {
65 if i >= 8 {
66 let start = i - 8;
67 conversion_line[i] = Some(midpoint(&highs[start..=i], &lows[start..=i]));
68 }
69
70 if i >= 25 {
71 let start = i - 25;
72 base_line[i] = Some(midpoint(&highs[start..=i], &lows[start..=i]));
73 }
74
75 if i >= 25
76 && let (Some(conv), Some(base)) = (conversion_line[i], base_line[i])
77 {
78 let val = (conv + base) / 2.0;
79 if i + 26 < len {
80 leading_span_a[i + 26] = Some(val);
81 }
82 }
83
84 if i >= 51 {
85 let start = i - 51;
86 let val = midpoint(&highs[start..=i], &lows[start..=i]);
87 if i + 26 < len {
88 leading_span_b[i + 26] = Some(val);
89 }
90 }
91
92 if i >= 26 {
93 lagging_span[i - 26] = Some(closes[i]);
94 }
95 }
96
97 Ok(IchimokuResult {
98 conversion_line,
99 base_line,
100 leading_span_a,
101 leading_span_b,
102 lagging_span,
103 })
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_ichimoku() {
112 let highs = vec![10.0; 100];
113 let lows = vec![8.0; 100];
114 let closes = vec![9.0; 100];
115 let result = ichimoku(&highs, &lows, &closes).unwrap();
116
117 assert_eq!(result.conversion_line.len(), 100);
118 assert!(result.conversion_line[8].is_some());
119 assert!(result.base_line[25].is_some());
120 assert!(result.leading_span_a[51].is_some()); assert!(result.leading_span_b[77].is_some()); assert!(result.lagging_span[0].is_some()); }
124}