wickra_core/indicators/
cvd.rs1use crate::microstructure::Trade;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
32pub struct CumulativeVolumeDelta {
33 cumulative: f64,
34 has_emitted: bool,
35}
36
37impl CumulativeVolumeDelta {
38 pub const fn new() -> Self {
40 Self {
41 cumulative: 0.0,
42 has_emitted: false,
43 }
44 }
45}
46
47impl Indicator for CumulativeVolumeDelta {
48 type Input = Trade;
49 type Output = f64;
50
51 #[inline]
52 fn update(&mut self, trade: Trade) -> Option<f64> {
53 self.has_emitted = true;
54 self.cumulative += trade.size * trade.side.sign();
55 Some(self.cumulative)
56 }
57
58 fn reset(&mut self) {
59 self.cumulative = 0.0;
60 self.has_emitted = false;
61 }
62
63 #[inline]
64 fn warmup_period(&self) -> usize {
65 1
66 }
67
68 #[inline]
69 fn is_ready(&self) -> bool {
70 self.has_emitted
71 }
72
73 #[inline]
74 fn name(&self) -> &'static str {
75 "CumulativeVolumeDelta"
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::microstructure::Side;
83 use crate::traits::BatchExt;
84
85 fn trade(size: f64, side: Side, ts: i64) -> Trade {
86 Trade::new(100.0, size, side, ts).unwrap()
87 }
88
89 #[test]
90 fn accessors_and_metadata() {
91 let cvd = CumulativeVolumeDelta::new();
92 assert_eq!(cvd.name(), "CumulativeVolumeDelta");
93 assert_eq!(cvd.warmup_period(), 1);
94 assert!(!cvd.is_ready());
95 }
96
97 #[test]
98 fn accumulates_signed_volume() {
99 let mut cvd = CumulativeVolumeDelta::new();
100 assert_eq!(cvd.update(trade(5.0, Side::Buy, 0)), Some(5.0));
101 assert_eq!(cvd.update(trade(2.0, Side::Sell, 1)), Some(3.0));
102 assert_eq!(cvd.update(trade(4.0, Side::Sell, 2)), Some(-1.0));
103 assert!(cvd.is_ready());
104 }
105
106 #[test]
107 fn batch_equals_streaming() {
108 let trades: Vec<Trade> = (0..20)
109 .map(|i| {
110 let side = if i % 3 == 0 { Side::Sell } else { Side::Buy };
111 trade(1.0 + (i % 4) as f64, side, i)
112 })
113 .collect();
114 let mut a = CumulativeVolumeDelta::new();
115 let mut b = CumulativeVolumeDelta::new();
116 assert_eq!(
117 a.batch(&trades),
118 trades.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
119 );
120 }
121
122 #[test]
123 fn reset_re_anchors_at_zero() {
124 let mut cvd = CumulativeVolumeDelta::new();
125 cvd.update(trade(5.0, Side::Buy, 0));
126 cvd.reset();
127 assert!(!cvd.is_ready());
128 assert_eq!(cvd.update(trade(2.0, Side::Buy, 1)), Some(2.0));
130 }
131}