wickra_core/indicators/
anchored_vwap.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
43pub struct AnchoredVwap {
44 sum_pv: f64,
45 sum_v: f64,
46 has_emitted: bool,
47 pending_anchor: bool,
48}
49
50impl AnchoredVwap {
51 pub const fn new() -> Self {
53 Self {
54 sum_pv: 0.0,
55 sum_v: 0.0,
56 has_emitted: false,
57 pending_anchor: false,
58 }
59 }
60
61 pub fn set_anchor(&mut self) {
65 self.pending_anchor = true;
66 }
67
68 pub fn value(&self) -> Option<f64> {
71 if self.sum_v == 0.0 {
72 None
73 } else {
74 Some(self.sum_pv / self.sum_v)
75 }
76 }
77}
78
79impl Indicator for AnchoredVwap {
80 type Input = Candle;
81 type Output = f64;
82
83 fn update(&mut self, candle: Candle) -> Option<f64> {
84 if self.pending_anchor {
85 self.sum_pv = 0.0;
87 self.sum_v = 0.0;
88 self.has_emitted = false;
89 self.pending_anchor = false;
90 }
91 let tp = candle.typical_price();
92 self.sum_pv += tp * candle.volume;
93 self.sum_v += candle.volume;
94 if self.sum_v == 0.0 {
95 return None;
96 }
97 self.has_emitted = true;
98 Some(self.sum_pv / self.sum_v)
99 }
100
101 fn reset(&mut self) {
102 self.sum_pv = 0.0;
103 self.sum_v = 0.0;
104 self.has_emitted = false;
105 self.pending_anchor = false;
106 }
107
108 fn warmup_period(&self) -> usize {
109 1
110 }
111
112 fn is_ready(&self) -> bool {
113 self.has_emitted
114 }
115
116 fn name(&self) -> &'static str {
117 "AnchoredVWAP"
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::traits::BatchExt;
125 use approx::assert_relative_eq;
126
127 fn c(price: f64, volume: f64, ts: i64) -> Candle {
128 Candle::new(price, price, price, price, volume, ts).unwrap()
129 }
130
131 #[test]
132 fn accessors_and_metadata() {
133 let v = AnchoredVwap::new();
134 assert_eq!(v.name(), "AnchoredVWAP");
135 assert_eq!(v.warmup_period(), 1);
136 assert_eq!(v.value(), None);
137 }
138
139 #[test]
140 fn first_bar_with_zero_volume_returns_none() {
141 let mut v = AnchoredVwap::new();
142 assert_eq!(v.update(c(50.0, 0.0, 0)), None);
143 assert!(!v.is_ready());
144 assert_relative_eq!(v.update(c(10.0, 4.0, 1)).unwrap(), 10.0, epsilon = 1e-12);
146 }
147
148 #[test]
149 fn equal_volumes_yield_mean_typical_price() {
150 let mut v = AnchoredVwap::new();
152 let out = v.batch(&[c(10.0, 1.0, 0), c(20.0, 1.0, 1), c(30.0, 1.0, 2)]);
153 assert_relative_eq!(out[2].unwrap(), 20.0, epsilon = 1e-12);
154 }
155
156 #[test]
157 fn set_anchor_clears_old_window() {
158 let mut v = AnchoredVwap::new();
161 v.batch(&[c(10.0, 1.0, 0), c(10.0, 1.0, 1), c(10.0, 1.0, 2)]);
162 assert_relative_eq!(v.value().unwrap(), 10.0, epsilon = 1e-12);
163 v.set_anchor();
164 let after = v.update(c(100.0, 5.0, 3)).unwrap();
165 assert_relative_eq!(after, 100.0, epsilon = 1e-12);
166 }
167
168 #[test]
169 fn set_anchor_before_first_bar_acts_as_normal_first_bar() {
170 let mut v = AnchoredVwap::new();
173 v.set_anchor();
174 assert_relative_eq!(v.update(c(42.0, 2.0, 0)).unwrap(), 42.0, epsilon = 1e-12);
175 }
176
177 #[test]
178 fn weighted_average_reference() {
179 let mut v = AnchoredVwap::new();
181 let out = v.batch(&[c(10.0, 1.0, 0), c(20.0, 3.0, 1)]);
182 assert_relative_eq!(out[1].unwrap(), 17.5, epsilon = 1e-12);
183 }
184
185 #[test]
186 fn batch_equals_streaming() {
187 let candles: Vec<Candle> = (1..30).map(|i| c(f64::from(i), 1.0, i.into())).collect();
188 let mut a = AnchoredVwap::new();
189 let mut b = AnchoredVwap::new();
190 assert_eq!(
191 a.batch(&candles),
192 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
193 );
194 }
195
196 #[test]
197 fn reset_clears_state() {
198 let mut v = AnchoredVwap::new();
199 v.batch(&[c(10.0, 1.0, 0), c(20.0, 1.0, 1)]);
200 assert!(v.is_ready());
201 v.reset();
202 assert!(!v.is_ready());
203 assert_eq!(v.value(), None);
204 assert_relative_eq!(v.update(c(50.0, 1.0, 2)).unwrap(), 50.0, epsilon = 1e-12);
206 }
207}