use crate::bar_indicators::average::MovingAverageType;
use crate::bar_indicators::average::moving_average::MovingAverageProvider;
use crate::bar_indicators::indicator_value::IndicatorValue;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Default)]
pub enum PriceChannelMode {
#[default]
Raw,
Smoothed,
}
#[derive(Debug, Clone)]
pub struct PriceChannels {
period: usize,
mode: PriceChannelMode,
ma_type: MovingAverageType,
high_buffer: Vec<f64>,
low_buffer: Vec<f64>,
buffer_index: usize,
buffer_filled: bool,
ma_high: Option<MovingAverageProvider>,
ma_low: Option<MovingAverageProvider>,
upper: f64,
middle: f64,
lower: f64,
}
impl PriceChannels {
pub fn new(period: usize, mode: PriceChannelMode, ma_type: MovingAverageType) -> Self {
assert!(period > 0 && period <= 512, "Period must be between 1 and 512");
let (ma_high, ma_low) = if mode == PriceChannelMode::Smoothed {
(
Some(MovingAverageProvider::new(ma_type, period)),
Some(MovingAverageProvider::new(ma_type, period)),
)
} else {
(None, None)
};
Self {
period,
mode,
ma_type,
high_buffer: Vec::with_capacity(period),
low_buffer: Vec::with_capacity(period),
buffer_index: 0,
buffer_filled: false,
ma_high,
ma_low,
upper: 0.0,
middle: 0.0,
lower: 0.0,
}
}
pub fn new_raw(period: usize) -> Self {
Self::new(period, PriceChannelMode::Raw, MovingAverageType::SMA)
}
pub fn new_smoothed_sma(period: usize) -> Self {
Self::new(period, PriceChannelMode::Smoothed, MovingAverageType::SMA)
}
pub fn new_smoothed_ema(period: usize) -> Self {
Self::new(period, PriceChannelMode::Smoothed, MovingAverageType::EMA)
}
pub fn new_smoothed(period: usize, ma_type: MovingAverageType) -> Self {
Self::new(period, PriceChannelMode::Smoothed, ma_type)
}
pub fn update_bar(&mut self, _open: f64, high: f64, low: f64, _close: f64, _volume: f64) -> (f64, f64, f64) {
if self.buffer_filled {
self.high_buffer[self.buffer_index] = high;
self.low_buffer[self.buffer_index] = low;
} else {
self.high_buffer.push(high);
self.low_buffer.push(low);
}
self.buffer_index = (self.buffer_index + 1) % self.period;
if self.high_buffer.len() == self.period && !self.buffer_filled {
self.buffer_filled = true;
}
match self.mode {
PriceChannelMode::Raw => {
self.calculate_raw_channels();
}
PriceChannelMode::Smoothed => {
self.calculate_smoothed_channels(high, low);
}
}
(self.upper, self.middle, self.lower)
}
fn calculate_raw_channels(&mut self) {
if self.high_buffer.is_empty() || self.low_buffer.is_empty() {
self.upper = 0.0;
self.lower = 0.0;
} else {
let buffer_len = if self.buffer_filled { self.period } else { self.high_buffer.len() };
let (lower, upper) = self.high_buffer.iter()
.zip(self.low_buffer.iter())
.take(buffer_len)
.fold((f64::INFINITY, f64::NEG_INFINITY),
|(min, max), (&h, &l)| (min.min(l), max.max(h)));
self.upper = upper;
self.lower = lower;
}
self.middle = (self.upper + self.lower) / 2.0;
}
fn calculate_smoothed_channels(&mut self, _current_high: f64, _current_low: f64) {
if let (Some(ma_high), Some(ma_low)) = (&mut self.ma_high, &mut self.ma_low) {
let buffer_len = if self.buffer_filled { self.period } else { self.high_buffer.len() };
let (current_min, current_max) = self.high_buffer.iter()
.zip(self.low_buffer.iter())
.take(buffer_len)
.fold((f64::INFINITY, f64::NEG_INFINITY),
|(min, max), (&h, &l)| (min.min(l), max.max(h)));
self.upper = ma_high.update_bar(current_max, current_max, current_max, current_max, 0.0);
self.lower = ma_low.update_bar(current_min, current_min, current_min, current_min, 0.0);
} else {
self.calculate_raw_channels();
}
self.middle = (self.upper + self.lower) / 2.0;
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Channel3 {
upper: self.upper,
middle: self.middle,
lower: self.lower,
}
}
pub fn value_tuple(&self) -> (f64, f64, f64) {
(self.upper, self.middle, self.lower)
}
pub fn upper(&self) -> f64 {
self.upper
}
pub fn middle(&self) -> f64 {
self.middle
}
pub fn lower(&self) -> f64 {
self.lower
}
pub fn channel_width(&self) -> f64 {
if self.is_ready() {
self.upper - self.lower
} else {
0.0
}
}
pub fn position_in_channel(&self, price: f64) -> f64 {
if !self.is_ready() || self.upper == self.lower {
0.5 } else {
((price - self.lower) / (self.upper - self.lower)).clamp(0.0, 1.0)
}
}
pub fn is_ready(&self) -> bool {
match self.mode {
PriceChannelMode::Raw => self.buffer_filled,
PriceChannelMode::Smoothed => {
if let (Some(ma_high), Some(ma_low)) = (&self.ma_high, &self.ma_low) {
self.buffer_filled && ma_high.is_ready() && ma_low.is_ready()
} else {
self.buffer_filled
}
}
}
}
pub fn reset(&mut self) {
self.high_buffer.clear();
self.low_buffer.clear();
self.buffer_index = 0;
self.buffer_filled = false;
if let Some(ref mut ma_high) = self.ma_high {
ma_high.reset();
}
if let Some(ref mut ma_low) = self.ma_low {
ma_low.reset();
}
self.upper = 0.0;
self.middle = 0.0;
self.lower = 0.0;
}
pub fn period(&self) -> usize {
self.period
}
pub fn mode(&self) -> PriceChannelMode {
self.mode
}
pub fn ma_type(&self) -> MovingAverageType {
self.ma_type
}
}
impl Default for PriceChannels {
fn default() -> Self {
Self::new_raw(20)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_price_channels_creation() {
let pc = PriceChannels::new_raw(20);
assert!(!pc.is_ready());
assert_eq!(pc.upper(), 0.0);
assert_eq!(pc.lower(), 0.0);
}
#[test]
fn test_price_channels_warmup() {
let mut pc = PriceChannels::new_raw(20);
for i in 0..25 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
pc.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
assert!(pc.is_ready());
}
#[test]
fn test_price_channels_values() {
let mut pc = PriceChannels::new_raw(20);
for i in 0..25 {
let price = 100.0 + i as f64;
pc.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
assert!(pc.upper() >= pc.middle());
assert!(pc.middle() >= pc.lower());
}
#[test]
fn test_price_channels_smoothed() {
let mut pc = PriceChannels::new_smoothed_ema(20);
for i in 0..30 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
pc.update_bar(price, price + 2.0, price - 2.0, price, 1000.0);
}
assert!(pc.is_ready());
assert!(pc.channel_width() >= 0.0);
}
#[test]
fn test_price_channels_position() {
let mut pc = PriceChannels::new_raw(20);
for i in 0..25 {
let price = 100.0 + i as f64;
pc.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
let pos = pc.position_in_channel(110.0);
assert!(pos >= 0.0 && pos <= 1.0);
}
#[test]
fn test_price_channels_reset() {
let mut pc = PriceChannels::new_raw(20);
for i in 0..25 {
pc.update_bar(100.0 + i as f64, 101.0, 99.0, 100.0 + i as f64, 1000.0);
}
pc.reset();
assert!(!pc.is_ready());
assert_eq!(pc.upper(), 0.0);
assert_eq!(pc.lower(), 0.0);
}
}