1use crate::{Candlestick, Error, IndexEntry, IndexEntryLike};
19
20#[derive(Debug)]
21pub struct IchimokuEntry {
22 pub at: u64,
23 pub conversion_line: Option<f64>,
24 pub base_line: Option<f64>,
25 pub leading_span_a: Option<f64>,
26 pub leading_span_b: Option<f64>,
27 pub lagging_span: Option<f64>,
28}
29
30#[derive(Debug)]
31pub struct IchimokuData {
32 pub conversion_line: Vec<IndexEntry>,
33 pub base_line: Vec<IndexEntry>,
34 pub leading_span_a: Vec<IndexEntry>,
35 pub leading_span_b: Vec<IndexEntry>,
36 pub lagging_span: Vec<IndexEntry>,
37}
38
39impl IchimokuData {
40 pub fn get(&self, at: u64) -> Option<IchimokuEntry> {
41 let conversion_line = self
42 .conversion_line
43 .iter()
44 .find(|x| x.at == at)
45 .map(|x| x.value);
46 let base_line = self.base_line.iter().find(|x| x.at == at).map(|x| x.value);
47 let leading_span_a = self
48 .leading_span_a
49 .iter()
50 .find(|x| x.at == at)
51 .map(|x| x.value);
52 let leading_span_b = self
53 .leading_span_b
54 .iter()
55 .find(|x| x.at == at)
56 .map(|x| x.value);
57 let lagging_span = self
58 .lagging_span
59 .iter()
60 .find(|x| x.at == at)
61 .map(|x| x.value);
62 if [
63 conversion_line,
64 base_line,
65 leading_span_a,
66 leading_span_b,
67 lagging_span,
68 ]
69 .iter()
70 .all(|x| x.is_none())
71 {
72 return None;
73 }
74 Some(IchimokuEntry {
75 at,
76 conversion_line,
77 base_line,
78 leading_span_a,
79 leading_span_b,
80 lagging_span,
81 })
82 }
83}
84
85pub fn ichimoku_default(entries: &[Candlestick]) -> Result<IchimokuData, Error> {
87 ichimoku(entries, 9, 26, 52, 26)
88}
89
90pub fn ichimoku(
92 entries: &[Candlestick],
93 conversion_line_len: usize,
94 base_line_len: usize,
95 leading_span_b_len: usize,
96 lagging_span: usize,
97) -> Result<IchimokuData, Error> {
98 Candlestick::validate_list(entries)?;
99
100 let base_line = calc_base_and_conversion_line(entries, base_line_len);
101 let conversion_line = calc_base_and_conversion_line(entries, conversion_line_len);
102 Ok(IchimokuData {
103 conversion_line: conversion_line.clone(),
104 base_line: base_line.clone(),
105 leading_span_a: calc_leading_span_a(&base_line, &conversion_line, lagging_span),
106 leading_span_b: calc_leading_span_b(entries, leading_span_b_len, lagging_span),
107 lagging_span: calc_lagging_span(entries, lagging_span),
108 })
109}
110
111fn calc_base_and_conversion_line(entries: &[Candlestick], line_len: usize) -> Vec<IndexEntry> {
112 if line_len == 0 {
113 return vec![];
114 }
115 let mut ret = Vec::<IndexEntry>::new();
116 for i in 0..entries.len() {
117 if entries.len() < i + line_len {
118 break;
119 }
120 let xs = &entries[i..i + line_len];
121 let highest = xs.iter().fold(
122 -1.0,
123 |z, x| if z == -1.0 || z < x.high { x.high } else { z },
124 );
125 let lowest = xs
126 .iter()
127 .fold(-1.0, |z, x| if z == -1.0 || z > x.low { x.low } else { z });
128 ret.push(IndexEntry {
129 at: xs.last().unwrap().at,
130 value: (highest + lowest) / 2.0,
131 });
132 }
133 ret
134}
135
136fn calc_leading_span_a(
137 base_line: &[IndexEntry],
138 conversion_line: &[IndexEntry],
139 span: usize,
140) -> Vec<IndexEntry> {
141 let entries: Vec<IndexEntry> = base_line
142 .iter()
143 .filter_map(|b| {
144 conversion_line
145 .iter()
146 .find(|c| c.at == b.at)
147 .map(|c| (b, c))
148 })
149 .map(|(b, c)| IndexEntry {
150 at: b.at,
151 value: (b.value + c.value) / 2.0,
152 })
153 .collect();
154 apply_lag(&entries, span, false)
155}
156
157fn calc_leading_span_b(entries: &[Candlestick], line_len: usize, span: usize) -> Vec<IndexEntry> {
158 apply_lag(
159 &calc_base_and_conversion_line(entries, line_len),
160 span,
161 false,
162 )
163}
164
165fn calc_lagging_span(entries: &[Candlestick], span: usize) -> Vec<IndexEntry> {
166 apply_lag(entries, span, true)
167}
168
169fn apply_lag(entries: &[impl IndexEntryLike], span: usize, backward: bool) -> Vec<IndexEntry> {
170 if entries.len() < 2 || span == 0 {
171 return entries.iter().map(|x| IndexEntry::from(x)).collect();
172 }
173 let len = entries.len();
174 let last = entries.last().unwrap();
175 let prev = &entries[len - 2];
176 let duration = (last.get_at() - prev.get_at()) * (span - 1) as u64;
177 entries
178 .iter()
179 .enumerate()
180 .map(|(i, x)| IndexEntry {
181 at: {
182 let pos = i as u32 + span as u32;
183 if 0 == pos && pos < len as u32 {
184 entries[pos as usize].get_at()
185 } else if backward {
186 x.get_at() - duration
187 } else {
188 x.get_at() + duration
189 }
190 },
191 value: x.get_value(),
192 })
193 .collect()
194}