nautilus_serialization/arrow/display/
quote.rs1use std::sync::Arc;
19
20use arrow::{
21 array::{Float64Builder, StringBuilder, TimestampNanosecondBuilder},
22 datatypes::Schema,
23 error::ArrowError,
24 record_batch::RecordBatch,
25};
26use nautilus_model::data::QuoteTick;
27
28use super::{
29 float64_field, price_to_f64, quantity_to_f64, timestamp_field, unix_nanos_to_i64, utf8_field,
30};
31use crate::arrow::timestamp_data_type;
32
33#[must_use]
35pub fn quotes_schema() -> Schema {
36 Schema::new(vec![
37 utf8_field("instrument_id", false),
38 float64_field("bid_price", false),
39 float64_field("ask_price", false),
40 float64_field("bid_size", false),
41 float64_field("ask_size", false),
42 timestamp_field("ts_event", false),
43 timestamp_field("ts_init", false),
44 ])
45}
46
47pub fn encode_quotes(data: &[QuoteTick]) -> Result<RecordBatch, ArrowError> {
61 let mut instrument_id_builder = StringBuilder::new();
62 let mut bid_price_builder = Float64Builder::with_capacity(data.len());
63 let mut ask_price_builder = Float64Builder::with_capacity(data.len());
64 let mut bid_size_builder = Float64Builder::with_capacity(data.len());
65 let mut ask_size_builder = Float64Builder::with_capacity(data.len());
66 let mut ts_event_builder =
67 TimestampNanosecondBuilder::with_capacity(data.len()).with_data_type(timestamp_data_type());
68 let mut ts_init_builder =
69 TimestampNanosecondBuilder::with_capacity(data.len()).with_data_type(timestamp_data_type());
70
71 for quote in data {
72 instrument_id_builder.append_value(quote.instrument_id.to_string());
73 bid_price_builder.append_value(price_to_f64("e.bid_price));
74 ask_price_builder.append_value(price_to_f64("e.ask_price));
75 bid_size_builder.append_value(quantity_to_f64("e.bid_size));
76 ask_size_builder.append_value(quantity_to_f64("e.ask_size));
77 ts_event_builder.append_value(unix_nanos_to_i64(quote.ts_event.as_u64()));
78 ts_init_builder.append_value(unix_nanos_to_i64(quote.ts_init.as_u64()));
79 }
80
81 RecordBatch::try_new(
82 Arc::new(quotes_schema()),
83 vec![
84 Arc::new(instrument_id_builder.finish()),
85 Arc::new(bid_price_builder.finish()),
86 Arc::new(ask_price_builder.finish()),
87 Arc::new(bid_size_builder.finish()),
88 Arc::new(ask_size_builder.finish()),
89 Arc::new(ts_event_builder.finish()),
90 Arc::new(ts_init_builder.finish()),
91 ],
92 )
93}
94
95#[cfg(test)]
96mod tests {
97 use arrow::{
98 array::{Array, Float64Array, StringArray, TimestampNanosecondArray},
99 datatypes::{DataType, TimeUnit},
100 };
101 use nautilus_model::{
102 identifiers::InstrumentId,
103 types::{Price, Quantity},
104 };
105 use rstest::rstest;
106
107 use super::*;
108
109 fn make_quote(instrument_id: &str, bid: &str, ask: &str, ts: u64) -> QuoteTick {
110 QuoteTick {
111 instrument_id: InstrumentId::from(instrument_id),
112 bid_price: Price::from(bid),
113 ask_price: Price::from(ask),
114 bid_size: Quantity::from(1_000),
115 ask_size: Quantity::from(500),
116 ts_event: ts.into(),
117 ts_init: (ts + 1).into(),
118 }
119 }
120
121 #[rstest]
122 fn test_encode_quotes_schema() {
123 let quotes = vec![make_quote("AAPL.XNAS", "100.10", "100.20", 1)];
124 let batch = encode_quotes("es).unwrap();
125
126 let schema = batch.schema();
127 let fields = schema.fields();
128 assert_eq!(fields.len(), 7);
129 assert_eq!(fields[0].name(), "instrument_id");
130 assert_eq!(fields[0].data_type(), &DataType::Utf8);
131 assert_eq!(fields[1].name(), "bid_price");
132 assert_eq!(fields[1].data_type(), &DataType::Float64);
133 assert_eq!(fields[2].name(), "ask_price");
134 assert_eq!(fields[2].data_type(), &DataType::Float64);
135 assert_eq!(fields[3].name(), "bid_size");
136 assert_eq!(fields[3].data_type(), &DataType::Float64);
137 assert_eq!(fields[4].name(), "ask_size");
138 assert_eq!(fields[4].data_type(), &DataType::Float64);
139 assert_eq!(fields[5].name(), "ts_event");
140 assert_eq!(
141 fields[5].data_type(),
142 &DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into()))
143 );
144 assert_eq!(fields[6].name(), "ts_init");
145 assert_eq!(
146 fields[6].data_type(),
147 &DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into()))
148 );
149 }
150
151 #[rstest]
152 fn test_encode_quotes_values() {
153 let quotes = vec![
154 make_quote("AAPL.XNAS", "100.10", "100.20", 1_000_000_000),
155 make_quote("AAPL.XNAS", "100.15", "100.25", 2_000_000_000),
156 ];
157 let batch = encode_quotes("es).unwrap();
158
159 assert_eq!(batch.num_rows(), 2);
160
161 let instrument_id_col = batch
162 .column(0)
163 .as_any()
164 .downcast_ref::<StringArray>()
165 .unwrap();
166 let bid_price_col = batch
167 .column(1)
168 .as_any()
169 .downcast_ref::<Float64Array>()
170 .unwrap();
171 let ask_price_col = batch
172 .column(2)
173 .as_any()
174 .downcast_ref::<Float64Array>()
175 .unwrap();
176 let bid_size_col = batch
177 .column(3)
178 .as_any()
179 .downcast_ref::<Float64Array>()
180 .unwrap();
181 let ask_size_col = batch
182 .column(4)
183 .as_any()
184 .downcast_ref::<Float64Array>()
185 .unwrap();
186 let ts_event_col = batch
187 .column(5)
188 .as_any()
189 .downcast_ref::<TimestampNanosecondArray>()
190 .unwrap();
191 let ts_init_col = batch
192 .column(6)
193 .as_any()
194 .downcast_ref::<TimestampNanosecondArray>()
195 .unwrap();
196
197 assert_eq!(instrument_id_col.value(0), "AAPL.XNAS");
198 assert_eq!(instrument_id_col.value(1), "AAPL.XNAS");
199 assert!((bid_price_col.value(0) - 100.10).abs() < 1e-9);
200 assert!((bid_price_col.value(1) - 100.15).abs() < 1e-9);
201 assert!((ask_price_col.value(0) - 100.20).abs() < 1e-9);
202 assert!((ask_price_col.value(1) - 100.25).abs() < 1e-9);
203 assert!((bid_size_col.value(0) - 1_000.0).abs() < 1e-9);
204 assert!((ask_size_col.value(0) - 500.0).abs() < 1e-9);
205 assert_eq!(ts_event_col.value(0), 1_000_000_000);
206 assert_eq!(ts_event_col.value(1), 2_000_000_000);
207 assert_eq!(ts_init_col.value(0), 1_000_000_001);
208 assert_eq!(ts_init_col.value(1), 2_000_000_001);
209 }
210
211 #[rstest]
212 fn test_encode_quotes_empty() {
213 let batch = encode_quotes(&[]).unwrap();
214 assert_eq!(batch.num_rows(), 0);
215 assert_eq!(batch.schema().fields().len(), 7);
216 }
217
218 #[rstest]
219 fn test_encode_quotes_mixed_instruments() {
220 let quotes = vec![
221 make_quote("AAPL.XNAS", "100.10", "100.20", 1),
222 make_quote("MSFT.XNAS", "250.00", "250.05", 2),
223 ];
224 let batch = encode_quotes("es).unwrap();
225
226 let instrument_id_col = batch
227 .column(0)
228 .as_any()
229 .downcast_ref::<StringArray>()
230 .unwrap();
231 assert_eq!(instrument_id_col.value(0), "AAPL.XNAS");
232 assert_eq!(instrument_id_col.value(1), "MSFT.XNAS");
233 }
234}