nautilus_binance/common/
bar.rs1use std::{collections::HashMap, sync::Arc};
17
18use nautilus_core::UnixNanos;
19use nautilus_model::{
20 data::{HasTsInit, bar::BarType, custom::CustomDataTrait},
21 types::{Price, Quantity},
22};
23use rust_decimal::Decimal;
24use serde::{Deserialize, Serialize};
25
26#[cfg_attr(
31 feature = "python",
32 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
33)]
34#[cfg_attr(
35 feature = "python",
36 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.binance")
37)]
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
39pub struct BinanceBar {
40 pub bar_type: BarType,
42 pub open: Price,
44 pub high: Price,
46 pub low: Price,
48 pub close: Price,
50 pub volume: Quantity,
52 pub quote_volume: Decimal,
54 pub count: u64,
56 pub taker_buy_base_volume: Decimal,
58 pub taker_buy_quote_volume: Decimal,
60 pub ts_event: UnixNanos,
62 pub ts_init: UnixNanos,
64}
65
66impl BinanceBar {
67 #[expect(clippy::too_many_arguments)]
69 #[must_use]
70 pub fn new(
71 bar_type: BarType,
72 open: Price,
73 high: Price,
74 low: Price,
75 close: Price,
76 volume: Quantity,
77 quote_volume: Decimal,
78 count: u64,
79 taker_buy_base_volume: Decimal,
80 taker_buy_quote_volume: Decimal,
81 ts_event: UnixNanos,
82 ts_init: UnixNanos,
83 ) -> Self {
84 Self {
85 bar_type,
86 open,
87 high,
88 low,
89 close,
90 volume,
91 quote_volume,
92 count,
93 taker_buy_base_volume,
94 taker_buy_quote_volume,
95 ts_event,
96 ts_init,
97 }
98 }
99
100 #[must_use]
102 pub fn get_metadata(bar_type: &BarType) -> HashMap<String, String> {
103 let mut metadata = HashMap::new();
104 metadata.insert("bar_type".to_string(), bar_type.to_string());
105 metadata.insert(
106 "instrument_id".to_string(),
107 bar_type.instrument_id().to_string(),
108 );
109 metadata
110 }
111
112 #[must_use]
114 pub fn taker_sell_base_volume(&self) -> Decimal {
115 Decimal::from(self.volume.raw) / Decimal::new(10i64.pow(self.volume.precision.into()), 0)
116 - self.taker_buy_base_volume
117 }
118
119 #[must_use]
121 pub fn taker_sell_quote_volume(&self) -> Decimal {
122 self.quote_volume - self.taker_buy_quote_volume
123 }
124}
125
126impl HasTsInit for BinanceBar {
127 fn ts_init(&self) -> UnixNanos {
128 self.ts_init
129 }
130}
131
132impl CustomDataTrait for BinanceBar {
133 fn type_name(&self) -> &'static str {
134 "BinanceBar"
135 }
136
137 fn as_any(&self) -> &dyn std::any::Any {
138 self
139 }
140
141 fn ts_event(&self) -> UnixNanos {
142 self.ts_event
143 }
144
145 fn to_json(&self) -> anyhow::Result<String> {
146 Ok(serde_json::to_string(self)?)
147 }
148
149 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
150 Arc::new(self.clone())
151 }
152
153 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
154 if let Some(o) = other.as_any().downcast_ref::<Self>() {
155 self == o
156 } else {
157 false
158 }
159 }
160
161 #[cfg(feature = "python")]
162 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
163 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
164 }
165
166 fn type_name_static() -> &'static str {
167 "BinanceBar"
168 }
169
170 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
171 let json_str = serde_json::to_string(&value)?;
175 let parsed: Self = serde_json::from_str(&json_str)?;
176 Ok(Arc::new(parsed))
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use rstest::rstest;
183 use rust_decimal_macros::dec;
184
185 use super::*;
186
187 fn stub_binance_bar() -> BinanceBar {
188 BinanceBar::new(
189 BarType::from("BTCUSDT.BINANCE-1-MINUTE-LAST-EXTERNAL"),
190 Price::from("0.01634790"),
191 Price::from("0.01640000"),
192 Price::from("0.01575800"),
193 Price::from("0.01577100"),
194 Quantity::from("148976.11427815"),
195 dec!(2434.19055334),
196 100,
197 dec!(1756.87402397),
198 dec!(28.46694368),
199 UnixNanos::from(1_650_000_000_000_000_000u64),
200 UnixNanos::from(1_650_000_000_000_000_000u64),
201 )
202 }
203
204 #[rstest]
205 fn test_type_name() {
206 let bar = stub_binance_bar();
207 assert_eq!(bar.type_name(), "BinanceBar");
208 assert_eq!(BinanceBar::type_name_static(), "BinanceBar");
209 }
210
211 #[rstest]
212 fn test_taker_sell_quote_volume() {
213 let bar = stub_binance_bar();
214 assert_eq!(bar.taker_sell_quote_volume(), dec!(2405.72360966));
215 }
216
217 #[rstest]
218 fn test_json_round_trip() {
219 let bar = stub_binance_bar();
220 let json = bar.to_json().unwrap();
221 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
222 let restored = BinanceBar::from_json(value).unwrap();
223 let restored_bar = restored.as_any().downcast_ref::<BinanceBar>().unwrap();
224 assert_eq!(restored_bar, &bar);
225 }
226}