nautilus_indicators/average/
vwap.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::fmt::{Display, Formatter};
17
18use nautilus_model::data::Bar;
19
20use crate::indicator::Indicator;
21
22#[repr(C)]
23#[derive(Debug, Default)]
24#[cfg_attr(
25    feature = "python",
26    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators")
27)]
28pub struct VolumeWeightedAveragePrice {
29    pub value: f64,
30    pub initialized: bool,
31    has_inputs: bool,
32    price_volume: f64,
33    volume_total: f64,
34    day: f64,
35}
36
37impl Display for VolumeWeightedAveragePrice {
38    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
39        write!(f, "{}", self.name())
40    }
41}
42
43impl Indicator for VolumeWeightedAveragePrice {
44    fn name(&self) -> String {
45        stringify!(VolumeWeightedAveragePrice).to_string()
46    }
47
48    fn has_inputs(&self) -> bool {
49        self.has_inputs
50    }
51
52    fn initialized(&self) -> bool {
53        self.initialized
54    }
55
56    fn handle_bar(&mut self, bar: &Bar) {
57        let typical_price = (bar.close.as_f64() + bar.high.as_f64() + bar.low.as_f64()) / 3.0;
58
59        self.update_raw(typical_price, (&bar.volume).into(), bar.ts_init.as_f64());
60    }
61
62    fn reset(&mut self) {
63        self.value = 0.0;
64        self.has_inputs = false;
65        self.initialized = false;
66        self.day = 0.0;
67        self.price_volume = 0.0;
68        self.volume_total = 0.0;
69        self.value = 0.0;
70    }
71}
72
73impl VolumeWeightedAveragePrice {
74    /// Creates a new [`VolumeWeightedAveragePrice`] instance.
75    #[must_use]
76    pub const fn new() -> Self {
77        Self {
78            value: 0.0,
79            has_inputs: false,
80            initialized: false,
81            price_volume: 0.0,
82            volume_total: 0.0,
83            day: 0.0,
84        }
85    }
86
87    pub fn update_raw(&mut self, price: f64, volume: f64, timestamp: f64) {
88        if timestamp != self.day {
89            self.reset();
90            self.day = timestamp;
91            self.value = price;
92        }
93
94        if !self.initialized {
95            self.has_inputs = true;
96            self.initialized = true;
97        }
98
99        if volume == 0.0 {
100            return;
101        }
102
103        self.price_volume += price * volume;
104        self.volume_total += volume;
105        self.value = self.price_volume / self.volume_total;
106    }
107}
108
109////////////////////////////////////////////////////////////////////////////////
110// Tests
111////////////////////////////////////////////////////////////////////////////////
112#[cfg(test)]
113mod tests {
114    use nautilus_model::data::Bar;
115    use rstest::rstest;
116
117    use crate::{average::vwap::VolumeWeightedAveragePrice, indicator::Indicator, stubs::*};
118
119    #[rstest]
120    fn test_vwap_initialized(indicator_vwap: VolumeWeightedAveragePrice) {
121        let display_st = format!("{indicator_vwap}");
122        assert_eq!(display_st, "VolumeWeightedAveragePrice");
123        assert!(!indicator_vwap.initialized());
124        assert!(!indicator_vwap.has_inputs());
125    }
126
127    #[rstest]
128    fn test_value_with_one_input(mut indicator_vwap: VolumeWeightedAveragePrice) {
129        indicator_vwap.update_raw(10.0, 10.0, 10.0);
130        assert_eq!(indicator_vwap.value, 10.0);
131    }
132
133    #[rstest]
134    fn test_value_with_three_inputs_on_the_same_day(
135        mut indicator_vwap: VolumeWeightedAveragePrice,
136    ) {
137        indicator_vwap.update_raw(10.0, 10.0, 10.0);
138        indicator_vwap.update_raw(20.0, 20.0, 10.0);
139        indicator_vwap.update_raw(30.0, 30.0, 10.0);
140        assert_eq!(indicator_vwap.value, 23.333_333_333_333_332);
141    }
142
143    #[rstest]
144    fn test_value_with_three_inputs_on_different_days(
145        mut indicator_vwap: VolumeWeightedAveragePrice,
146    ) {
147        indicator_vwap.update_raw(10.0, 10.0, 10.0);
148        indicator_vwap.update_raw(20.0, 20.0, 20.0);
149        indicator_vwap.update_raw(30.0, 30.0, 10.0);
150        assert_eq!(indicator_vwap.value, 30.0);
151    }
152
153    #[rstest]
154    fn test_value_with_ten_inputs(mut indicator_vwap: VolumeWeightedAveragePrice) {
155        indicator_vwap.update_raw(1.00000, 1.00000, 10.0);
156        indicator_vwap.update_raw(1.00010, 2.00000, 10.0);
157        indicator_vwap.update_raw(1.00020, 3.00000, 10.0);
158        indicator_vwap.update_raw(1.00030, 1.00000, 10.0);
159        indicator_vwap.update_raw(1.00040, 2.00000, 10.0);
160        indicator_vwap.update_raw(1.00050, 3.00000, 10.0);
161        indicator_vwap.update_raw(1.00040, 1.00000, 10.0);
162        indicator_vwap.update_raw(1.00030, 2.00000, 10.0);
163        indicator_vwap.update_raw(1.00020, 3.00000, 10.0);
164        indicator_vwap.update_raw(1.00010, 1.00000, 10.0);
165        indicator_vwap.update_raw(1.00000, 2.00000, 10.0);
166        assert_eq!(indicator_vwap.value, 1.000_242_857_142_857);
167    }
168
169    #[rstest]
170    fn test_handle_bar(
171        mut indicator_vwap: VolumeWeightedAveragePrice,
172        bar_ethusdt_binance_minute_bid: Bar,
173    ) {
174        indicator_vwap.handle_bar(&bar_ethusdt_binance_minute_bid);
175        assert_eq!(indicator_vwap.value, 1522.333333333333);
176        assert!(indicator_vwap.initialized);
177    }
178
179    #[rstest]
180    fn test_reset(mut indicator_vwap: VolumeWeightedAveragePrice) {
181        indicator_vwap.update_raw(10.0, 10.0, 10.0);
182        assert_eq!(indicator_vwap.value, 10.0);
183        indicator_vwap.reset();
184        assert_eq!(indicator_vwap.value, 0.0);
185        assert!(!indicator_vwap.has_inputs);
186        assert!(!indicator_vwap.initialized);
187    }
188}