nautilus_indicators/average/
vwap.rs1use std::fmt::Display;
17
18use nautilus_model::data::Bar;
19
20use crate::{
21 indicator::Indicator,
22 support::{is_valid_hlc, typical_price},
23};
24
25#[repr(C)]
27#[derive(Debug)]
28#[cfg_attr(
29 feature = "python",
30 pyo3::pyclass(module = "nautilus_trader.indicators")
31)]
32#[cfg_attr(
33 feature = "python",
34 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.indicators")
35)]
36pub struct VolumeWeightedAveragePrice {
37 pub value: f64,
38 pub initialized: bool,
39 has_inputs: bool,
40 price_volume: f64,
41 volume_total: f64,
42}
43
44impl Indicator for VolumeWeightedAveragePrice {
45 fn name(&self) -> String {
46 stringify!(VolumeWeightedAveragePrice).to_string()
47 }
48
49 fn has_inputs(&self) -> bool {
50 self.has_inputs
51 }
52
53 fn initialized(&self) -> bool {
54 self.initialized
55 }
56
57 fn handle_bar(&mut self, bar: &Bar) {
58 let (high, low, close) = (bar.high.as_f64(), bar.low.as_f64(), bar.close.as_f64());
59 if !is_valid_hlc(high, low, close) {
60 return;
61 }
62 self.update_raw(typical_price(high, low, close), bar.volume.as_f64());
63 }
64
65 fn reset(&mut self) {
66 self.value = 0.0;
67 self.has_inputs = false;
68 self.initialized = false;
69 self.price_volume = 0.0;
70 self.volume_total = 0.0;
71 }
72}
73
74impl VolumeWeightedAveragePrice {
75 #[must_use]
77 pub const fn new() -> Self {
78 Self {
79 value: 0.0,
80 initialized: false,
81 has_inputs: false,
82 price_volume: 0.0,
83 volume_total: 0.0,
84 }
85 }
86
87 pub fn update_raw(&mut self, price: f64, volume: f64) {
90 if !price.is_finite() || !volume.is_finite() || volume < 0.0 {
91 return;
92 }
93 self.has_inputs = true;
94
95 if volume == 0.0 {
96 return;
97 }
98 self.price_volume += price * volume;
99 self.volume_total += volume;
100 self.value = self.price_volume / self.volume_total;
101 self.initialized = true;
102 }
103}
104
105impl Default for VolumeWeightedAveragePrice {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111impl Display for VolumeWeightedAveragePrice {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 write!(f, "{}", self.name())
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use nautilus_model::data::Bar;
120 use rstest::rstest;
121
122 use super::*;
123 use crate::stubs::*;
124
125 #[rstest]
126 fn test_manual_window_and_reset() {
127 let mut vwap = VolumeWeightedAveragePrice::new();
128 assert_eq!(vwap.to_string(), "VolumeWeightedAveragePrice");
129 assert_eq!(
130 (vwap.value, vwap.initialized(), vwap.has_inputs()),
131 (0.0, false, false)
132 );
133 vwap.update_raw(99.0, 0.0);
134 assert_eq!(
135 (vwap.value, vwap.initialized(), vwap.has_inputs()),
136 (0.0, false, true)
137 );
138
139 for (price, volume) in [(10.0, 1.0), (20.0, 3.0), (30.0, 6.0)] {
140 vwap.update_raw(price, volume);
141 }
142 assert_eq!(
143 (vwap.value, vwap.initialized(), vwap.has_inputs()),
144 (25.0, true, true)
145 );
146 vwap.update_raw(999.0, 0.0);
147 assert_eq!(vwap.value, 25.0);
148 vwap.reset();
149 assert_eq!(
150 (vwap.value, vwap.initialized(), vwap.has_inputs()),
151 (0.0, false, false)
152 );
153 vwap.update_raw(42.0, 7.0);
154 assert_eq!(vwap.value, 42.0);
155 }
156
157 #[rstest]
158 #[case(f64::NAN, 1.0)]
159 #[case(f64::INFINITY, 1.0)]
160 #[case(2.0, f64::NAN)]
161 #[case(2.0, f64::INFINITY)]
162 #[case(2.0, -1.0)]
163 fn test_invalid_input_is_atomic(#[case] price: f64, #[case] volume: f64) {
164 let mut vwap = VolumeWeightedAveragePrice::new();
165 vwap.update_raw(price, volume);
166 assert_eq!(
167 (vwap.value, vwap.initialized(), vwap.has_inputs()),
168 (0.0, false, false)
169 );
170 vwap.update_raw(10.0, 2.0);
171 vwap.update_raw(price, volume);
172 vwap.update_raw(20.0, 2.0);
173 assert_eq!(vwap.value, 15.0);
174 }
175
176 #[rstest]
177 fn test_bar_uses_typical_price(bar_ethusdt_binance_minute_bid: Bar) {
178 let bar = bar_ethusdt_binance_minute_bid;
179 let mut event = VolumeWeightedAveragePrice::new();
180 let mut raw = VolumeWeightedAveragePrice::new();
181 event.handle_bar(&bar);
182 raw.update_raw(
183 typical_price(bar.high.as_f64(), bar.low.as_f64(), bar.close.as_f64()),
184 bar.volume.as_f64(),
185 );
186 assert_eq!(
187 (event.value, event.initialized(), event.has_inputs()),
188 (raw.value, true, true)
189 );
190 }
191}