use crate::bar_indicators::average::{MovingAverageProvider, MovingAverageType};
use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::ohlcv_field::OhlcvField;
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct MesaAdaptiveResult {
pub value: f64, pub period: f64, pub phase: f64, pub i_component: f64, pub q_component: f64, pub cycle_strength: f64, }
impl MesaAdaptiveResult {
pub fn empty() -> Self {
Self {
value: 0.0,
period: 20.0,
phase: 0.0,
i_component: 0.0,
q_component: 0.0,
cycle_strength: 0.0,
}
}
}
#[derive(Clone)]
pub struct MesaAdaptiveMA {
fast_ma: MovingAverageProvider, slow_ma: MovingAverageProvider, period_ma: MovingAverageProvider,
prices: Vec<f64>,
i_components: Vec<f64>,
q_components: Vec<f64>,
periods: Vec<f64>,
min_period: f64,
max_period: f64,
ma_type: MovingAverageType,
current_period: f64,
adaptive_ma: MovingAverageProvider,
current_result: MesaAdaptiveResult,
source: OhlcvField,
is_ready: bool,
update_count: usize,
}
impl MesaAdaptiveMA {
pub fn new() -> Self {
Self::with_parameters(8.0, 50.0, MovingAverageType::EMA)
}
pub fn with_parameters(min_period: f64, max_period: f64, ma_type: MovingAverageType) -> Self {
Self::with_source(min_period, max_period, ma_type, OhlcvField::Close)
}
pub fn with_source(min_period: f64, max_period: f64, ma_type: MovingAverageType, source: OhlcvField) -> Self {
assert!(min_period > 0.0 && max_period > min_period,
"Invalid period range");
let initial_period = ((min_period + max_period) / 2.0) as usize;
Self {
fast_ma: MovingAverageProvider::new(MovingAverageType::EMA, 6),
slow_ma: MovingAverageProvider::new(MovingAverageType::EMA, 12),
period_ma: MovingAverageProvider::new(MovingAverageType::EMA, 10),
prices: Vec::with_capacity(64),
i_components: Vec::with_capacity(32),
q_components: Vec::with_capacity(32),
periods: Vec::with_capacity(32),
min_period,
max_period,
ma_type,
current_period: (min_period + max_period) / 2.0,
adaptive_ma: MovingAverageProvider::new(ma_type, initial_period),
current_result: MesaAdaptiveResult::empty(),
source,
is_ready: false,
update_count: 0,
}
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, volume: f64) -> MesaAdaptiveResult {
let price = self.source.extract(open, high, low, close, volume);
self.update_price(price)
}
pub fn update_price(&mut self, price: f64) -> MesaAdaptiveResult {
if self.prices.len() >= 64 {
self.prices.remove(0);
}
self.prices.push(price);
if self.prices.len() >= 7 {
self.calculate_hilbert_components();
self.calculate_adaptive_period();
self.update_adaptive_ma(price);
self.is_ready = true;
}
self.update_count += 1;
self.current_result
}
fn calculate_hilbert_components(&mut self) {
let len = self.prices.len();
if len < 7 {
return;
}
let current_price = self.prices[len - 1];
let fast_value = self.fast_ma.update_bar(0.0, 0.0, 0.0, current_price, 0.0);
let slow_value = self.slow_ma.update_bar(0.0, 0.0, 0.0, current_price, 0.0);
let i_component = fast_value - slow_value;
let q_component = if len >= 4 {
let prev_fast = if self.i_components.len() >= 2 {
self.i_components[self.i_components.len() - 2]
} else {
i_component
};
(i_component + prev_fast) / 2.0
} else {
i_component
};
if self.i_components.len() >= 32 {
self.i_components.remove(0);
}
self.i_components.push(i_component);
if self.q_components.len() >= 32 {
self.q_components.remove(0);
}
self.q_components.push(q_component);
self.current_result.i_component = i_component;
self.current_result.q_component = q_component;
if i_component != 0.0 {
self.current_result.phase = (q_component / i_component).atan();
}
}
fn calculate_adaptive_period(&mut self) {
if self.i_components.len() < 2 || self.q_components.len() < 2 {
return;
}
let i_len = self.i_components.len();
let q_len = self.q_components.len();
let i_curr = self.i_components[i_len - 1];
let i_prev = self.i_components[i_len - 2];
let q_curr = self.q_components[q_len - 1];
let q_prev = self.q_components[q_len - 2];
let phase_curr = if i_curr != 0.0 { (q_curr / i_curr).atan() } else { 0.0 };
let phase_prev = if i_prev != 0.0 { (q_prev / i_prev).atan() } else { 0.0 };
let mut delta_phase = phase_curr - phase_prev;
if delta_phase < -PI {
delta_phase += 2.0 * PI;
} else if delta_phase > PI {
delta_phase -= 2.0 * PI;
}
let inst_period = if delta_phase.abs() > 0.01 {
let period = 2.0 * PI / delta_phase.abs();
period.max(self.min_period).min(self.max_period)
} else {
self.current_period
};
let smoothed_period = self.period_ma.update_bar(0.0, 0.0, 0.0, inst_period, 0.0);
if self.periods.len() >= 32 {
self.periods.remove(0);
}
self.periods.push(smoothed_period);
self.current_period = smoothed_period;
self.current_result.period = smoothed_period;
self.calculate_cycle_strength();
}
fn calculate_cycle_strength(&mut self) {
if self.periods.len() < 5 {
self.current_result.cycle_strength = 0.5;
return;
}
let recent_periods = &self.periods[self.periods.len() - 5..];
let mean: f64 = recent_periods.iter().sum::<f64>() / recent_periods.len() as f64;
let variance: f64 = recent_periods.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f64>() / recent_periods.len() as f64;
let std_dev = variance.sqrt();
let cv = if mean > 0.0 { std_dev / mean } else { 1.0 };
let strength = (1.0 - cv.min(1.0)).max(0.0);
self.current_result.cycle_strength = strength;
}
fn update_adaptive_ma(&mut self, price: f64) {
let adaptive_value = self.adaptive_ma.update_bar(0.0, 0.0, 0.0, price, 0.0);
self.current_result.value = adaptive_value;
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.current_result.value)
}
pub fn result(&self) -> MesaAdaptiveResult {
self.current_result
}
pub fn is_ready(&self) -> bool {
self.is_ready && self.adaptive_ma.is_ready()
}
pub fn reset(&mut self) {
self.fast_ma.reset();
self.slow_ma.reset();
self.period_ma.reset();
self.adaptive_ma.reset();
self.prices.clear();
self.i_components.clear();
self.q_components.clear();
self.periods.clear();
self.current_period = (self.min_period + self.max_period) / 2.0;
self.current_result = MesaAdaptiveResult::empty();
self.is_ready = false;
self.update_count = 0;
}
pub fn period(&self) -> usize {
self.current_period as usize
}
pub fn trading_signal(&self, current_price: f64, prev_price: f64) -> i8 {
if !self.is_ready {
return 0;
}
let ma_value = self.current_result.value;
let strength = self.current_result.cycle_strength;
if strength < 0.3 {
return 0;
}
if prev_price <= ma_value && current_price > ma_value {
return 1;
}
if prev_price >= ma_value && current_price < ma_value {
return -1;
}
0
}
pub fn info(&self, current_price: f64) -> String {
let result = self.current_result;
let trend = if current_price > result.value { "Восходящий" } else { "Нисходящий" };
format!(
"MESA Adaptive MA: {:.4}, Период: {:.1}, Фаза: {:.3}, Сила цикла: {:.2}, Тренд: {}",
result.value,
result.period,
result.phase,
result.cycle_strength,
trend
)
}
pub fn additional_values(&self) -> std::collections::HashMap<String, f64> {
let mut values = std::collections::HashMap::new();
values.insert("mesa_ma".to_string(), self.current_result.value);
values.insert("adaptive_period".to_string(), self.current_result.period);
values.insert("phase".to_string(), self.current_result.phase);
values.insert("i_component".to_string(), self.current_result.i_component);
values.insert("q_component".to_string(), self.current_result.q_component);
values.insert("cycle_strength".to_string(), self.current_result.cycle_strength);
values
}
pub fn update_count(&self) -> usize {
self.update_count
}
pub fn parameters(&self) -> (f64, f64, MovingAverageType) {
(self.min_period, self.max_period, self.ma_type)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesa_adaptive_ma_creation() {
let mesa = MesaAdaptiveMA::new();
assert!(!mesa.is_ready());
assert_eq!(mesa.parameters().0, 8.0);
assert_eq!(mesa.parameters().1, 50.0);
}
#[test]
fn test_mesa_with_parameters() {
let mesa = MesaAdaptiveMA::with_parameters(10.0, 30.0, MovingAverageType::SMA);
assert_eq!(mesa.parameters(), (10.0, 30.0, MovingAverageType::SMA));
}
#[test]
fn test_mesa_update() {
let mut mesa = MesaAdaptiveMA::new();
for i in 0..250 {
let price = 100.0 + 10.0 * (i as f64 * 0.2).sin();
let result = mesa.update_price(price);
if i > 10 {
assert!(result.period >= mesa.parameters().0);
assert!(result.period <= mesa.parameters().1);
assert!(result.cycle_strength >= 0.0 && result.cycle_strength <= 1.0);
}
}
}
#[test]
fn test_trading_signals() {
let mut mesa = MesaAdaptiveMA::new();
for i in 0..20 {
let price = 100.0 + i as f64;
let _result = mesa.update_price(price);
}
if mesa.is_ready() {
let signal = mesa.trading_signal(120.0, 115.0);
assert!(signal >= -1 && signal <= 1);
}
}
}