1use crate::types::Candle;
7use chrono::{DateTime, NaiveDate, TimeZone, Utc};
8use std::collections::VecDeque;
9
10pub struct CVDTracker {
11 slope_bars: usize,
12 div_lookback: usize,
13
14 day_cvd: f64,
15 last_date: Option<NaiveDate>,
16 cvd_hist: VecDeque<f64>,
17 price_hist: VecDeque<f64>,
18
19 pub cvd: f64,
20 pub delta: f64,
21 pub cvd_slope: f64,
22 pub bullish: bool,
23 pub divergence: i8,
25}
26
27impl CVDTracker {
28 pub fn new(slope_bars: usize, div_lookback: usize) -> Self {
29 let cap = (div_lookback + 10).max(50);
30 Self {
31 slope_bars,
32 div_lookback,
33 day_cvd: 0.0,
34 last_date: None,
35 cvd_hist: VecDeque::with_capacity(cap),
36 price_hist: VecDeque::with_capacity(cap),
37 cvd: 0.0,
38 delta: 0.0,
39 cvd_slope: 0.0,
40 bullish: false,
41 divergence: 0,
42 }
43 }
44
45 pub fn update(&mut self, candle: &Candle) {
46 let dt: DateTime<Utc> = Utc
47 .timestamp_millis_opt(candle.time)
48 .single()
49 .unwrap_or_else(Utc::now);
50 let date = dt.date_naive();
51
52 if Some(date) != self.last_date {
53 self.day_cvd = 0.0;
54 self.last_date = Some(date);
55 }
56
57 let bar_rng = candle.high - candle.low;
58 let buy_vol = if bar_rng > 0.0 {
59 candle.volume * (candle.close - candle.low) / bar_rng
60 } else {
61 candle.volume * 0.5
62 };
63 self.delta = buy_vol - (candle.volume - buy_vol);
64 self.day_cvd += self.delta;
65 self.cvd = self.day_cvd;
66
67 let cap = self.cvd_hist.capacity();
68 if self.cvd_hist.len() == cap {
69 self.cvd_hist.pop_front();
70 }
71 if self.price_hist.len() == cap {
72 self.price_hist.pop_front();
73 }
74 self.cvd_hist.push_back(self.cvd);
75 self.price_hist.push_back(candle.close);
76
77 if self.cvd_hist.len() >= self.slope_bars {
78 let arr: Vec<f64> = self.cvd_hist.iter().copied().collect();
79 self.cvd_slope = arr[arr.len() - 1] - arr[arr.len() - self.slope_bars];
80 }
81 self.bullish = self.cvd_slope > 0.0;
82 self.divergence = self.check_divergence();
83 }
84
85 fn check_divergence(&self) -> i8 {
86 let n = self.cvd_hist.len().min(self.div_lookback);
87 if n < 10 {
88 return 0;
89 }
90 let prices: Vec<f64> = self.price_hist.iter().rev().take(n).copied().collect();
91 let cvds: Vec<f64> = self.cvd_hist.iter().rev().take(n).copied().collect();
92
93 let last_p = prices[0];
94 let last_c = cvds[0];
95
96 let min_p = prices[1..].iter().copied().fold(f64::INFINITY, f64::min);
98 let min_c = cvds[1..].iter().copied().fold(f64::INFINITY, f64::min);
99 if last_p < min_p && last_c > min_c {
100 return 1;
101 }
102
103 let max_p = prices[1..]
105 .iter()
106 .copied()
107 .fold(f64::NEG_INFINITY, f64::max);
108 let max_c = cvds[1..].iter().copied().fold(f64::NEG_INFINITY, f64::max);
109 if last_p > max_p && last_c < max_c {
110 return -1;
111 }
112
113 0
114 }
115}