use crate::bar_indicators::indicator_value::IndicatorValue;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum CloudState {
Bullish,
Bearish,
Flat,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum CloudPosition {
AboveCloud,
InCloud,
BelowCloud,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum IchimokuSignal {
StrongBuy,
Buy,
Neutral,
Sell,
StrongSell,
}
#[derive(Debug, Clone)]
pub struct IchimokuCloud {
tenkan_period: usize, kijun_period: usize, senkou_b_period: usize, displacement: usize,
tenkan_high_buffer: Vec<f64>,
tenkan_low_buffer: Vec<f64>,
tenkan_index: usize,
tenkan_filled: bool,
kijun_high_buffer: Vec<f64>,
kijun_low_buffer: Vec<f64>,
kijun_index: usize,
kijun_filled: bool,
senkou_b_high_buffer: Vec<f64>,
senkou_b_low_buffer: Vec<f64>,
senkou_b_index: usize,
senkou_b_filled: bool,
chikou_buffer: Vec<f64>,
senkou_future_buffer: Vec<(f64, f64)>,
tenkan_sen: f64, kijun_sen: f64, senkou_span_a: f64, senkou_span_b: f64, chikou_span: f64,
current_cloud_top: f64,
current_cloud_bottom: f64,
cloud_state: CloudState,
cloud_thickness: f64,
bar_count: usize,
}
impl IchimokuCloud {
pub fn new() -> Self {
Self::new_custom(9, 26, 52, 26)
}
pub fn new_custom(
tenkan_period: usize,
kijun_period: usize,
senkou_b_period: usize,
displacement: usize
) -> Self {
assert!(tenkan_period > 0 && tenkan_period <= 512);
assert!(kijun_period > 0 && kijun_period <= 512);
assert!(senkou_b_period > 0 && senkou_b_period <= 512);
assert!(displacement > 0 && displacement <= 512);
Self {
tenkan_period,
kijun_period,
senkou_b_period,
displacement,
tenkan_high_buffer: Vec::with_capacity(512),
tenkan_low_buffer: Vec::with_capacity(512),
tenkan_index: 0,
tenkan_filled: false,
kijun_high_buffer: Vec::with_capacity(512),
kijun_low_buffer: Vec::with_capacity(512),
kijun_index: 0,
kijun_filled: false,
senkou_b_high_buffer: Vec::with_capacity(512),
senkou_b_low_buffer: Vec::with_capacity(512),
senkou_b_index: 0,
senkou_b_filled: false,
chikou_buffer: Vec::with_capacity(512),
senkou_future_buffer: Vec::with_capacity(512),
tenkan_sen: 0.0,
kijun_sen: 0.0,
senkou_span_a: 0.0,
senkou_span_b: 0.0,
chikou_span: 0.0,
current_cloud_top: 0.0,
current_cloud_bottom: 0.0,
cloud_state: CloudState::Flat,
cloud_thickness: 0.0,
bar_count: 0,
}
}
pub fn update_bar(&mut self, _open: f64, high: f64, low: f64, close: f64, _volume: f64) -> (f64, f64, f64, f64, f64) {
self.bar_count += 1;
self.update_tenkan_sen(high, low);
self.update_kijun_sen(high, low);
self.update_senkou_b_buffer(high, low);
self.update_senkou_spans();
self.update_chikou_span(close);
self.update_current_cloud();
self.analyze_cloud();
(self.tenkan_sen, self.kijun_sen, self.senkou_span_a, self.senkou_span_b, self.chikou_span)
}
fn update_tenkan_sen(&mut self, high: f64, low: f64) {
if self.tenkan_filled {
self.tenkan_high_buffer[self.tenkan_index] = high;
self.tenkan_low_buffer[self.tenkan_index] = low;
} else {
self.tenkan_high_buffer.push(high);
self.tenkan_low_buffer.push(low);
}
self.tenkan_index = (self.tenkan_index + 1) % self.tenkan_period;
if self.tenkan_high_buffer.len() == self.tenkan_period && !self.tenkan_filled {
self.tenkan_filled = true;
}
if self.tenkan_filled {
let (min_low, max_high) = self.tenkan_high_buffer.iter()
.zip(self.tenkan_low_buffer.iter())
.fold((f64::INFINITY, f64::NEG_INFINITY),
|(min, max), (&h, &l)| (min.min(l), max.max(h)));
self.tenkan_sen = (max_high + min_low) / 2.0;
}
}
fn update_kijun_sen(&mut self, high: f64, low: f64) {
if self.kijun_filled {
self.kijun_high_buffer[self.kijun_index] = high;
self.kijun_low_buffer[self.kijun_index] = low;
} else {
self.kijun_high_buffer.push(high);
self.kijun_low_buffer.push(low);
}
self.kijun_index = (self.kijun_index + 1) % self.kijun_period;
if self.kijun_high_buffer.len() == self.kijun_period && !self.kijun_filled {
self.kijun_filled = true;
}
if self.kijun_filled {
let (min_low, max_high) = self.kijun_high_buffer.iter()
.zip(self.kijun_low_buffer.iter())
.fold((f64::INFINITY, f64::NEG_INFINITY),
|(min, max), (&h, &l)| (min.min(l), max.max(h)));
self.kijun_sen = (max_high + min_low) / 2.0;
}
}
fn update_senkou_b_buffer(&mut self, high: f64, low: f64) {
if self.senkou_b_filled {
self.senkou_b_high_buffer[self.senkou_b_index] = high;
self.senkou_b_low_buffer[self.senkou_b_index] = low;
} else {
self.senkou_b_high_buffer.push(high);
self.senkou_b_low_buffer.push(low);
}
self.senkou_b_index = (self.senkou_b_index + 1) % self.senkou_b_period;
if self.senkou_b_high_buffer.len() == self.senkou_b_period && !self.senkou_b_filled {
self.senkou_b_filled = true;
}
}
fn update_senkou_spans(&mut self) {
if self.tenkan_filled && self.kijun_filled {
self.senkou_span_a = (self.tenkan_sen + self.kijun_sen) / 2.0;
}
if self.senkou_b_filled {
let (min_low, max_high) = self.senkou_b_high_buffer.iter()
.zip(self.senkou_b_low_buffer.iter())
.fold((f64::INFINITY, f64::NEG_INFINITY),
|(min, max), (&h, &l)| (min.min(l), max.max(h)));
self.senkou_span_b = (max_high + min_low) / 2.0;
}
if self.senkou_future_buffer.len() >= self.displacement {
self.senkou_future_buffer.remove(0);
}
self.senkou_future_buffer.push((self.senkou_span_a, self.senkou_span_b));
}
fn update_chikou_span(&mut self, close: f64) {
if self.chikou_buffer.len() >= self.displacement {
self.chikou_buffer.remove(0);
}
self.chikou_buffer.push(close);
if self.chikou_buffer.len() >= self.displacement {
self.chikou_span = self.chikou_buffer[0];
}
}
fn update_current_cloud(&mut self) {
if self.senkou_future_buffer.len() >= self.displacement {
let (senkou_a_past, senkou_b_past) = self.senkou_future_buffer[0];
self.current_cloud_top = senkou_a_past.max(senkou_b_past);
self.current_cloud_bottom = senkou_a_past.min(senkou_b_past);
}
}
fn analyze_cloud(&mut self) {
if self.senkou_span_a > self.senkou_span_b + 0.0001 {
self.cloud_state = CloudState::Bullish;
} else if self.senkou_span_a < self.senkou_span_b - 0.0001 {
self.cloud_state = CloudState::Bearish;
} else {
self.cloud_state = CloudState::Flat;
}
self.cloud_thickness = (self.current_cloud_top - self.current_cloud_bottom).abs();
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Ichimoku {
tenkan: self.tenkan_sen,
kijun: self.kijun_sen,
senkou_a: self.senkou_span_a,
senkou_b: self.senkou_span_b,
chikou: self.chikou_span,
}
}
pub fn value_tuple(&self) -> (f64, f64, f64, f64, f64) {
(self.tenkan_sen, self.kijun_sen, self.senkou_span_a, self.senkou_span_b, self.chikou_span)
}
pub fn current_cloud(&self) -> (f64, f64) {
(self.current_cloud_top, self.current_cloud_bottom)
}
pub fn future_cloud(&self) -> (f64, f64) {
(self.senkou_span_a, self.senkou_span_b)
}
pub fn tenkan_sen(&self) -> f64 {
self.tenkan_sen
}
pub fn kijun_sen(&self) -> f64 {
self.kijun_sen
}
pub fn chikou_span(&self) -> f64 {
self.chikou_span
}
pub fn cloud_state(&self) -> CloudState {
self.cloud_state
}
pub fn cloud_thickness(&self) -> f64 {
self.cloud_thickness
}
pub fn price_cloud_position(&self, price: f64) -> CloudPosition {
if price > self.current_cloud_top {
CloudPosition::AboveCloud
} else if price < self.current_cloud_bottom {
CloudPosition::BelowCloud
} else {
CloudPosition::InCloud
}
}
pub fn generate_signal(&self, current_price: f64) -> IchimokuSignal {
let mut bullish_signals = 0;
let mut bearish_signals = 0;
if self.tenkan_sen > self.kijun_sen {
bullish_signals += 1;
} else if self.tenkan_sen < self.kijun_sen {
bearish_signals += 1;
}
match self.price_cloud_position(current_price) {
CloudPosition::AboveCloud => bullish_signals += 2,
CloudPosition::BelowCloud => bearish_signals += 2,
CloudPosition::InCloud => {}, }
match self.cloud_state {
CloudState::Bullish => bullish_signals += 1,
CloudState::Bearish => bearish_signals += 1,
CloudState::Flat => {},
}
if self.chikou_span > current_price {
bullish_signals += 1;
} else if self.chikou_span < current_price {
bearish_signals += 1;
}
let signal_strength = bullish_signals - bearish_signals;
match signal_strength {
3..=5 => IchimokuSignal::StrongBuy,
1..=2 => IchimokuSignal::Buy,
0 => IchimokuSignal::Neutral,
-2..=-1 => IchimokuSignal::Sell,
-5..=-3 => IchimokuSignal::StrongSell,
_ => IchimokuSignal::Neutral,
}
}
pub fn is_kijun_breakout(&self, current_price: f64, previous_price: f64) -> Option<bool> {
if previous_price <= self.kijun_sen && current_price > self.kijun_sen {
Some(true) } else if previous_price >= self.kijun_sen && current_price < self.kijun_sen {
Some(false) } else {
None
}
}
pub fn is_tk_cross(&self, prev_tenkan: f64, prev_kijun: f64) -> Option<bool> {
let current_tk_diff = self.tenkan_sen - self.kijun_sen;
let prev_tk_diff = prev_tenkan - prev_kijun;
if prev_tk_diff <= 0.0 && current_tk_diff > 0.0 {
Some(true) } else if prev_tk_diff >= 0.0 && current_tk_diff < 0.0 {
Some(false) } else {
None
}
}
pub fn is_ready(&self) -> bool {
self.tenkan_filled && self.kijun_filled && self.senkou_b_filled
&& self.chikou_buffer.len() >= self.displacement
&& self.senkou_future_buffer.len() >= self.displacement
}
pub fn reset(&mut self) {
self.tenkan_high_buffer.clear();
self.tenkan_low_buffer.clear();
self.tenkan_index = 0;
self.tenkan_filled = false;
self.kijun_high_buffer.clear();
self.kijun_low_buffer.clear();
self.kijun_index = 0;
self.kijun_filled = false;
self.senkou_b_high_buffer.clear();
self.senkou_b_low_buffer.clear();
self.senkou_b_index = 0;
self.senkou_b_filled = false;
self.chikou_buffer.clear();
self.senkou_future_buffer.clear();
self.tenkan_sen = 0.0;
self.kijun_sen = 0.0;
self.senkou_span_a = 0.0;
self.senkou_span_b = 0.0;
self.chikou_span = 0.0;
self.current_cloud_top = 0.0;
self.current_cloud_bottom = 0.0;
self.cloud_state = CloudState::Flat;
self.cloud_thickness = 0.0;
self.bar_count = 0;
}
pub fn get_params(&self) -> (usize, usize, usize, usize) {
(self.tenkan_period, self.kijun_period, self.senkou_b_period, self.displacement)
}
}
impl Default for IchimokuCloud {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ichimoku_cloud_creation() {
let ic = IchimokuCloud::new();
assert!(!ic.is_ready());
let (tenkan, kijun, _senkou_b, _displacement) = ic.get_params();
assert_eq!(tenkan, 9);
assert_eq!(kijun, 26);
}
#[test]
fn test_ichimoku_cloud_warmup() {
let mut ic = IchimokuCloud::new();
for i in 0..55 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
ic.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
assert!(ic.is_ready());
}
#[test]
fn test_ichimoku_cloud_values() {
let mut ic = IchimokuCloud::new();
for i in 0..80 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
let (tenkan, kijun, _senkou_a, _senkou_b, _chikou) = ic.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
if ic.is_ready() {
assert!(tenkan.is_finite());
assert!(kijun.is_finite());
}
}
}
#[test]
fn test_ichimoku_cloud_reset() {
let mut ic = IchimokuCloud::new();
for i in 0..80 {
ic.update_bar(100.0 + i as f64, 101.0, 99.0, 100.0 + i as f64, 1000.0);
}
ic.reset();
assert!(!ic.is_ready());
}
}