1use super::{MarketEvent, OrderEvent, ReplayEvent, RestingOrder};
4use crate::model::{Level1Quote, OrderBook, OrderBookLevel, OrderSide, Tick, TradeConditions};
5use chrono::{DateTime, Utc};
6use std::io::{self, Cursor, Read, Write};
7
8const MAGIC: &[u8; 8] = b"EGCFLOW2";
9const MAX_RECORD: usize = 16 * 1024 * 1024;
10
11pub struct RecordingWriter<W: Write> {
16 writer: W,
17}
18
19impl<W: Write> RecordingWriter<W> {
20 pub fn new(mut writer: W) -> io::Result<Self> {
22 writer.write_all(MAGIC)?;
23 Ok(Self { writer })
24 }
25
26 pub fn write(&mut self, event: &ReplayEvent) -> io::Result<()> {
28 if !super::replay::valid(event) || event.symbol.len() > u16::MAX as usize {
29 return Err(invalid());
30 }
31 let mut bytes = Vec::new();
32 bytes.extend_from_slice(&(event.symbol.len() as u16).to_le_bytes());
33 bytes.extend_from_slice(event.symbol.as_bytes());
34 put_time(&mut bytes, event.time);
35 put_u64(&mut bytes, event.sequence);
36 match &event.event {
37 MarketEvent::Trade(t) => {
38 bytes.push(0);
39 put_tick(&mut bytes, t);
40 }
41 MarketEvent::Quote(q) => {
42 bytes.push(1);
43 for value in [q.bid, q.ask, q.bid_size, q.ask_size] {
44 put_f64(&mut bytes, value);
45 }
46 }
47 MarketEvent::DepthSnapshot { sequence, book }
48 | MarketEvent::DepthDelta { sequence, book } => {
49 bytes.push(
50 if matches!(event.event, MarketEvent::DepthSnapshot { .. }) {
51 2
52 } else {
53 3
54 },
55 );
56 put_u64(&mut bytes, *sequence);
57 for side in [&book.bids, &book.asks] {
58 bytes.extend_from_slice(&(side.len() as u32).to_le_bytes());
59 for level in side {
60 put_f64(&mut bytes, level.price);
61 put_f64(&mut bytes, level.size);
62 bytes.push(u8::from(level.order_cnt.is_some()));
63 if let Some(n) = level.order_cnt {
64 bytes.extend_from_slice(&n.to_le_bytes());
65 }
66 }
67 }
68 }
69 MarketEvent::OrderSnapshot { sequence, orders } => {
70 bytes.push(4);
71 put_u64(&mut bytes, *sequence);
72 bytes.extend_from_slice(&(orders.len() as u32).to_le_bytes());
73 for order in orders {
74 put_order(&mut bytes, order);
75 }
76 }
77 MarketEvent::Order { sequence, event } => {
78 bytes.push(5);
79 put_u64(&mut bytes, *sequence);
80 match event {
81 OrderEvent::Add(order) => {
82 bytes.push(0);
83 put_order(&mut bytes, order);
84 }
85 OrderEvent::Modify {
86 id,
87 price,
88 size,
89 keep_priority,
90 } => {
91 bytes.push(1);
92 put_u64(&mut bytes, *id);
93 for value in [price, size] {
94 bytes.push(u8::from(value.is_some()));
95 if let Some(value) = value {
96 put_f64(&mut bytes, *value);
97 }
98 }
99 bytes.push(u8::from(*keep_priority));
100 }
101 OrderEvent::Cancel { id } => {
102 bytes.push(2);
103 put_u64(&mut bytes, *id);
104 }
105 OrderEvent::Execute { id, size, trade } => {
106 bytes.push(3);
107 put_u64(&mut bytes, *id);
108 put_f64(&mut bytes, *size);
109 bytes.push(u8::from(trade.is_some()));
110 if let Some(trade) = trade {
111 put_tick(&mut bytes, trade);
112 }
113 }
114 }
115 }
116 }
117 if bytes.len() > MAX_RECORD {
118 return Err(invalid());
119 }
120 self.writer.write_all(&(bytes.len() as u32).to_le_bytes())?;
121 self.writer.write_all(&bytes)
122 }
123
124 pub fn capture(
128 &mut self,
129 events: impl IntoIterator<Item = io::Result<ReplayEvent>>,
130 ) -> io::Result<()> {
131 for event in events {
132 self.write(&event?)?;
133 }
134 Ok(())
135 }
136
137 pub fn get_ref(&self) -> &W {
139 &self.writer
140 }
141
142 pub fn finish(mut self) -> io::Result<W> {
144 self.writer.flush()?;
145 Ok(self.writer)
146 }
147}
148
149pub struct RecordingReader<R: Read> {
153 reader: R,
154 order_metadata: bool,
155}
156impl<R: Read> RecordingReader<R> {
157 pub fn new(mut reader: R) -> io::Result<Self> {
159 let header = read::<8>(&mut reader)?;
160 let order_metadata = match &header {
161 b"EGCFLOW1" => false,
162 MAGIC => true,
163 _ => return Err(invalid()),
164 };
165 Ok(Self {
166 reader,
167 order_metadata,
168 })
169 }
170
171 pub fn read(&mut self) -> io::Result<Option<ReplayEvent>> {
173 let mut length = [0; 4];
174 loop {
175 match self.reader.read(&mut length[..1]) {
176 Ok(0) => return Ok(None),
177 Ok(_) => break,
178 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
179 Err(e) => return Err(e),
180 }
181 }
182 self.reader.read_exact(&mut length[1..])?;
183 let length = u32::from_le_bytes(length) as usize;
184 if length > MAX_RECORD {
185 return Err(invalid());
186 }
187 let mut bytes = vec![0; length];
188 self.reader.read_exact(&mut bytes)?;
189 let mut reader = Cursor::new(bytes.as_slice());
190 let symbol_len = u16::from_le_bytes(read(&mut reader)?) as usize;
191 let mut symbol = vec![0; symbol_len];
192 reader.read_exact(&mut symbol)?;
193 let symbol = String::from_utf8(symbol).map_err(|_| invalid())?;
194 let time = get_time(&mut reader)?;
195 let sequence = get_u64(&mut reader)?;
196 let event = match get_byte(&mut reader)? {
197 0 => MarketEvent::Trade(get_tick(&mut reader)?),
198 1 => MarketEvent::Quote(Level1Quote {
199 ts: time,
200 symbol: symbol.clone(),
201 bid: get_f64(&mut reader)?,
202 ask: get_f64(&mut reader)?,
203 bid_size: get_f64(&mut reader)?,
204 ask_size: get_f64(&mut reader)?,
205 }),
206 kind @ (2 | 3) => {
207 let sequence = get_u64(&mut reader)?;
208 let book = OrderBook {
209 ts: time,
210 symbol: symbol.clone(),
211 bids: get_levels(&mut reader)?,
212 asks: get_levels(&mut reader)?,
213 };
214 if kind == 2 {
215 MarketEvent::DepthSnapshot { sequence, book }
216 } else {
217 MarketEvent::DepthDelta { sequence, book }
218 }
219 }
220 4 => {
221 let sequence = get_u64(&mut reader)?;
222 let count = u32::from_le_bytes(read(&mut reader)?) as usize;
223 if count > super::depth::MAX_LEVELS * 2 {
224 return Err(invalid());
225 }
226 let orders = (0..count)
227 .map(|_| get_order(&mut reader, self.order_metadata))
228 .collect::<io::Result<Vec<_>>>()?;
229 MarketEvent::OrderSnapshot { sequence, orders }
230 }
231 5 => {
232 let sequence = get_u64(&mut reader)?;
233 let event = match get_byte(&mut reader)? {
234 0 => OrderEvent::Add(get_order(&mut reader, self.order_metadata)?),
235 1 => OrderEvent::Modify {
236 id: get_u64(&mut reader)?,
237 price: get_optional(&mut reader, get_f64)?,
238 size: get_optional(&mut reader, get_f64)?,
239 keep_priority: get_bool(&mut reader)?,
240 },
241 2 => OrderEvent::Cancel {
242 id: get_u64(&mut reader)?,
243 },
244 3 => OrderEvent::Execute {
245 id: get_u64(&mut reader)?,
246 size: get_f64(&mut reader)?,
247 trade: get_optional(&mut reader, get_tick)?,
248 },
249 _ => return Err(invalid()),
250 };
251 MarketEvent::Order { sequence, event }
252 }
253 _ => return Err(invalid()),
254 };
255 let event = ReplayEvent::new(symbol, time, sequence, event);
256 if reader.position() as usize != bytes.len() || !super::replay::valid(&event) {
257 return Err(invalid());
258 }
259 Ok(Some(event))
260 }
261}
262
263fn invalid() -> io::Error {
264 io::Error::new(io::ErrorKind::InvalidData, "invalid market recording")
265}
266fn read<const N: usize>(reader: &mut impl Read) -> io::Result<[u8; N]> {
267 let mut b = [0; N];
268 reader.read_exact(&mut b)?;
269 Ok(b)
270}
271fn put_u64(bytes: &mut Vec<u8>, value: u64) {
272 bytes.extend_from_slice(&value.to_le_bytes());
273}
274fn put_f64(bytes: &mut Vec<u8>, value: f64) {
275 bytes.extend_from_slice(&value.to_le_bytes());
276}
277fn get_u64(reader: &mut impl Read) -> io::Result<u64> {
278 Ok(u64::from_le_bytes(read(reader)?))
279}
280fn get_f64(reader: &mut impl Read) -> io::Result<f64> {
281 Ok(f64::from_le_bytes(read(reader)?))
282}
283fn get_byte(reader: &mut impl Read) -> io::Result<u8> {
284 Ok(read::<1>(reader)?[0])
285}
286fn get_bool(reader: &mut impl Read) -> io::Result<bool> {
287 match get_byte(reader)? {
288 0 => Ok(false),
289 1 => Ok(true),
290 _ => Err(invalid()),
291 }
292}
293fn get_optional<R: Read, T>(
294 reader: &mut R,
295 get: impl FnOnce(&mut R) -> io::Result<T>,
296) -> io::Result<Option<T>> {
297 if get_bool(reader)? {
298 get(reader).map(Some)
299 } else {
300 Ok(None)
301 }
302}
303fn put_time(bytes: &mut Vec<u8>, time: DateTime<Utc>) {
304 bytes.extend_from_slice(&time.timestamp().to_le_bytes());
305 bytes.extend_from_slice(&time.timestamp_subsec_nanos().to_le_bytes());
306}
307fn get_time(reader: &mut impl Read) -> io::Result<DateTime<Utc>> {
308 DateTime::from_timestamp(
309 i64::from_le_bytes(read(reader)?),
310 u32::from_le_bytes(read(reader)?),
311 )
312 .ok_or_else(invalid)
313}
314fn put_tick(bytes: &mut Vec<u8>, trade: &Tick) {
315 put_time(bytes, trade.ts);
316 put_f64(bytes, trade.price);
317 put_f64(bytes, trade.size);
318 bytes.push(match trade.side {
319 None => 0,
320 Some(OrderSide::Buy) => 1,
321 Some(OrderSide::Sell) => 2,
322 });
323 for id in [trade.id, trade.aggressor_order_id, trade.resting_order_id] {
324 bytes.push(u8::from(id.is_some()));
325 if let Some(id) = id {
326 put_u64(bytes, id);
327 }
328 }
329 bytes.extend_from_slice(&trade.conditions.bits().to_le_bytes());
330}
331fn get_tick(reader: &mut impl Read) -> io::Result<Tick> {
332 let time = get_time(reader)?;
333 let price = get_f64(reader)?;
334 let size = get_f64(reader)?;
335 let side = match get_byte(reader)? {
336 0 => None,
337 1 => Some(OrderSide::Buy),
338 2 => Some(OrderSide::Sell),
339 _ => return Err(invalid()),
340 };
341 let mut trade = Tick::new(time, price, size, side);
342 trade.id = get_optional(reader, get_u64)?;
343 trade.aggressor_order_id = get_optional(reader, get_u64)?;
344 trade.resting_order_id = get_optional(reader, get_u64)?;
345 trade.conditions = TradeConditions::from_bits(u16::from_le_bytes(read(reader)?));
346 Ok(trade)
347}
348fn put_order(bytes: &mut Vec<u8>, order: &RestingOrder) {
349 put_u64(bytes, order.id);
350 bytes.push(u8::from(order.side == OrderSide::Sell));
351 put_f64(bytes, order.price);
352 put_f64(bytes, order.size);
353 bytes.push(u8::from(order.participant.is_some()));
354 if let Some(participant) = order.participant {
355 put_u64(bytes, participant);
356 }
357 bytes.push(u8::from(order.placed.is_some()));
358 if let Some(placed) = order.placed {
359 put_time(bytes, placed);
360 }
361}
362fn get_order(reader: &mut impl Read, metadata: bool) -> io::Result<RestingOrder> {
363 let mut order = RestingOrder::new(
364 get_u64(reader)?,
365 if get_bool(reader)? {
366 OrderSide::Sell
367 } else {
368 OrderSide::Buy
369 },
370 get_f64(reader)?,
371 get_f64(reader)?,
372 );
373 if metadata {
374 order.participant = get_optional(reader, get_u64)?;
375 order.placed = get_optional(reader, get_time)?;
376 }
377 Ok(order)
378}
379fn get_levels(reader: &mut impl Read) -> io::Result<Vec<OrderBookLevel>> {
380 let count = u32::from_le_bytes(read(reader)?) as usize;
381 if count > super::depth::MAX_LEVELS {
382 return Err(invalid());
383 }
384 (0..count)
385 .map(|_| {
386 Ok(OrderBookLevel {
387 price: get_f64(reader)?,
388 size: get_f64(reader)?,
389 order_cnt: if get_bool(reader)? {
390 Some(u32::from_le_bytes(read(reader)?))
391 } else {
392 None
393 },
394 })
395 })
396 .collect()
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn binary_recording_round_trips_market_data_and_rejects_truncation() {
405 let time = DateTime::from_timestamp(1_700_000_000, 123).unwrap();
406 let trade = Tick::new(time, 101.25, 3.0, Some(OrderSide::Buy))
407 .with_id(7)
408 .with_aggressor_order_id(8)
409 .with_resting_order_id(9)
410 .with_conditions(TradeConditions::AUCTION);
411 let book = OrderBook {
412 ts: time,
413 symbol: "A".into(),
414 bids: vec![OrderBookLevel {
415 price: 100.0,
416 size: 2.0,
417 order_cnt: Some(1),
418 }],
419 asks: vec![],
420 };
421 let order = RestingOrder::new(9, OrderSide::Sell, 101.25, 5.0)
422 .with_participant(42)
423 .with_placed(time - chrono::TimeDelta::seconds(30));
424 let events = vec![
425 MarketEvent::Trade(trade.clone()),
426 MarketEvent::Quote(Level1Quote {
427 ts: time,
428 symbol: "A".into(),
429 bid: 100.0,
430 ask: 101.25,
431 bid_size: 2.0,
432 ask_size: 5.0,
433 }),
434 MarketEvent::DepthSnapshot {
435 sequence: 1,
436 book: book.clone(),
437 },
438 MarketEvent::DepthDelta { sequence: 2, book },
439 MarketEvent::OrderSnapshot {
440 sequence: 3,
441 orders: vec![order.clone()],
442 },
443 MarketEvent::Order {
444 sequence: 4,
445 event: OrderEvent::Add(order),
446 },
447 MarketEvent::Order {
448 sequence: 5,
449 event: OrderEvent::Modify {
450 id: 9,
451 price: None,
452 size: Some(6.0),
453 keep_priority: false,
454 },
455 },
456 MarketEvent::Order {
457 sequence: 6,
458 event: OrderEvent::Cancel { id: 9 },
459 },
460 MarketEvent::Order {
461 sequence: 7,
462 event: OrderEvent::Execute {
463 id: 9,
464 size: 3.0,
465 trade: Some(trade),
466 },
467 },
468 ];
469 for payload in events {
470 let event = ReplayEvent::new("A", time, 10, payload);
471 let mut writer = RecordingWriter::new(Vec::new()).unwrap();
472 writer.write(&event).unwrap();
473 let bytes = writer.finish().unwrap();
474 let mut reader = RecordingReader::new(bytes.as_slice()).unwrap();
475 let decoded = reader.read().unwrap().unwrap();
476 assert_eq!(decoded, event);
477 let mut again = RecordingWriter::new(Vec::new()).unwrap();
478 again.write(&decoded).unwrap();
479 assert_eq!(again.finish().unwrap(), bytes);
480 assert!(reader.read().unwrap().is_none());
481 for end in 9..bytes.len() {
482 assert!(
483 RecordingReader::new(&bytes[..end]).unwrap().read().is_err(),
484 "truncated at {end}"
485 );
486 }
487 }
488 let legacy_event = ReplayEvent::new(
490 "A",
491 time,
492 0,
493 MarketEvent::OrderSnapshot {
494 sequence: 0,
495 orders: vec![RestingOrder::new(1, OrderSide::Buy, 100.0, 2.0)],
496 },
497 );
498 let legacy: &[u8] = &[
499 0x45, 0x47, 0x43, 0x46, 0x4c, 0x4f, 0x57, 0x31, 0x3d, 0x00, 0x00, 0x00, 0x01, 0x00,
500 0x41, 0x00, 0xf1, 0x53, 0x65, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x00,
501 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
502 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
503 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
504 0x00, 0x00, 0x40,
505 ];
506 assert_eq!(
507 RecordingReader::new(legacy).unwrap().read().unwrap(),
508 Some(legacy_event)
509 );
510
511 struct FailedStorage;
512 impl Write for FailedStorage {
513 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
514 Ok(bytes.len())
515 }
516 fn flush(&mut self) -> io::Result<()> {
517 Err(io::Error::new(
518 io::ErrorKind::StorageFull,
519 "recording storage full",
520 ))
521 }
522 }
523 assert_eq!(
524 RecordingWriter::new(FailedStorage)
525 .unwrap()
526 .finish()
527 .err()
528 .unwrap()
529 .kind(),
530 io::ErrorKind::StorageFull
531 );
532 assert!(RecordingReader::new(b"EGCFLOW3".as_slice()).is_err());
533 let mut oversized = MAGIC.to_vec();
534 oversized.extend_from_slice(&u32::MAX.to_le_bytes());
535 assert!(
536 RecordingReader::new(oversized.as_slice())
537 .unwrap()
538 .read()
539 .is_err()
540 );
541 }
542}