1use std::{collections::HashMap, str::FromStr, sync::Arc};
17
18use arrow::{
19 array::{FixedSizeBinaryArray, FixedSizeBinaryBuilder, UInt64Array},
20 datatypes::{DataType, Field, Schema},
21 error::ArrowError,
22 record_batch::RecordBatch,
23};
24use nautilus_model::{data::QuoteTick, identifiers::InstrumentId, types::fixed::PRECISION_BYTES};
25
26use super::{
27 DecodeDataFromRecordBatch, EncodingError, KEY_INSTRUMENT_ID, KEY_PRICE_PRECISION,
28 KEY_SIZE_PRECISION, decode_price, decode_quantity, extract_column, validate_precision_bytes,
29};
30use crate::arrow::{ArrowSchemaProvider, Data, DecodeFromRecordBatch, EncodeToRecordBatch};
31
32impl ArrowSchemaProvider for QuoteTick {
33 fn get_schema(metadata: Option<HashMap<String, String>>) -> Schema {
34 let fields = vec![
35 Field::new(
36 "bid_price",
37 DataType::FixedSizeBinary(PRECISION_BYTES),
38 false,
39 ),
40 Field::new(
41 "ask_price",
42 DataType::FixedSizeBinary(PRECISION_BYTES),
43 false,
44 ),
45 Field::new(
46 "bid_size",
47 DataType::FixedSizeBinary(PRECISION_BYTES),
48 false,
49 ),
50 Field::new(
51 "ask_size",
52 DataType::FixedSizeBinary(PRECISION_BYTES),
53 false,
54 ),
55 Field::new("ts_event", DataType::UInt64, false),
56 Field::new("ts_init", DataType::UInt64, false),
57 ];
58
59 match metadata {
60 Some(metadata) => Schema::new_with_metadata(fields, metadata),
61 None => Schema::new(fields),
62 }
63 }
64}
65
66fn parse_metadata(
67 metadata: &HashMap<String, String>,
68) -> Result<(InstrumentId, u8, u8), EncodingError> {
69 let instrument_id_str = metadata
70 .get(KEY_INSTRUMENT_ID)
71 .ok_or_else(|| EncodingError::MissingMetadata(KEY_INSTRUMENT_ID))?;
72 let instrument_id = InstrumentId::from_str(instrument_id_str)
73 .map_err(|e| EncodingError::ParseError(KEY_INSTRUMENT_ID, e.to_string()))?;
74
75 let price_precision = metadata
76 .get(KEY_PRICE_PRECISION)
77 .ok_or_else(|| EncodingError::MissingMetadata(KEY_PRICE_PRECISION))?
78 .parse::<u8>()
79 .map_err(|e| EncodingError::ParseError(KEY_PRICE_PRECISION, e.to_string()))?;
80
81 let size_precision = metadata
82 .get(KEY_SIZE_PRECISION)
83 .ok_or_else(|| EncodingError::MissingMetadata(KEY_SIZE_PRECISION))?
84 .parse::<u8>()
85 .map_err(|e| EncodingError::ParseError(KEY_SIZE_PRECISION, e.to_string()))?;
86
87 Ok((instrument_id, price_precision, size_precision))
88}
89
90impl EncodeToRecordBatch for QuoteTick {
91 fn encode_batch(
92 metadata: &HashMap<String, String>,
93 data: &[Self],
94 ) -> Result<RecordBatch, ArrowError> {
95 let mut bid_price_builder =
96 FixedSizeBinaryBuilder::with_capacity(data.len(), PRECISION_BYTES);
97 let mut ask_price_builder =
98 FixedSizeBinaryBuilder::with_capacity(data.len(), PRECISION_BYTES);
99 let mut bid_size_builder =
100 FixedSizeBinaryBuilder::with_capacity(data.len(), PRECISION_BYTES);
101 let mut ask_size_builder =
102 FixedSizeBinaryBuilder::with_capacity(data.len(), PRECISION_BYTES);
103 let mut ts_event_builder = UInt64Array::builder(data.len());
104 let mut ts_init_builder = UInt64Array::builder(data.len());
105
106 for quote in data {
107 bid_price_builder
108 .append_value(quote.bid_price.raw.to_le_bytes())
109 .unwrap();
110 ask_price_builder
111 .append_value(quote.ask_price.raw.to_le_bytes())
112 .unwrap();
113 bid_size_builder
114 .append_value(quote.bid_size.raw.to_le_bytes())
115 .unwrap();
116 ask_size_builder
117 .append_value(quote.ask_size.raw.to_le_bytes())
118 .unwrap();
119 ts_event_builder.append_value(quote.ts_event.as_u64());
120 ts_init_builder.append_value(quote.ts_init.as_u64());
121 }
122
123 RecordBatch::try_new(
124 Self::get_schema(Some(metadata.clone())).into(),
125 vec![
126 Arc::new(bid_price_builder.finish()),
127 Arc::new(ask_price_builder.finish()),
128 Arc::new(bid_size_builder.finish()),
129 Arc::new(ask_size_builder.finish()),
130 Arc::new(ts_event_builder.finish()),
131 Arc::new(ts_init_builder.finish()),
132 ],
133 )
134 }
135
136 fn metadata(&self) -> HashMap<String, String> {
137 Self::get_metadata(
138 &self.instrument_id,
139 self.bid_price.precision,
140 self.bid_size.precision,
141 )
142 }
143}
144
145impl DecodeFromRecordBatch for QuoteTick {
146 fn decode_batch(
147 metadata: &HashMap<String, String>,
148 record_batch: RecordBatch,
149 ) -> Result<Vec<Self>, EncodingError> {
150 let (instrument_id, price_precision, size_precision) = parse_metadata(metadata)?;
151 let cols = record_batch.columns();
152
153 let bid_price_values = extract_column::<FixedSizeBinaryArray>(
154 cols,
155 "bid_price",
156 0,
157 DataType::FixedSizeBinary(PRECISION_BYTES),
158 )?;
159 let ask_price_values = extract_column::<FixedSizeBinaryArray>(
160 cols,
161 "ask_price",
162 1,
163 DataType::FixedSizeBinary(PRECISION_BYTES),
164 )?;
165 let bid_size_values = extract_column::<FixedSizeBinaryArray>(
166 cols,
167 "bid_size",
168 2,
169 DataType::FixedSizeBinary(PRECISION_BYTES),
170 )?;
171 let ask_size_values = extract_column::<FixedSizeBinaryArray>(
172 cols,
173 "ask_size",
174 3,
175 DataType::FixedSizeBinary(PRECISION_BYTES),
176 )?;
177 let ts_event_values = extract_column::<UInt64Array>(cols, "ts_event", 4, DataType::UInt64)?;
178 let ts_init_values = extract_column::<UInt64Array>(cols, "ts_init", 5, DataType::UInt64)?;
179
180 validate_precision_bytes(bid_price_values, "bid_price")?;
181 validate_precision_bytes(ask_price_values, "ask_price")?;
182 validate_precision_bytes(bid_size_values, "bid_size")?;
183 validate_precision_bytes(ask_size_values, "ask_size")?;
184
185 let result: Result<Vec<Self>, EncodingError> = (0..record_batch.num_rows())
186 .map(|row| {
187 let bid_price = decode_price(
188 bid_price_values.value(row),
189 price_precision,
190 "bid_price",
191 row,
192 )?;
193 let ask_price = decode_price(
194 ask_price_values.value(row),
195 price_precision,
196 "ask_price",
197 row,
198 )?;
199 let bid_size =
200 decode_quantity(bid_size_values.value(row), size_precision, "bid_size", row)?;
201 let ask_size =
202 decode_quantity(ask_size_values.value(row), size_precision, "ask_size", row)?;
203 Ok(Self {
204 instrument_id,
205 bid_price,
206 ask_price,
207 bid_size,
208 ask_size,
209 ts_event: ts_event_values.value(row).into(),
210 ts_init: ts_init_values.value(row).into(),
211 })
212 })
213 .collect();
214
215 result
216 }
217}
218
219impl DecodeDataFromRecordBatch for QuoteTick {
220 fn decode_data_batch(
221 metadata: &HashMap<String, String>,
222 record_batch: RecordBatch,
223 ) -> Result<Vec<Data>, EncodingError> {
224 let ticks: Vec<Self> = Self::decode_batch(metadata, record_batch)?;
225 Ok(ticks.into_iter().map(Data::from).collect())
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use std::{collections::HashMap, sync::Arc};
232
233 use arrow::{array::Array, record_batch::RecordBatch};
234 use nautilus_model::types::{
235 Price, Quantity, fixed::FIXED_SCALAR, price::PriceRaw, quantity::QuantityRaw,
236 };
237 use rstest::rstest;
238
239 use super::*;
240 use crate::arrow::{get_raw_price, get_raw_quantity};
241
242 #[rstest]
243 fn test_get_schema() {
244 let instrument_id = InstrumentId::from("AAPL.XNAS");
245 let metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
246 let schema = QuoteTick::get_schema(Some(metadata.clone()));
247
248 let mut expected_fields = Vec::with_capacity(6);
249
250 expected_fields.push(Field::new(
251 "bid_price",
252 DataType::FixedSizeBinary(PRECISION_BYTES),
253 false,
254 ));
255 expected_fields.push(Field::new(
256 "ask_price",
257 DataType::FixedSizeBinary(PRECISION_BYTES),
258 false,
259 ));
260
261 expected_fields.extend(vec![
262 Field::new(
263 "bid_size",
264 DataType::FixedSizeBinary(PRECISION_BYTES),
265 false,
266 ),
267 Field::new(
268 "ask_size",
269 DataType::FixedSizeBinary(PRECISION_BYTES),
270 false,
271 ),
272 Field::new("ts_event", DataType::UInt64, false),
273 Field::new("ts_init", DataType::UInt64, false),
274 ]);
275
276 let expected_schema = Schema::new_with_metadata(expected_fields, metadata);
277 assert_eq!(schema, expected_schema);
278 }
279
280 #[rstest]
281 fn test_get_schema_map() {
282 let arrow_schema = QuoteTick::get_schema_map();
283 let mut expected_map = HashMap::new();
284
285 let fixed_size_binary = format!("FixedSizeBinary({PRECISION_BYTES})");
286 expected_map.insert("bid_price".to_string(), fixed_size_binary.clone());
287 expected_map.insert("ask_price".to_string(), fixed_size_binary.clone());
288 expected_map.insert("bid_size".to_string(), fixed_size_binary.clone());
289 expected_map.insert("ask_size".to_string(), fixed_size_binary);
290 expected_map.insert("ts_event".to_string(), "UInt64".to_string());
291 expected_map.insert("ts_init".to_string(), "UInt64".to_string());
292 assert_eq!(arrow_schema, expected_map);
293 }
294
295 #[rstest]
296 fn test_encode_quote_tick() {
297 let instrument_id = InstrumentId::from("AAPL.XNAS");
299 let tick1 = QuoteTick {
300 instrument_id,
301 bid_price: Price::from("100.10"),
302 ask_price: Price::from("101.50"),
303 bid_size: Quantity::from(1000),
304 ask_size: Quantity::from(500),
305 ts_event: 1.into(),
306 ts_init: 3.into(),
307 };
308
309 let tick2 = QuoteTick {
310 instrument_id,
311 bid_price: Price::from("100.75"),
312 ask_price: Price::from("100.20"),
313 bid_size: Quantity::from(750),
314 ask_size: Quantity::from(300),
315 ts_event: 2.into(),
316 ts_init: 4.into(),
317 };
318
319 let data = vec![tick1, tick2];
320 let metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
321 let record_batch = QuoteTick::encode_batch(&metadata, &data).unwrap();
322
323 let columns = record_batch.columns();
325
326 let bid_price_values = columns[0]
327 .as_any()
328 .downcast_ref::<FixedSizeBinaryArray>()
329 .unwrap();
330 let ask_price_values = columns[1]
331 .as_any()
332 .downcast_ref::<FixedSizeBinaryArray>()
333 .unwrap();
334 assert_eq!(
335 get_raw_price(bid_price_values.value(0)),
336 (100.10 * FIXED_SCALAR) as PriceRaw
337 );
338 assert_eq!(
339 get_raw_price(bid_price_values.value(1)),
340 (100.75 * FIXED_SCALAR) as PriceRaw
341 );
342 assert_eq!(
343 get_raw_price(ask_price_values.value(0)),
344 (101.50 * FIXED_SCALAR) as PriceRaw
345 );
346 assert_eq!(
347 get_raw_price(ask_price_values.value(1)),
348 (100.20 * FIXED_SCALAR) as PriceRaw
349 );
350
351 let bid_size_values = columns[2]
352 .as_any()
353 .downcast_ref::<FixedSizeBinaryArray>()
354 .unwrap();
355 let ask_size_values = columns[3]
356 .as_any()
357 .downcast_ref::<FixedSizeBinaryArray>()
358 .unwrap();
359 let ts_event_values = columns[4].as_any().downcast_ref::<UInt64Array>().unwrap();
360 let ts_init_values = columns[5].as_any().downcast_ref::<UInt64Array>().unwrap();
361
362 assert_eq!(columns.len(), 6);
363 assert_eq!(bid_size_values.len(), 2);
364 assert_eq!(
365 get_raw_quantity(bid_size_values.value(0)),
366 (1000.0 * FIXED_SCALAR) as QuantityRaw
367 );
368 assert_eq!(
369 get_raw_quantity(bid_size_values.value(1)),
370 (750.0 * FIXED_SCALAR) as QuantityRaw
371 );
372 assert_eq!(ask_size_values.len(), 2);
373 assert_eq!(
374 get_raw_quantity(ask_size_values.value(0)),
375 (500.0 * FIXED_SCALAR) as QuantityRaw
376 );
377 assert_eq!(
378 get_raw_quantity(ask_size_values.value(1)),
379 (300.0 * FIXED_SCALAR) as QuantityRaw
380 );
381 assert_eq!(ts_event_values.len(), 2);
382 assert_eq!(ts_event_values.value(0), 1);
383 assert_eq!(ts_event_values.value(1), 2);
384 assert_eq!(ts_init_values.len(), 2);
385 assert_eq!(ts_init_values.value(0), 3);
386 assert_eq!(ts_init_values.value(1), 4);
387 }
388
389 #[rstest]
390 fn test_decode_batch() {
391 let instrument_id = InstrumentId::from("AAPL.XNAS");
392 let metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
393
394 let raw_bid1 = (100.00 * FIXED_SCALAR) as PriceRaw;
395 let raw_bid2 = (99.00 * FIXED_SCALAR) as PriceRaw;
396 let raw_ask1 = (101.00 * FIXED_SCALAR) as PriceRaw;
397 let raw_ask2 = (100.00 * FIXED_SCALAR) as PriceRaw;
398
399 let (bid_price, ask_price) = (
400 FixedSizeBinaryArray::from(vec![&raw_bid1.to_le_bytes(), &raw_bid2.to_le_bytes()]),
401 FixedSizeBinaryArray::from(vec![&raw_ask1.to_le_bytes(), &raw_ask2.to_le_bytes()]),
402 );
403
404 let bid_size = FixedSizeBinaryArray::from(vec![
405 &((100.0 * FIXED_SCALAR) as PriceRaw).to_le_bytes(),
406 &((90.0 * FIXED_SCALAR) as PriceRaw).to_le_bytes(),
407 ]);
408 let ask_size = FixedSizeBinaryArray::from(vec![
409 &((110.0 * FIXED_SCALAR) as PriceRaw).to_le_bytes(),
410 &((100.0 * FIXED_SCALAR) as PriceRaw).to_le_bytes(),
411 ]);
412 let ts_event = UInt64Array::from(vec![1, 2]);
413 let ts_init = UInt64Array::from(vec![3, 4]);
414
415 let record_batch = RecordBatch::try_new(
416 QuoteTick::get_schema(Some(metadata.clone())).into(),
417 vec![
418 Arc::new(bid_price),
419 Arc::new(ask_price),
420 Arc::new(bid_size),
421 Arc::new(ask_size),
422 Arc::new(ts_event),
423 Arc::new(ts_init),
424 ],
425 )
426 .unwrap();
427
428 let decoded_data = QuoteTick::decode_batch(&metadata, record_batch).unwrap();
429 assert_eq!(decoded_data.len(), 2);
430
431 assert_eq!(decoded_data[0].bid_price, Price::from_raw(raw_bid1, 2));
433 assert_eq!(decoded_data[0].ask_price, Price::from_raw(raw_ask1, 2));
434 assert_eq!(decoded_data[1].bid_price, Price::from_raw(raw_bid2, 2));
435 assert_eq!(decoded_data[1].ask_price, Price::from_raw(raw_ask2, 2));
436 }
437
438 #[rstest]
439 fn test_decode_batch_invalid_bid_price_returns_error() {
440 let instrument_id = InstrumentId::from("AAPL.XNAS");
441 let metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
442
443 let invalid_price: PriceRaw = PriceRaw::MAX - 1000;
444 let valid_price = (100.00 * FIXED_SCALAR) as PriceRaw;
445
446 let bid_price = FixedSizeBinaryArray::from(vec![&invalid_price.to_le_bytes()]);
447 let ask_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
448 let bid_size = FixedSizeBinaryArray::from(vec![
449 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
450 ]);
451 let ask_size = FixedSizeBinaryArray::from(vec![
452 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
453 ]);
454 let ts_event = UInt64Array::from(vec![1]);
455 let ts_init = UInt64Array::from(vec![2]);
456
457 let record_batch = RecordBatch::try_new(
458 QuoteTick::get_schema(Some(metadata.clone())).into(),
459 vec![
460 Arc::new(bid_price),
461 Arc::new(ask_price),
462 Arc::new(bid_size),
463 Arc::new(ask_size),
464 Arc::new(ts_event),
465 Arc::new(ts_init),
466 ],
467 )
468 .unwrap();
469
470 let result = QuoteTick::decode_batch(&metadata, record_batch);
471 assert!(result.is_err());
472 let err = result.unwrap_err();
473 assert!(
474 err.to_string().contains("bid_price") && err.to_string().contains("row 0"),
475 "Expected bid_price error at row 0, was: {err}"
476 );
477 }
478
479 #[rstest]
480 fn test_decode_batch_invalid_ask_size_returns_error() {
481 use nautilus_model::types::quantity::QUANTITY_RAW_MAX;
482
483 let instrument_id = InstrumentId::from("AAPL.XNAS");
484 let metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
485
486 let valid_price = (100.00 * FIXED_SCALAR) as PriceRaw;
487 let bid_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
488 let ask_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
489 let bid_size = FixedSizeBinaryArray::from(vec![
490 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
491 ]);
492
493 let invalid_size = QUANTITY_RAW_MAX + 1;
494 let ask_size = FixedSizeBinaryArray::from(vec![&invalid_size.to_le_bytes()]);
495 let ts_event = UInt64Array::from(vec![1]);
496 let ts_init = UInt64Array::from(vec![2]);
497
498 let record_batch = RecordBatch::try_new(
499 QuoteTick::get_schema(Some(metadata.clone())).into(),
500 vec![
501 Arc::new(bid_price),
502 Arc::new(ask_price),
503 Arc::new(bid_size),
504 Arc::new(ask_size),
505 Arc::new(ts_event),
506 Arc::new(ts_init),
507 ],
508 )
509 .unwrap();
510
511 let result = QuoteTick::decode_batch(&metadata, record_batch);
512 assert!(result.is_err());
513 let err = result.unwrap_err();
514 assert!(
515 err.to_string().contains("ask_size") && err.to_string().contains("row 0"),
516 "Expected ask_size error at row 0, was: {err}"
517 );
518 }
519
520 #[rstest]
521 fn test_decode_batch_missing_instrument_id_returns_error() {
522 let instrument_id = InstrumentId::from("AAPL.XNAS");
523 let mut metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
524 metadata.remove(KEY_INSTRUMENT_ID);
525
526 let valid_price = (100.00 * FIXED_SCALAR) as PriceRaw;
527 let bid_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
528 let ask_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
529 let bid_size = FixedSizeBinaryArray::from(vec![
530 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
531 ]);
532 let ask_size = FixedSizeBinaryArray::from(vec![
533 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
534 ]);
535 let ts_event = UInt64Array::from(vec![1]);
536 let ts_init = UInt64Array::from(vec![2]);
537
538 let record_batch = RecordBatch::try_new(
539 QuoteTick::get_schema(Some(metadata.clone())).into(),
540 vec![
541 Arc::new(bid_price),
542 Arc::new(ask_price),
543 Arc::new(bid_size),
544 Arc::new(ask_size),
545 Arc::new(ts_event),
546 Arc::new(ts_init),
547 ],
548 )
549 .unwrap();
550
551 let result = QuoteTick::decode_batch(&metadata, record_batch);
552 assert!(result.is_err());
553 let err = result.unwrap_err();
554 assert!(
555 err.to_string().contains("instrument_id"),
556 "Expected missing instrument_id error, was: {err}"
557 );
558 }
559
560 #[rstest]
561 fn test_decode_batch_missing_price_precision_returns_error() {
562 let instrument_id = InstrumentId::from("AAPL.XNAS");
563 let mut metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
564 metadata.remove(KEY_PRICE_PRECISION);
565
566 let valid_price = (100.00 * FIXED_SCALAR) as PriceRaw;
567 let bid_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
568 let ask_price = FixedSizeBinaryArray::from(vec![&valid_price.to_le_bytes()]);
569 let bid_size = FixedSizeBinaryArray::from(vec![
570 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
571 ]);
572 let ask_size = FixedSizeBinaryArray::from(vec![
573 &((100.0 * FIXED_SCALAR) as QuantityRaw).to_le_bytes(),
574 ]);
575 let ts_event = UInt64Array::from(vec![1]);
576 let ts_init = UInt64Array::from(vec![2]);
577
578 let record_batch = RecordBatch::try_new(
579 QuoteTick::get_schema(Some(metadata.clone())).into(),
580 vec![
581 Arc::new(bid_price),
582 Arc::new(ask_price),
583 Arc::new(bid_size),
584 Arc::new(ask_size),
585 Arc::new(ts_event),
586 Arc::new(ts_init),
587 ],
588 )
589 .unwrap();
590
591 let result = QuoteTick::decode_batch(&metadata, record_batch);
592 assert!(result.is_err());
593 let err = result.unwrap_err();
594 assert!(
595 err.to_string().contains("price_precision"),
596 "Expected missing price_precision error, was: {err}"
597 );
598 }
599
600 #[rstest]
601 fn test_encode_decode_round_trip() {
602 let instrument_id = InstrumentId::from("AAPL.XNAS");
603 let metadata = QuoteTick::get_metadata(&instrument_id, 2, 0);
604
605 let tick1 = QuoteTick {
606 instrument_id,
607 bid_price: Price::from("100.10"),
608 ask_price: Price::from("100.20"),
609 bid_size: Quantity::from(1000),
610 ask_size: Quantity::from(500),
611 ts_event: 1_000_000_000.into(),
612 ts_init: 1_000_000_001.into(),
613 };
614
615 let tick2 = QuoteTick {
616 instrument_id,
617 bid_price: Price::from("100.15"),
618 ask_price: Price::from("100.25"),
619 bid_size: Quantity::from(750),
620 ask_size: Quantity::from(250),
621 ts_event: 2_000_000_000.into(),
622 ts_init: 2_000_000_001.into(),
623 };
624
625 let original = vec![tick1, tick2];
626 let record_batch = QuoteTick::encode_batch(&metadata, &original).unwrap();
627 let decoded = QuoteTick::decode_batch(&metadata, record_batch).unwrap();
628
629 assert_eq!(decoded.len(), original.len());
630 for (orig, dec) in original.iter().zip(decoded.iter()) {
631 assert_eq!(dec.instrument_id, orig.instrument_id);
632 assert_eq!(dec.bid_price, orig.bid_price);
633 assert_eq!(dec.ask_price, orig.ask_price);
634 assert_eq!(dec.bid_size, orig.bid_size);
635 assert_eq!(dec.ask_size, orig.ask_size);
636 assert_eq!(dec.ts_event, orig.ts_event);
637 assert_eq!(dec.ts_init, orig.ts_init);
638 }
639 }
640}