quantwave_core/indicators/
ichimoku.rs1use crate::indicators::donchian::DonchianChannels;
2use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
3use crate::traits::Next;
4
5#[derive(Debug, Clone)]
11pub struct IchimokuCloud {
12 tenkan_dc: DonchianChannels,
13 kijun_dc: DonchianChannels,
14 senkou_b_dc: DonchianChannels,
15}
16
17impl IchimokuCloud {
18 pub fn new(tenkan_period: usize, kijun_period: usize, senkou_b_period: usize) -> Self {
19 Self {
20 tenkan_dc: DonchianChannels::new(tenkan_period),
21 kijun_dc: DonchianChannels::new(kijun_period),
22 senkou_b_dc: DonchianChannels::new(senkou_b_period),
23 }
24 }
25}
26
27impl Next<(f64, f64)> for IchimokuCloud {
28 type Output = (f64, f64, f64, f64); fn next(&mut self, (high, low): (f64, f64)) -> Self::Output {
31 let (_, tenkan, _) = self.tenkan_dc.next((high, low));
32 let (_, kijun, _) = self.kijun_dc.next((high, low));
33 let (_, senkou_b, _) = self.senkou_b_dc.next((high, low));
34
35 let senkou_a = (tenkan + kijun) / 2.0;
36
37 (tenkan, kijun, senkou_a, senkou_b)
38 }
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use proptest::prelude::*;
45 use serde::Deserialize;
46 use std::fs;
47 use std::path::Path;
48
49 #[derive(Debug, Deserialize)]
50 struct IchimokuCase {
51 high: Vec<f64>,
52 low: Vec<f64>,
53 expected_tenkan: Vec<f64>,
54 expected_kijun: Vec<f64>,
55 expected_senkou_a: Vec<f64>,
56 expected_senkou_b: Vec<f64>,
57 }
58
59 #[test]
60 fn test_ichimoku_gold_standard() {
61 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
62 let manifest_path = Path::new(&manifest_dir);
63 let path = manifest_path.join("tests/gold_standard/ichimoku.json");
64 let path = if path.exists() {
65 path
66 } else {
67 manifest_path
68 .parent()
69 .unwrap()
70 .join("tests/gold_standard/ichimoku.json")
71 };
72 let content = fs::read_to_string(path).unwrap();
73 let case: IchimokuCase = serde_json::from_str(&content).unwrap();
74
75 let mut ic = IchimokuCloud::new(9, 26, 52);
76 for i in 0..case.high.len() {
77 let (t, k, sa, sb) = ic.next((case.high[i], case.low[i]));
78 approx::assert_relative_eq!(t, case.expected_tenkan[i], epsilon = 1e-6);
79 approx::assert_relative_eq!(k, case.expected_kijun[i], epsilon = 1e-6);
80 approx::assert_relative_eq!(sa, case.expected_senkou_a[i], epsilon = 1e-6);
81 approx::assert_relative_eq!(sb, case.expected_senkou_b[i], epsilon = 1e-6);
82 }
83 }
84
85 fn ichimoku_batch(
86 data: Vec<(f64, f64)>,
87 p1: usize,
88 p2: usize,
89 p3: usize,
90 ) -> Vec<(f64, f64, f64, f64)> {
91 let mut ic = IchimokuCloud::new(p1, p2, p3);
92 data.into_iter().map(|x| ic.next(x)).collect()
93 }
94
95 proptest! {
96 #[test]
97 fn test_ichimoku_parity(input in prop::collection::vec((0.0..100.0, 0.0..100.0), 1..100)) {
98 let mut adj_input = Vec::with_capacity(input.len());
99 for (h, l) in input {
100 let h_f: f64 = h;
101 let l_f: f64 = l;
102 let high = h_f.max(l_f);
103 let low = l_f.min(h_f);
104 adj_input.push((high, low));
105 }
106
107 let p1 = 9;
108 let p2 = 26;
109 let p3 = 52;
110 let mut ic = IchimokuCloud::new(p1, p2, p3);
111 let mut streaming_results = Vec::with_capacity(adj_input.len());
112 for &val in &adj_input {
113 streaming_results.push(ic.next(val));
114 }
115
116 let batch_results = ichimoku_batch(adj_input, p1, p2, p3);
117
118 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
119 approx::assert_relative_eq!(s.0, b.0, epsilon = 1e-6);
120 approx::assert_relative_eq!(s.1, b.1, epsilon = 1e-6);
121 approx::assert_relative_eq!(s.2, b.2, epsilon = 1e-6);
122 approx::assert_relative_eq!(s.3, b.3, epsilon = 1e-6);
123 }
124 }
125 }
126
127 #[test]
128 fn test_ichimoku_basic() {
129 let mut ic = IchimokuCloud::new(3, 5, 10);
130 let (t, k, sa, sb) = ic.next((10.0, 8.0));
131 assert_eq!(t, 9.0);
132 assert_eq!(k, 9.0);
133 assert_eq!(sa, 9.0);
134 assert_eq!(sb, 9.0);
135 }
136}
137
138pub const ICHIMOKU_METADATA: IndicatorMetadata = IndicatorMetadata {
139 name: "Ichimoku Cloud",
140 description: "Ichimoku Kinko Hyo is a comprehensive indicator that defines support and resistance, identifies trend direction, gauges momentum and provides trading signals.",
141 usage: "Use as a complete trend system providing support, resistance, momentum, and cloud-based bias in a single indicator. The Kumo cloud thickness indicates trend strength.",
142 keywords: &["trend", "support-resistance", "classic", "japanese", "momentum"],
143 ehlers_summary: "Ichimoku Kinko Hyo was developed by Goichi Hosoda in the 1960s. The system comprises five components: Tenkan-sen (9-period midpoint), Kijun-sen (26-period midpoint), Senkou Span A and B (cloud), and Chikou Span (lagged close). Price above the cloud is bullish; the cloud thickness quantifies the strength of support or resistance. — Ichimoku Charts, Nicole Elliott",
144 params: &[
145 ParamDef {
146 name: "tenkan_period",
147 default: "9",
148 description: "Tenkan-sen period",
149 },
150 ParamDef {
151 name: "kijun_period",
152 default: "26",
153 description: "Kijun-sen period",
154 },
155 ParamDef {
156 name: "senkou_span_b_period",
157 default: "52",
158 description: "Senkou Span B period",
159 },
160 ],
161 formula_source: "https://www.investopedia.com/terms/i/ichimoku-cloud.asp",
162 formula_latex: r#"
163\[
164\text{Tenkan-sen} = \frac{\text{Highest High} + \text{Lowest Low}}{2} \text{ for past 9 periods}
165\]
166"#,
167 gold_standard_file: "ichimoku.json",
168 category: "Classic",
169};