use crate::bar_indicators::average::ema::Ema;
use crate::bar_indicators::indicator_value::IndicatorValue;
use super::super::ohlcv_field::OhlcvField;
#[derive(Debug, Clone, Copy)]
pub struct FractalAdaptiveResult {
pub fama: f64, pub fractal_dimension: f64, pub efficiency_ratio: f64, pub adaptive_period: f64, pub trend_strength: f64, }
impl FractalAdaptiveResult {
pub fn empty() -> Self {
Self {
fama: 0.0,
fractal_dimension: 0.0,
efficiency_ratio: 0.0,
adaptive_period: 0.0,
trend_strength: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct EhlersFractalAdaptiveMa {
adaptive_ema: Ema, base_ema: Ema,
period: usize,
source: OhlcvField, min_period: usize,
max_period: usize,
prices: Vec<f64>,
last_result: FractalAdaptiveResult,
initialized: bool,
}
impl EhlersFractalAdaptiveMa {
pub fn new(period: usize) -> Self {
Self::with_source(period, OhlcvField::HLC3)
}
pub fn with_source(period: usize, source: OhlcvField) -> Self {
let min_period = (period / 4).max(2);
let max_period = period * 2;
Self {
adaptive_ema: Ema::with_source(period, source),
base_ema: Ema::with_source(period, source),
period,
source,
min_period,
max_period,
prices: Vec::with_capacity(period),
last_result: FractalAdaptiveResult::empty(),
initialized: false,
}
}
pub fn period(&self) -> usize {
self.period
}
pub fn is_initialized(&self) -> bool {
self.initialized && self.prices.len() >= self.period
}
pub fn update(&mut self, price: f64) -> f64 {
self.update_bar(price, price, price, price, 1.0)
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, volume: f64) -> f64 {
let price = self.source.extract(open, high, low, close, volume);
if self.prices.len() >= 512 {
self.prices.remove(0);
}
self.prices.push(price);
let _base_value = self.base_ema.update_bar(open, high, low, close, volume);
if self.prices.len() < self.period {
let adaptive_value = self.adaptive_ema.update_bar(open, high, low, close, volume);
return adaptive_value;
}
let fractal_dim = self.calculate_fractal_dimension();
let efficiency = self.calculate_efficiency_ratio();
let adaptive_period = self.calculate_adaptive_period(fractal_dim, efficiency);
let new_period = adaptive_period.round() as usize;
if new_period != self.adaptive_ema.period() {
self.adaptive_ema = Ema::with_source(new_period, self.source);
}
let adaptive_value = self.adaptive_ema.update_bar(open, high, low, close, volume);
self.last_result = FractalAdaptiveResult {
fama: adaptive_value,
fractal_dimension: fractal_dim,
efficiency_ratio: efficiency,
adaptive_period,
trend_strength: efficiency * (2.0 - fractal_dim), };
self.initialized = true;
adaptive_value
}
fn calculate_fractal_dimension(&self) -> f64 {
if self.prices.len() < self.period {
return 1.5; }
let mut total_length = 0.0;
let start_idx = self.prices.len() - self.period;
for i in 1..self.period {
let prev = self.prices[start_idx + i - 1];
let curr = self.prices[start_idx + i];
total_length += (curr - prev).abs();
}
let direct_distance = (self.prices[start_idx + self.period - 1] - self.prices[start_idx]).abs();
if direct_distance == 0.0 || total_length == 0.0 {
return 1.5;
}
let fractal_dim = (total_length / direct_distance).ln() / (self.period as f64).ln();
fractal_dim.clamp(1.0, 2.0)
}
fn calculate_efficiency_ratio(&self) -> f64 {
if self.prices.len() < self.period {
return 0.0;
}
let start_idx = self.prices.len() - self.period;
let direction = (self.prices[start_idx + self.period - 1] - self.prices[start_idx]).abs();
let mut volatility = 0.0;
for i in 1..self.period {
volatility += (self.prices[start_idx + i] - self.prices[start_idx + i - 1]).abs();
}
if volatility == 0.0 {
return 0.0;
}
direction / volatility
}
fn calculate_adaptive_period(&self, fractal_dim: f64, efficiency: f64) -> f64 {
let complexity_factor = fractal_dim; let efficiency_factor = efficiency;
let adaptation_speed = efficiency_factor / complexity_factor;
let adaptive_period = self.max_period as f64 -
adaptation_speed * (self.max_period - self.min_period) as f64;
adaptive_period.max(self.min_period as f64).min(self.max_period as f64)
}
pub fn result(&self) -> FractalAdaptiveResult {
self.last_result
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.last_result.fama)
}
#[inline]
pub fn is_ready(&self) -> bool {
self.prices.len() >= self.period
}
pub fn reset(&mut self) {
self.prices.clear();
self.adaptive_ema = Ema::new(self.period);
self.base_ema = Ema::new(self.period);
self.last_result = FractalAdaptiveResult::empty();
self.initialized = false;
}
}