use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Clone)]
pub struct AccumulationDistribution {
ad_value: f64,
ad_history: Vec<f64>,
bars_count: usize,
is_ready: bool,
}
impl AccumulationDistribution {
pub fn new() -> Self {
Self {
ad_value: 0.0,
ad_history: Vec::with_capacity(512),
bars_count: 0,
is_ready: false,
}
}
pub fn update_bar(&mut self, _open: f64, high: f64, low: f64, close: f64, volume: f64) -> f64 {
self.bars_count += 1;
let range = high - low;
let money_flow_multiplier = if range.abs() < 1e-12 {
0.0
} else {
((close - low) - (high - close)) / range
};
let money_flow_volume = money_flow_multiplier * volume;
self.ad_value += money_flow_volume;
if self.ad_history.len() >= 512 {
self.ad_history.remove(0);
}
self.ad_history.push(self.ad_value);
if self.bars_count >= 1 {
self.is_ready = true;
}
self.ad_value
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.ad_value)
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn reset(&mut self) {
self.ad_value = 0.0;
self.ad_history.clear();
self.bars_count = 0;
self.is_ready = false;
}
pub fn trend_direction(&self, lookback: usize) -> i8 {
if !self.is_ready() || self.ad_history.len() < lookback + 1 {
return 0;
}
let current = self.ad_value;
let past = self.ad_history[self.ad_history.len() - lookback - 1];
let change = current - past;
let threshold = past.abs() * 0.001;
if change > threshold {
1 } else if change < -threshold {
-1 } else {
0 }
}
pub fn rate_of_change(&self, lookback: usize) -> f64 {
if !self.is_ready() || self.ad_history.len() < lookback + 1 {
return 0.0;
}
let current = self.ad_value;
let past = self.ad_history[self.ad_history.len() - lookback - 1];
if past.abs() < 1e-12 {
return 0.0;
}
((current - past) / past.abs()) * 100.0
}
pub fn check_divergence(&self, price_history: &[f64], lookback: usize) -> i8 {
if !self.is_ready() || price_history.len() < lookback + 1 || self.ad_history.len() < lookback + 1 {
return 0;
}
let current_price = price_history[price_history.len() - 1];
let past_price = price_history[price_history.len() - lookback - 1];
let current_ad = self.ad_value;
let past_ad = self.ad_history[self.ad_history.len() - lookback - 1];
let price_change = current_price - past_price;
let ad_change = current_ad - past_ad;
if price_change < 0.0 && ad_change > 0.0 {
let price_change_pct = (price_change / past_price).abs();
let ad_change_pct = (ad_change / past_ad.abs()).abs();
if price_change_pct > 0.005 && ad_change_pct > 0.005 {
return 1;
}
}
if price_change > 0.0 && ad_change < 0.0 {
let price_change_pct = price_change / past_price;
let ad_change_pct = (ad_change / past_ad.abs()).abs();
if price_change_pct > 0.005 && ad_change_pct > 0.005 {
return -1;
}
}
0
}
pub fn trading_signal(&self, price_history: &[f64]) -> i8 {
if !self.is_ready() {
return 0;
}
let short_trend = self.trend_direction(5);
let long_trend = self.trend_direction(20);
let divergence = self.check_divergence(price_history, 10);
if short_trend == 1 && long_trend == 1 && divergence == 1 {
return 1;
}
if short_trend == -1 && long_trend == -1 && divergence == -1 {
return -1;
}
if short_trend == 1 && long_trend == 1 {
return 1;
} else if short_trend == -1 && long_trend == -1 {
return -1;
}
0
}
pub fn signal_strength(&self) -> f64 {
if !self.is_ready() {
return 0.0;
}
let roc_5 = self.rate_of_change(5).abs();
let roc_10 = self.rate_of_change(10).abs();
let roc_20 = self.rate_of_change(20).abs();
let weighted_roc = (roc_5 * 0.5 + roc_10 * 0.3 + roc_20 * 0.2) / 100.0;
weighted_roc.min(1.0)
}
pub fn market_condition(&self) -> &'static str {
let short_trend = self.trend_direction(5);
let long_trend = self.trend_direction(20);
match (short_trend, long_trend) {
(1, 1) => "Strong Accumulation", (1, 0) | (1, -1) => "Accumulation", (-1, -1) => "Strong Distribution", (-1, 0) | (-1, 1) => "Distribution", _ => "Neutral" }
}
pub fn history(&self, n: usize) -> Vec<f64> {
let len = self.ad_history.len();
if n >= len {
self.ad_history.iter().cloned().collect()
} else {
self.ad_history[len - n..].to_vec()
}
}
pub fn moving_average(&self, period: usize) -> f64 {
if !self.is_ready() || self.ad_history.len() < period {
return self.ad_value;
}
let len = self.ad_history.len();
let start_idx = len - period;
let sum: f64 = self.ad_history[start_idx..].iter().sum();
sum / period as f64
}
pub fn info(&self) -> String {
format!(
"A/D Line: {:.2}, Condition: {}, Strength: {:.3}",
self.ad_value,
self.market_condition(),
self.signal_strength()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_accumulation_distribution_creation() {
let ad = AccumulationDistribution::new();
assert!(!ad.is_ready());
assert_eq!(ad.value().main(), 0.0);
}
#[test]
fn test_accumulation_distribution_warmup() {
let mut ad = AccumulationDistribution::new();
for i in 0..10 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
ad.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
assert!(ad.is_ready());
}
#[test]
fn test_accumulation_distribution_values_finite() {
let mut ad = AccumulationDistribution::new();
for i in 0..30 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
let value = ad.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
assert!(value.is_finite());
}
}
#[test]
fn test_accumulation_distribution_reset() {
let mut ad = AccumulationDistribution::new();
for i in 0..20 {
ad.update_bar(100.0 + i as f64, 105.0, 95.0, 101.0, 1000.0);
}
ad.reset();
assert!(!ad.is_ready());
assert_eq!(ad.value().main(), 0.0);
}
}