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 fn update(&mut self, trade: Trade) -> Option<f64> {
52 self.has_emitted = true;
53 self.cumulative += trade.size * trade.side.sign();
54 Some(self.cumulative)
55 }
56
57 fn reset(&mut self) {
58 self.cumulative = 0.0;
59 self.has_emitted = false;
60 }
61
62 fn warmup_period(&self) -> usize {
63 1
64 }
65
66 fn is_ready(&self) -> bool {
67 self.has_emitted
68 }
69
70 fn name(&self) -> &'static str {
71 "CumulativeVolumeDelta"
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::microstructure::Side;
79 use crate::traits::BatchExt;
80
81 fn trade(size: f64, side: Side, ts: i64) -> Trade {
82 Trade::new(100.0, size, side, ts).unwrap()
83 }
84
85 #[test]
86 fn accessors_and_metadata() {
87 let cvd = CumulativeVolumeDelta::new();
88 assert_eq!(cvd.name(), "CumulativeVolumeDelta");
89 assert_eq!(cvd.warmup_period(), 1);
90 assert!(!cvd.is_ready());
91 }
92
93 #[test]
94 fn accumulates_signed_volume() {
95 let mut cvd = CumulativeVolumeDelta::new();
96 assert_eq!(cvd.update(trade(5.0, Side::Buy, 0)), Some(5.0));
97 assert_eq!(cvd.update(trade(2.0, Side::Sell, 1)), Some(3.0));
98 assert_eq!(cvd.update(trade(4.0, Side::Sell, 2)), Some(-1.0));
99 assert!(cvd.is_ready());
100 }
101
102 #[test]
103 fn batch_equals_streaming() {
104 let trades: Vec<Trade> = (0..20)
105 .map(|i| {
106 let side = if i % 3 == 0 { Side::Sell } else { Side::Buy };
107 trade(1.0 + (i % 4) as f64, side, i)
108 })
109 .collect();
110 let mut a = CumulativeVolumeDelta::new();
111 let mut b = CumulativeVolumeDelta::new();
112 assert_eq!(
113 a.batch(&trades),
114 trades.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
115 );
116 }
117
118 #[test]
119 fn reset_re_anchors_at_zero() {
120 let mut cvd = CumulativeVolumeDelta::new();
121 cvd.update(trade(5.0, Side::Buy, 0));
122 cvd.reset();
123 assert!(!cvd.is_ready());
124 assert_eq!(cvd.update(trade(2.0, Side::Buy, 1)), Some(2.0));
126 }
127}