├── .gitignore ├── optimized-lob ├── .gitignore ├── src │ ├── lib.rs │ ├── utils.rs │ ├── quantity.rs │ ├── price.rs │ ├── pool.rs │ ├── orderbook.rs │ ├── level.rs │ ├── order.rs │ └── orderbook_manager.rs ├── Cargo.toml └── README.md ├── itch-parser ├── .gitignore ├── src │ ├── errors.rs │ ├── lib.rs │ ├── utils.rs │ ├── body.rs │ ├── message_stream.rs │ └── message.rs ├── Cargo.toml └── README.md ├── tests ├── .gitignore ├── src │ ├── lob_tests │ │ ├── mod.rs │ │ ├── utils.rs │ │ ├── same_level_and_book_test.rs │ │ ├── same_book_test.rs │ │ └── test_order.rs │ ├── main.rs │ ├── test_itch_parser.rs │ └── test_lob.rs ├── Cargo.toml └── Cargo.lock ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 12302019.NASDAQ_ITCH50 3 | .idea 4 | .DS_Store -------------------------------------------------------------------------------- /optimized-lob/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /itch-parser/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .idea 4 | .DS_Store -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 12302019.NASDAQ_ITCH50 3 | .idea 4 | .DS_Store -------------------------------------------------------------------------------- /tests/src/lob_tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod same_book_test; 2 | mod same_level_and_book_test; 3 | mod test_order; 4 | mod utils; 5 | -------------------------------------------------------------------------------- /optimized-lob/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod level; 2 | pub mod order; 3 | pub mod orderbook; 4 | pub mod orderbook_manager; 5 | pub mod pool; 6 | pub mod price; 7 | pub mod quantity; 8 | pub mod utils; 9 | -------------------------------------------------------------------------------- /itch-parser/src/errors.rs: -------------------------------------------------------------------------------- 1 | // errors.rs 2 | 3 | use error_chain::error_chain; 4 | 5 | error_chain! { 6 | foreign_links { 7 | Io(::std::io::Error); 8 | Nom(::nom::Err); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /itch-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | // lib.rs 2 | 3 | mod body; 4 | mod errors; 5 | mod message; 6 | mod message_stream; 7 | mod utils; 8 | 9 | pub use body::*; 10 | pub use errors::*; 11 | pub use message::*; 12 | pub use message_stream::*; 13 | pub use utils::*; 14 | -------------------------------------------------------------------------------- /itch-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "itch-parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | error-chain = "0.12.4" 10 | nom = "7.1.3" 11 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | itch-parser = { path = "../itch-parser" } 10 | optimized-lob = { path = "../optimized-lob" } -------------------------------------------------------------------------------- /optimized-lob/src/utils.rs: -------------------------------------------------------------------------------- 1 | // utils.rs 2 | 3 | pub const INITIAL_ORDER_COUNT: usize = 1 << 20; 4 | pub const MAX_BOOKS: usize = 1 << 14; 5 | pub const MAX_LEVELS: usize = 1 << 20; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 8 | pub struct BookId(pub u16); 9 | 10 | impl BookId { 11 | #[inline] 12 | pub fn value(&self) -> u16 { 13 | self.0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /optimized-lob/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "optimized-lob" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Aman Kumar "] 7 | description = "An optimized version of the Limit Order Book implementation with Rust" 8 | readme = "README.md" 9 | homepage = "https://github.com/amankrx/matching-engine-rs/optimized-lob" 10 | repository = "https://github.com/amankrx/matching-engine-rs" 11 | keywords = ["rust", "order-book", "limit-order-book", "orderbook", "itch"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | -------------------------------------------------------------------------------- /tests/src/lob_tests/utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | use optimized_lob::level::LevelId; 3 | use optimized_lob::orderbook_manager::OrderBookManager; 4 | use optimized_lob::quantity::Qty; 5 | 6 | // A few helper functions for the tests 7 | 8 | #[cfg(test)] 9 | pub(crate) fn get_level_capacity( 10 | orderbook_manager: &OrderBookManager, 11 | book_id: usize, 12 | level_id: u32, 13 | ) -> Qty { 14 | orderbook_manager 15 | .books 16 | .get(book_id) 17 | .unwrap() 18 | .clone() 19 | .unwrap() 20 | .level_pool 21 | .get(LevelId(level_id)) 22 | .unwrap() 23 | .size() 24 | } 25 | -------------------------------------------------------------------------------- /optimized-lob/src/quantity.rs: -------------------------------------------------------------------------------- 1 | //quantity.rs 2 | 3 | use std::ops::{AddAssign, SubAssign}; 4 | 5 | #[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] 6 | pub struct Qty(pub u32); 7 | 8 | impl AddAssign for Qty { 9 | fn add_assign(&mut self, other: Qty) { 10 | self.0 += other.0; 11 | } 12 | } 13 | 14 | impl SubAssign for Qty { 15 | fn sub_assign(&mut self, other: Qty) { 16 | self.0 -= other.0; 17 | } 18 | } 19 | 20 | impl Qty { 21 | #[inline] 22 | pub fn value(&self) -> u32 { 23 | self.0 24 | } 25 | 26 | #[inline] 27 | pub fn is_empty(&self) -> bool { 28 | self.0 == 0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /optimized-lob/src/price.rs: -------------------------------------------------------------------------------- 1 | // price.rs 2 | 3 | #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default)] 4 | pub struct Price(pub i32); 5 | 6 | impl Price { 7 | /// Returns the value of the price. 8 | #[inline] 9 | pub fn value(&self) -> i32 { 10 | self.0 11 | } 12 | 13 | /// Returns true if the price is a bid. 14 | #[inline] 15 | pub fn is_bid(&self) -> bool { 16 | self.0 > 0 17 | } 18 | 19 | /// Returns the absolute value of the price. 20 | #[inline] 21 | pub fn absolute(&self) -> i32 { 22 | self.0.abs() 23 | } 24 | 25 | /// Convert a u32 to a Price. 26 | #[inline] 27 | pub fn from_u32(price: u32, is_bid: bool) -> Self { 28 | Self(price as i32 * if is_bid { 1 } else { -1 }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/main.rs: -------------------------------------------------------------------------------- 1 | // main.rs 2 | mod lob_tests; 3 | mod test_itch_parser; 4 | mod test_lob; 5 | 6 | use std::env; 7 | 8 | fn main() { 9 | let args: Vec = env::args().collect(); 10 | 11 | // Parse command-line arguments 12 | let test_to_run = if args.len() > 1 && args[1] == "--itch-parser" { 13 | // If the "--itch-parser" flag is provided, run the itch parser test 14 | "itch_parser" 15 | } else { 16 | // Default to running the test_lob 17 | "test_lob" 18 | }; 19 | 20 | // Set the file path using an environment variable 21 | let file_path = env::var("ITCH_DATA").unwrap_or_else(|_| { 22 | panic!("ITCH_DATA environment variable not set"); 23 | }); 24 | 25 | match test_to_run { 26 | "itch_parser" => test_itch_parser::test_itch_parser(&file_path), 27 | "test_lob" => test_lob::test_lob(&file_path), 28 | _ => println!("Invalid test specified"), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /itch-parser/src/utils.rs: -------------------------------------------------------------------------------- 1 | // utils.rs 2 | 3 | use super::errors::*; 4 | use nom::{IResult, Needed}; 5 | 6 | /// Converts a u8 input to a boolean. 7 | #[inline] 8 | pub fn char_to_bool(input: u8) -> Result { 9 | if input == b'Y' { 10 | Ok(true) 11 | } else if input == b'N' { 12 | Ok(false) 13 | } else { 14 | Err(Error::from("Invalid input")) 15 | } 16 | } 17 | 18 | /// Parses a big-endian u48 integer from a byte slice. 19 | /// Useful for parsing timestamps. 20 | #[inline] 21 | pub fn be_u48(i: &[u8]) -> IResult<&[u8], u64> { 22 | if i.len() < 6 { 23 | Err(nom::Err::Incomplete(Needed::Size( 24 | std::num::NonZeroUsize::new(6).unwrap(), 25 | ))) 26 | } else { 27 | let res = ((i[0] as u64) << 40) 28 | + ((i[1] as u64) << 32) 29 | + ((i[2] as u64) << 24) 30 | + ((i[3] as u64) << 16) 31 | + ((i[4] as u64) << 8) 32 | + i[5] as u64; 33 | Ok((&i[6..], res)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/src/test_itch_parser.rs: -------------------------------------------------------------------------------- 1 | extern crate itch_parser; 2 | 3 | use std::path::Path; 4 | use std::time::Instant; 5 | 6 | pub fn test_itch_parser(file_path: &str) { 7 | let path_to_market_data = Path::new(file_path); 8 | let stream = itch_parser::MessageStream::from_file(path_to_market_data).unwrap(); 9 | 10 | let mut messages: u32 = 0; 11 | 12 | println!("------------------------------------"); 13 | println!("ITCH Parser Processing...\n"); 14 | 15 | let start = Instant::now(); 16 | 17 | for _ in stream { 18 | messages += 1; 19 | } 20 | 21 | let duration = Instant::now() - start; 22 | let speed = messages as f64 / duration.as_secs_f64(); 23 | 24 | println!("Success...\n"); 25 | println!("ITCH Parsing Statistics:"); 26 | println!("Total Messages: {}", messages); 27 | println!("Total Time: {:.3} seconds", duration.as_secs_f64()); 28 | println!("Speed: {} msg/second", speed as u32); 29 | println!("Latency: {} ns", duration.as_nanos() / messages as u128); 30 | println!("------------------------------------"); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Aman Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /optimized-lob/README.md: -------------------------------------------------------------------------------- 1 | # optimized-lob 2 | 3 | This library contains an optimized implementation of a Limit Order Book (LOB). It keeps the aggregate quantities at each level. 4 | 5 | Note: Will add the comments describing the project later on. 6 | 7 | ## Usage 8 | 9 | - Add the library to your project. 10 | - Import the `optimized_lob` struct from `optimized-lob`. 11 | - The `orderbook_manager` struct contains all the functions you need to manage the LOB. You can perform add, cancel, execute, delete, and replace operations on the LOB. 12 | - There are individual wrapper structs as well that you might need to store price, quantity, order ID, book ID, etc. 13 | 14 | ## Examples 15 | A simple example demonstrating the use of library to add, delete, and replace orders. 16 | ```rust 17 | extern crate optimized_lob; 18 | use optimized_lob::{ 19 | order::OrderId, orderbook_manager::OrderBookManager, quantity::Qty, utils::BookId, 20 | }; 21 | 22 | fn test_lob() { 23 | let mut orderbook_manager = OrderBookManager::new(); 24 | 25 | // Add an order 26 | orderbook_manager.add_order( 27 | OrderId(0), // Order ID 28 | BookId(0), // Book ID 29 | Qty(100), // Quantity 30 | 600, // Price 31 | true, // Is Bid 32 | ); 33 | 34 | // Replace an order 35 | orderbook_manager.replace_order( 36 | OrderId(0), // Order ID to replace 37 | OrderId(1), // New Order ID 38 | Qty(50), // New Quantity 39 | 500, // New Price 40 | ); 41 | 42 | // Remove an order 43 | orderbook_manager.remove_order(OrderId(1)); 44 | } 45 | ``` 46 | 47 | See Also: 48 | - [CppTrader](https://github.com/chronoxor/CppTrader) matching engine implementation 49 | - A [StackOverflow answer](https://quant.stackexchange.com/questions/3783/what-is-an-efficient-data-structure-to-model-order-book/32482#32482) along with his implementation of an [optimized LOB](https://github.com/charles-cooper/itch-order-book/). 50 | - This [blog post](https://web.archive.org/web/20110219163448/http://howtohft.wordpress.com/2011/02/15/how-to-build-a-fast-limit-order-book/) gives a good idea for the low-level design of the orderbook. 51 | -------------------------------------------------------------------------------- /itch-parser/src/body.rs: -------------------------------------------------------------------------------- 1 | // body.rs 2 | 3 | use nom::IResult; 4 | 5 | /// The message body. It just uses the important variants and their fields. 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub enum Body { 8 | AddOrder { 9 | order_id: u64, 10 | is_bid: bool, 11 | shares: u32, 12 | stock: u64, 13 | price: u32, 14 | }, 15 | DeleteOrder { 16 | order_id: u64, 17 | }, 18 | OrderCancelled { 19 | order_id: u64, 20 | shares: u32, 21 | }, 22 | OrderExecuted { 23 | order_id: u64, 24 | shares: u32, 25 | match_number: u64, 26 | }, 27 | OrderExecutedWithPrice { 28 | order_id: u64, 29 | shares: u32, 30 | match_number: u64, 31 | printable: bool, 32 | price: u32, 33 | }, 34 | ReplaceOrder { 35 | old_order_id: u64, 36 | new_order_id: u64, 37 | shares: u32, 38 | price: u32, 39 | }, 40 | SystemEvent { 41 | event: EventCode, 42 | }, 43 | // Enum variant representing a placeholder "Pass" message with no data. 44 | Pass(()), 45 | } 46 | 47 | // Enum representing different event codes for SystemEvent messages. 48 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 49 | pub enum EventCode { 50 | StartOfMessages, 51 | StartOfSystemHours, 52 | StartOfMarketHours, 53 | EndOfMarketHours, 54 | EndOfSystemHours, 55 | EndOfMessages, 56 | } 57 | 58 | // Parse a SystemEvent message from input bytes. 59 | #[inline] 60 | pub fn parse_system_event(input: &[u8]) -> IResult<&[u8], EventCode> { 61 | let (input, event_char) = nom::character::streaming::anychar(input)?; 62 | 63 | // Match the parsed character to an EventCode variant or return an error if it doesn't match. 64 | let event = match event_char { 65 | 'O' => EventCode::StartOfMessages, 66 | 'S' => EventCode::StartOfSystemHours, 67 | 'Q' => EventCode::StartOfMarketHours, 68 | 'M' => EventCode::EndOfMarketHours, 69 | 'E' => EventCode::EndOfSystemHours, 70 | 'C' => EventCode::EndOfMessages, 71 | _ => { 72 | // If the character doesn't match any known EventCode, return a parsing error. 73 | return Err(nom::Err::Error(nom::error::Error::new( 74 | input, 75 | nom::error::ErrorKind::Tag, 76 | ))); 77 | } 78 | }; 79 | 80 | Ok((input, event)) 81 | } 82 | -------------------------------------------------------------------------------- /tests/src/lob_tests/same_level_and_book_test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | mod test { 3 | use crate::lob_tests::utils::get_level_capacity; 4 | /// Tests for the same level and book 5 | /// It tests all the use cases for the same level and book 6 | /// like add, cancel, remove, and execute orders 7 | use optimized_lob::order::OrderId; 8 | use optimized_lob::orderbook_manager::OrderBookManager; 9 | use optimized_lob::quantity::Qty; 10 | use optimized_lob::utils::BookId; 11 | 12 | #[test] 13 | fn test_for_same_book_and_level() { 14 | let mut orderbook_manager = OrderBookManager::new(); 15 | 16 | orderbook_manager.add_order(OrderId(0), BookId(1), Qty(800), 500, true); 17 | orderbook_manager.add_order(OrderId(1), BookId(1), Qty(50), 500, true); 18 | orderbook_manager.add_order(OrderId(2), BookId(1), Qty(26), 500, true); 19 | 20 | assert_eq!(Qty(876), get_level_capacity(&orderbook_manager, 1, 0)); 21 | 22 | orderbook_manager.remove_order(OrderId(2)); 23 | assert_eq!(Qty(850), get_level_capacity(&orderbook_manager, 1, 0)); 24 | 25 | orderbook_manager.cancel_order(OrderId(0), Qty(100)); 26 | assert_eq!(Qty(750), get_level_capacity(&orderbook_manager, 1, 0)); 27 | 28 | orderbook_manager.cancel_order(OrderId(1), Qty(50)); 29 | assert_eq!(Qty(700), get_level_capacity(&orderbook_manager, 1, 0)); 30 | 31 | orderbook_manager.add_order(OrderId(3), BookId(1), Qty(50), 500, true); 32 | orderbook_manager.add_order(OrderId(4), BookId(1), Qty(26), 500, true); 33 | assert_eq!(Qty(776), get_level_capacity(&orderbook_manager, 1, 0)); 34 | 35 | orderbook_manager.remove_order(OrderId(3)); 36 | assert_eq!(Qty(726), get_level_capacity(&orderbook_manager, 1, 0)); 37 | 38 | orderbook_manager.remove_order(OrderId(4)); 39 | assert_eq!(Qty(700), get_level_capacity(&orderbook_manager, 1, 0)); 40 | 41 | orderbook_manager.remove_order(OrderId(0)); 42 | assert_eq!(Qty(0), get_level_capacity(&orderbook_manager, 1, 0)); 43 | 44 | orderbook_manager.add_order(OrderId(5), BookId(1), Qty(50), 500, true); 45 | orderbook_manager.add_order(OrderId(6), BookId(1), Qty(26), 500, true); 46 | assert_eq!(Qty(76), get_level_capacity(&orderbook_manager, 1, 0)); 47 | orderbook_manager.execute_order(OrderId(5), Qty(50)); 48 | assert_eq!(Qty(26), get_level_capacity(&orderbook_manager, 1, 0)); 49 | orderbook_manager.execute_order(OrderId(6), Qty(10)); 50 | assert_eq!(Qty(16), get_level_capacity(&orderbook_manager, 1, 0)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /itch-parser/README.md: -------------------------------------------------------------------------------- 1 | # ITCH Parser 2 | A nom-based Rust parser for parsing NASDAQ ITCH Protocol 5.0 based data. 3 | I initially intended to use the [itchy-rust](https://github.com/adwhit/itchy-rust) library directly which is in itself a robust way to handle the ITCH data, but much of the libraries that are used in it are outdated, and maybe rejected in future versions. 4 | For eg: it still uses `v4.x` of nom, when `v7.x` are available now. 5 | But still though much of the logic 6 | 7 | Note: The parser is incomplete since I just included a few useful operations for my optimized-lob. For a robust parser please use the [itchy-rust](https://github.com/adwhit/itchy-rust) library. 8 | 9 | ## Usage 10 | 11 | - Add the library to your project. 12 | - Import the `itch_parser` struct from `itch-parser`. 13 | - It contains all the functions you need to parse the ITCH data. 14 | 15 | ## Examples 16 | A simple example to use the stream. 17 | ```rust 18 | extern crate itch_parser; 19 | 20 | use std::path::Path; 21 | 22 | pub fn test_itch_parser(file_path: &str) { 23 | let path_to_market_data = Path::new(file_path); 24 | let stream = itch_parser::MessageStream::from_file(path_to_market_data).unwrap(); 25 | 26 | let mut messages: u32 = 0; 27 | for _ in stream { 28 | messages += 1; 29 | } 30 | } 31 | ``` 32 | 33 | Using the stream to parse with the message types: 34 | ```rust 35 | extern crate itch_parser; 36 | 37 | use itch_parser::Body::{ 38 | AddOrder, DeleteOrder, OrderCancelled 39 | }; 40 | use itch_parser::MessageStream; 41 | use std::path::Path; 42 | use std::time::Instant; 43 | 44 | pub fn test_parser(file_path: &str) { 45 | let path_to_market_data = Path::new(file_path); 46 | let stream = MessageStream::from_file(path_to_market_data).unwrap(); 47 | 48 | // Counters 49 | let mut messages = 0; 50 | let mut add_order_count = 0; 51 | let mut execute_orders_count = 0; 52 | let mut cancel_order_count = 0; 53 | 54 | // Process messages 55 | for msg in stream { 56 | let unwrapped_msg = msg.unwrap(); 57 | 58 | match unwrapped_msg.body { 59 | AddOrder { 60 | order_id: _, 61 | is_bid: _, 62 | shares: _, 63 | stock: _, 64 | price: _, 65 | } => { 66 | add_order_count += 1; 67 | } 68 | OrderCancelled { order_id: _, shares: _ } => { 69 | cancel_order_count += 1; 70 | } 71 | DeleteOrder { order_id: _ } => { 72 | delete_order_count += 1; 73 | } 74 | _ => {} 75 | } 76 | messages += 1; 77 | } 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /tests/src/lob_tests/same_book_test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | mod test { 3 | use crate::lob_tests::utils::get_level_capacity; 4 | /// Tests for the same book and multiple levels 5 | /// It tests all the use cases for the same level and book 6 | /// like add, cancel, remove, and execute orders. 7 | use optimized_lob::order::OrderId; 8 | use optimized_lob::orderbook_manager::OrderBookManager; 9 | use optimized_lob::quantity::Qty; 10 | use optimized_lob::utils::BookId; 11 | 12 | #[test] 13 | fn test_for_same_book_with_multiple_levels() { 14 | let mut orderbook_manager = OrderBookManager::new(); 15 | 16 | orderbook_manager.add_order(OrderId(0), BookId(1), Qty(800), 500, true); 17 | orderbook_manager.add_order(OrderId(1), BookId(1), Qty(50), 600, true); 18 | orderbook_manager.add_order(OrderId(2), BookId(1), Qty(26), 600, true); 19 | 20 | assert_eq!(Qty(800), get_level_capacity(&orderbook_manager, 1, 0)); 21 | 22 | orderbook_manager.remove_order(OrderId(2)); 23 | assert_eq!(Qty(50), get_level_capacity(&orderbook_manager, 1, 1)); 24 | 25 | orderbook_manager.cancel_order(OrderId(0), Qty(100)); 26 | assert_eq!(Qty(700), get_level_capacity(&orderbook_manager, 1, 0)); 27 | 28 | orderbook_manager.remove_order(OrderId(1)); 29 | assert_eq!(Qty(0), get_level_capacity(&orderbook_manager, 1, 1)); 30 | 31 | orderbook_manager.add_order(OrderId(3), BookId(1), Qty(50), 800, true); 32 | orderbook_manager.add_order(OrderId(4), BookId(1), Qty(26), 600, true); 33 | assert_eq!(Qty(50), get_level_capacity(&orderbook_manager, 1, 1)); 34 | assert_eq!(Qty(26), get_level_capacity(&orderbook_manager, 1, 2)); 35 | orderbook_manager.remove_order(OrderId(3)); 36 | assert_eq!(Qty(0), get_level_capacity(&orderbook_manager, 1, 1)); 37 | orderbook_manager.remove_order(OrderId(4)); 38 | assert_eq!(Qty(0), get_level_capacity(&orderbook_manager, 1, 2)); 39 | orderbook_manager.remove_order(OrderId(0)); 40 | assert_eq!(Qty(0), get_level_capacity(&orderbook_manager, 1, 0)); 41 | orderbook_manager.add_order(OrderId(5), BookId(1), Qty(50), 1500, true); 42 | orderbook_manager.add_order(OrderId(6), BookId(1), Qty(26), 500, true); 43 | assert_eq!(Qty(50), get_level_capacity(&orderbook_manager, 1, 0)); 44 | assert_eq!(Qty(26), get_level_capacity(&orderbook_manager, 1, 2)); 45 | assert_eq!(Qty(0), get_level_capacity(&orderbook_manager, 1, 1)); 46 | orderbook_manager.add_order(OrderId(6), BookId(1), Qty(86), 1400, true); 47 | assert_eq!(Qty(86), get_level_capacity(&orderbook_manager, 1, 1)); 48 | orderbook_manager.add_order(OrderId(6), BookId(1), Qty(96), 1300, true); 49 | assert_eq!(Qty(96), get_level_capacity(&orderbook_manager, 1, 3)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /optimized-lob/src/pool.rs: -------------------------------------------------------------------------------- 1 | // pool.rs 2 | 3 | // Import the Level and LevelId structs from the level module. 4 | use crate::level::{Level, LevelId}; 5 | 6 | // Define a struct named LevelPool, which is a pool for managing Level objects. 7 | #[derive(Default, Clone)] 8 | pub struct LevelPool { 9 | allocated: Vec, // A vector to store allocated Level objects. 10 | free: Vec, // A vector to store free LevelId values. 11 | } 12 | 13 | impl LevelPool { 14 | // Constructor for creating a new LevelPool instance with default values. 15 | #[inline] 16 | pub fn new() -> Self { 17 | Self { 18 | allocated: Vec::new(), // Initialize allocated vector as empty. 19 | free: Vec::new(), // Initialize free vector as empty. 20 | } 21 | } 22 | 23 | // Constructor for creating a new LevelPool instance with a specified capacity. 24 | #[inline] 25 | pub fn new_with_capacity(size: usize) -> Self { 26 | Self { 27 | allocated: Vec::with_capacity(size), // Initialize allocated vector with the specified capacity. 28 | free: Vec::new(), // Initialize free vector as empty. 29 | } 30 | } 31 | 32 | // Allocate a LevelId from the pool. Reuses a free LevelId if available or creates a new one. 33 | #[inline] 34 | pub fn alloc(&mut self) -> LevelId { 35 | if let Some(idx) = self.free.pop() { 36 | idx // Reuse a free LevelId. 37 | } else { 38 | let idx = LevelId(self.allocated.len() as u32); // Create a new LevelId. 39 | self.allocated.push(Level::default()); // Allocate a new Level object. 40 | idx 41 | } 42 | } 43 | 44 | // Free a LevelId by adding it back to the pool of available LevelIds. 45 | #[inline] 46 | pub fn free(&mut self, idx: LevelId) { 47 | self.free.push(idx); 48 | } 49 | 50 | // Get a reference to a Level by LevelId if it exists in the pool. 51 | #[inline] 52 | pub fn get(&self, idx: LevelId) -> Option<&Level> { 53 | let idx = idx.value() as usize; 54 | if idx < self.allocated.len() { 55 | Some(&self.allocated[idx]) 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | // Get a mutable reference to a Level by LevelId if it exists in the pool. 62 | #[inline] 63 | pub fn get_mut(&mut self, idx: LevelId) -> Option<&mut Level> { 64 | let idx = idx.value() as usize; 65 | if idx < self.allocated.len() { 66 | Some(&mut self.allocated[idx]) 67 | } else { 68 | None 69 | } 70 | } 71 | 72 | // Set the Level object associated with a LevelId in the pool. 73 | #[inline] 74 | pub fn set_level(&mut self, idx: LevelId, level: Level) { 75 | let idx = idx.value() as usize; 76 | self.allocated[idx] = level 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/src/lob_tests/test_order.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use optimized_lob::level::LevelId; 4 | use optimized_lob::order::{OidMap, Order, OrderId}; 5 | use optimized_lob::quantity::Qty; 6 | use optimized_lob::utils::BookId; 7 | 8 | #[test] 9 | fn test_order_creation() { 10 | let qty = Qty(100); 11 | let level_id = LevelId(1); 12 | let book_id = BookId(42); 13 | 14 | let order = Order::new(qty, level_id, book_id); 15 | 16 | assert_eq!(order.qty(), qty); 17 | assert_eq!(order.level_id(), level_id); 18 | assert_eq!(order.book_id(), book_id); 19 | } 20 | 21 | #[test] 22 | fn test_order_replace() { 23 | let qty1 = Qty(100); 24 | let qty2 = Qty(200); 25 | let level_id1 = LevelId(1); 26 | let level_id2 = LevelId(2); 27 | let book_id1 = BookId(42); 28 | let book_id2 = BookId(43); 29 | 30 | let mut order = Order::new(qty1, level_id1, book_id1); 31 | let new_order = Order::new(qty2, level_id2, book_id2); 32 | 33 | order.replace(new_order); 34 | 35 | assert_eq!(order.qty(), qty2); 36 | assert_eq!(order.level_id(), level_id2); 37 | assert_eq!(order.book_id(), book_id2); 38 | } 39 | 40 | #[test] 41 | fn test_order_set_level_id() { 42 | let qty = Qty(100); 43 | let level_id1 = LevelId(1); 44 | let level_id2 = LevelId(2); 45 | let book_id = BookId(42); 46 | 47 | let mut order = Order::new(qty, level_id1, book_id); 48 | 49 | order.set_level_id(level_id2); 50 | 51 | assert_eq!(order.level_id(), level_id2); 52 | } 53 | 54 | #[test] 55 | fn test_oid_map_insert_and_get() { 56 | let mut oid_map = OidMap::new(); 57 | let oid = OrderId(0); 58 | let qty = Qty(100); 59 | let level_id = LevelId(1); 60 | let book_id = BookId(42); 61 | let order = Order::new(qty, level_id, book_id); 62 | 63 | oid_map.insert(oid, &order); 64 | let retrieved_order = oid_map.get(oid); 65 | 66 | assert_eq!(retrieved_order, Some(&order)); 67 | } 68 | 69 | #[test] 70 | fn test_oid_map_reserve() { 71 | let mut oid_map = OidMap::new(); 72 | let oid = OrderId(1000); 73 | let qty = Qty(100); 74 | let level_id = LevelId(1); 75 | let book_id = BookId(42); 76 | let order = Order::new(qty, level_id, book_id); 77 | 78 | oid_map.reserve(oid); 79 | oid_map.insert(oid, &order); 80 | let retrieved_order = oid_map.get(oid); 81 | 82 | assert_eq!(retrieved_order, Some(&order)); 83 | } 84 | 85 | #[test] 86 | fn test_oid_map_remove() { 87 | let mut oid_map = OidMap::new(); 88 | let oid = OrderId(0); 89 | let qty = Qty(100); 90 | let level_id = LevelId(1); 91 | let book_id = BookId(42); 92 | let order = Order::new(qty, level_id, book_id); 93 | 94 | oid_map.insert(oid, &order); 95 | oid_map.remove(oid); 96 | let retrieved_order = oid_map.get(oid); 97 | 98 | assert_eq!(retrieved_order, None); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /optimized-lob/src/orderbook.rs: -------------------------------------------------------------------------------- 1 | // orderbook.rs 2 | 3 | use crate::{ 4 | level::{Level, LevelId, PriceLevel, SortedLevels}, 5 | order::Order, 6 | pool::LevelPool, 7 | price::Price, 8 | quantity::Qty, 9 | utils::MAX_LEVELS, 10 | }; 11 | 12 | /// Represents an order book that holds bids and asks sorted by price levels. 13 | #[derive(Clone)] 14 | pub struct OrderBook { 15 | pub bids: SortedLevels, // Sorted levels for bid orders. 16 | pub asks: SortedLevels, // Sorted levels for ask orders. 17 | pub level_pool: LevelPool, // Pool for managing price levels. 18 | } 19 | 20 | impl Default for OrderBook { 21 | fn default() -> Self { 22 | Self::new() 23 | } 24 | } 25 | 26 | impl OrderBook { 27 | /// Creates a new OrderBook with empty bids, asks, and a level pool. 28 | #[inline] 29 | pub fn new() -> Self { 30 | Self { 31 | bids: SortedLevels::new(), 32 | asks: SortedLevels::new(), 33 | level_pool: LevelPool::new_with_capacity(MAX_LEVELS), 34 | } 35 | } 36 | 37 | /// Adds an order to the order book with the given price and quantity. 38 | /// Determines whether the order is a bid or ask and inserts it accordingly. 39 | #[inline] 40 | pub fn add_order(&mut self, order: &mut Order, price: Price, qty: Qty) { 41 | let levels = if price.is_bid() { 42 | &mut self.bids 43 | } else { 44 | &mut self.asks 45 | }; 46 | 47 | let mut insertion_point = levels.len(); 48 | let mut found_insertion_point = false; 49 | 50 | // Find the insertion point from the end of the Sorted Level. 51 | while insertion_point > 0 { 52 | insertion_point -= 1; 53 | let cur_level = levels.get_mut(insertion_point); 54 | 55 | match cur_level.price().cmp(&price) { 56 | std::cmp::Ordering::Equal => { 57 | order.set_level_id(LevelId(cur_level.level_id().value())); 58 | found_insertion_point = true; 59 | break; 60 | } 61 | std::cmp::Ordering::Less => { 62 | insertion_point += 1; 63 | break; 64 | } 65 | _ => {} 66 | } 67 | } 68 | 69 | // If the insertion point is not found, insert it at the appropriate position. 70 | // Do the necessary allocations as well to the level pool. 71 | if !found_insertion_point { 72 | let level_ptr = self.level_pool.alloc(); 73 | order.set_level_id(level_ptr); 74 | let level = Level::new(price, Qty(0)); 75 | self.level_pool.set_level(level_ptr, level); 76 | let px = PriceLevel::new(price, level_ptr); 77 | levels.insert(insertion_point, px); 78 | } 79 | self.level_pool.get_mut(order.level_id()).unwrap().incr(qty); 80 | } 81 | 82 | /// Reduces the quantity of an existing order in the order book. 83 | #[inline] 84 | pub fn reduce_order(&mut self, order: &mut Order, qty: Qty) { 85 | self.level_pool 86 | .get_mut(LevelId(order.level_id().value())) 87 | .unwrap() 88 | .decr(qty); 89 | } 90 | 91 | /// Removes an order from the order book and deallocates the associated level if it becomes empty. 92 | #[inline] 93 | pub fn remove_order(&mut self, order: &mut Order) { 94 | let lvl = self.level_pool.get_mut(order.level_id()).unwrap(); 95 | lvl.decr(order.qty()); 96 | 97 | if lvl.size().is_empty() { 98 | let level_price = lvl.price(); 99 | let levels = if level_price.is_bid() { 100 | &mut self.bids 101 | } else { 102 | &mut self.asks 103 | }; 104 | levels.remove(level_price); 105 | self.level_pool.free(LevelId(order.level_id().value())); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /optimized-lob/src/level.rs: -------------------------------------------------------------------------------- 1 | // level.rs 2 | 3 | use crate::{price::Price, quantity::Qty}; 4 | use std::cmp::Ordering; 5 | use std::fmt::Debug; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 8 | pub struct LevelId(pub u32); 9 | 10 | /// Represents the Level ID for a price. 11 | impl LevelId { 12 | #[inline] 13 | pub fn value(&self) -> u32 { 14 | self.0 15 | } 16 | } 17 | 18 | /// Represents the Level for a price. 19 | /// It stores the price and total capacity of the level. 20 | #[derive(Debug, Clone)] 21 | pub struct Level { 22 | price: Price, 23 | size: Qty, 24 | } 25 | 26 | impl Default for Level { 27 | #[inline] 28 | fn default() -> Self { 29 | Self { 30 | price: Price(0), 31 | size: Qty(0), 32 | } 33 | } 34 | } 35 | 36 | impl Level { 37 | #[inline] 38 | pub fn new(price: Price, size: Qty) -> Self { 39 | Self { price, size } 40 | } 41 | 42 | #[inline] 43 | pub fn price(&self) -> Price { 44 | self.price 45 | } 46 | 47 | #[inline] 48 | pub fn size(&self) -> Qty { 49 | self.size 50 | } 51 | 52 | #[inline] 53 | pub fn set_price(&mut self, price: Price) { 54 | self.price = price 55 | } 56 | 57 | #[inline] 58 | pub fn set_size(&mut self, size: Qty) { 59 | self.size = size 60 | } 61 | 62 | #[inline] 63 | pub fn incr(&mut self, size: Qty) { 64 | self.size += size 65 | } 66 | 67 | #[inline] 68 | pub fn decr(&mut self, size: Qty) { 69 | self.size -= size 70 | } 71 | } 72 | 73 | /// Represents a price level that will be used to locate the level in the orderbook. 74 | #[derive(Eq, PartialEq, Clone)] 75 | pub struct PriceLevel { 76 | price: Price, 77 | level_idx: LevelId, 78 | } 79 | 80 | impl Default for PriceLevel { 81 | #[inline] 82 | fn default() -> Self { 83 | Self { 84 | price: Price(0), 85 | level_idx: LevelId(0), 86 | } 87 | } 88 | } 89 | 90 | impl Debug for PriceLevel { 91 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 92 | f.debug_struct("PriceLevel") 93 | .field("price", &self.price) 94 | .field("level_idx", &self.level_idx) 95 | .finish() 96 | } 97 | } 98 | 99 | impl PriceLevel { 100 | #[inline] 101 | pub(crate) fn new(price: Price, level_idx: LevelId) -> Self { 102 | Self { price, level_idx } 103 | } 104 | 105 | #[inline] 106 | pub fn price(&self) -> Price { 107 | self.price 108 | } 109 | 110 | #[inline] 111 | pub fn level_id(&self) -> LevelId { 112 | self.level_idx 113 | } 114 | } 115 | 116 | impl Ord for PriceLevel { 117 | fn cmp(&self, other: &Self) -> Ordering { 118 | self.price.value().cmp(&other.price.value()) 119 | } 120 | } 121 | 122 | impl PartialOrd for PriceLevel { 123 | fn partial_cmp(&self, other: &Self) -> Option { 124 | Some(self.cmp(other)) 125 | } 126 | } 127 | 128 | #[derive(Default, Clone)] 129 | pub struct SortedLevels(Vec); 130 | 131 | impl SortedLevels { 132 | #[inline] 133 | pub fn new() -> Self { 134 | Self(Vec::new()) 135 | } 136 | 137 | #[inline] 138 | pub fn len(&self) -> usize { 139 | self.0.len() 140 | } 141 | 142 | #[inline] 143 | pub fn is_empty(&self) -> bool { 144 | self.0.is_empty() 145 | } 146 | 147 | #[inline] 148 | pub fn get_mut(&mut self, idx: usize) -> &mut PriceLevel { 149 | &mut self.0[idx] 150 | } 151 | 152 | #[inline] 153 | pub fn insert(&mut self, idx: usize, px: PriceLevel) { 154 | self.0.insert(idx, px); 155 | } 156 | 157 | #[inline] 158 | pub fn remove(&mut self, price: Price) { 159 | for px in self.0.iter_mut().rev() { 160 | if px.price == price { 161 | self.0.retain(|x| x.price != price); 162 | break; 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "backtrace" 22 | version = "0.3.69" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 25 | dependencies = [ 26 | "addr2line", 27 | "cc", 28 | "cfg-if", 29 | "libc", 30 | "miniz_oxide", 31 | "object", 32 | "rustc-demangle", 33 | ] 34 | 35 | [[package]] 36 | name = "cc" 37 | version = "1.0.83" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "cfg-if" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 49 | 50 | [[package]] 51 | name = "error-chain" 52 | version = "0.12.4" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 55 | dependencies = [ 56 | "backtrace", 57 | "version_check", 58 | ] 59 | 60 | [[package]] 61 | name = "gimli" 62 | version = "0.28.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 65 | 66 | [[package]] 67 | name = "itch-parser" 68 | version = "0.1.0" 69 | dependencies = [ 70 | "error-chain", 71 | "nom", 72 | ] 73 | 74 | [[package]] 75 | name = "libc" 76 | version = "0.2.148" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" 79 | 80 | [[package]] 81 | name = "memchr" 82 | version = "2.6.4" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 85 | 86 | [[package]] 87 | name = "minimal-lexical" 88 | version = "0.2.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 91 | 92 | [[package]] 93 | name = "miniz_oxide" 94 | version = "0.7.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 97 | dependencies = [ 98 | "adler", 99 | ] 100 | 101 | [[package]] 102 | name = "nom" 103 | version = "7.1.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 106 | dependencies = [ 107 | "memchr", 108 | "minimal-lexical", 109 | ] 110 | 111 | [[package]] 112 | name = "object" 113 | version = "0.32.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 116 | dependencies = [ 117 | "memchr", 118 | ] 119 | 120 | [[package]] 121 | name = "optimized-lob" 122 | version = "0.1.0" 123 | 124 | [[package]] 125 | name = "rustc-demangle" 126 | version = "0.1.23" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 129 | 130 | [[package]] 131 | name = "tests" 132 | version = "0.1.0" 133 | dependencies = [ 134 | "itch-parser", 135 | "optimized-lob", 136 | ] 137 | 138 | [[package]] 139 | name = "version_check" 140 | version = "0.9.4" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 143 | -------------------------------------------------------------------------------- /itch-parser/src/message_stream.rs: -------------------------------------------------------------------------------- 1 | // message_stream.rs 2 | 3 | use super::{ 4 | errors::*, 5 | message::{parse_message, Message}, 6 | }; 7 | use std::{fs::File, io::Read, path::Path}; 8 | 9 | const BUF_SIZE: usize = 64 * 1024; 10 | 11 | /// Represents an iterable stream of ITCH protocol messages. 12 | pub struct MessageStream { 13 | reader: R, 14 | buffer: Box<[u8; BUF_SIZE]>, 15 | buf_start: usize, 16 | buf_end: usize, 17 | bytes_read: usize, 18 | read_calls: u32, 19 | message_ct: u32, // Total messages read so far 20 | in_error_state: bool, 21 | } 22 | 23 | impl MessageStream { 24 | /// Creates a new `MessageStream` from a file at the specified path. 25 | pub fn from_file>(path: P) -> Result> { 26 | let reader = File::open(path)?; 27 | Ok(MessageStream::from_reader(reader)) 28 | } 29 | } 30 | 31 | impl MessageStream { 32 | /// Creates a new `MessageStream` from any type that implements the `Read` trait. 33 | #[inline] 34 | pub fn from_reader(reader: R) -> MessageStream { 35 | MessageStream::new(reader) 36 | } 37 | 38 | /// Initializes a new `MessageStream` with default values. 39 | #[inline] 40 | fn new(reader: R) -> MessageStream { 41 | MessageStream { 42 | reader, 43 | buffer: Box::new([0; BUF_SIZE]), 44 | buf_start: 0, 45 | buf_end: 0, 46 | bytes_read: 0, 47 | read_calls: 0, 48 | message_ct: 0, 49 | in_error_state: false, 50 | } 51 | } 52 | 53 | /// Fetches more bytes from the reader and updates the buffer. 54 | #[inline] 55 | fn fetch_more_bytes(&mut self) -> Result { 56 | self.read_calls += 1; 57 | if self.buf_end == BUF_SIZE { 58 | // Safety Checks 59 | assert!(self.buf_start > BUF_SIZE / 2); 60 | assert!(BUF_SIZE - self.buf_start < 100); 61 | 62 | { 63 | let (left, right) = self.buffer.split_at_mut(self.buf_start); 64 | left[..right.len()].copy_from_slice(right); 65 | self.buf_start = 0; 66 | self.buf_end = right.len(); 67 | } 68 | } 69 | Ok(self.reader.read(&mut self.buffer[self.buf_end..])?) 70 | } 71 | } 72 | 73 | impl Iterator for MessageStream { 74 | type Item = Result; 75 | 76 | #[inline] 77 | fn next(&mut self) -> Option> { 78 | { 79 | let buf = &self.buffer[self.buf_start..self.buf_end]; 80 | match parse_message(buf) { 81 | Ok((rest, msg)) => { 82 | self.buf_start = self.buf_end - rest.len(); 83 | self.message_ct += 1; 84 | self.in_error_state = false; 85 | return Some(Ok(msg)); 86 | } 87 | Err(nom::Err::Error(_e)) | Err(nom::Err::Failure(_e)) => { 88 | return if self.in_error_state { 89 | None 90 | } else { 91 | self.in_error_state = true; 92 | Some(Err(format!( 93 | "Parse failed: {:?}, buffer context", 94 | &self.buffer[self.buf_start..self.buf_start + 20] 95 | ) 96 | .into())) 97 | } 98 | } 99 | Err(nom::Err::Incomplete(_)) => { 100 | // Fall through to below... necessary to appease borrow checker. 101 | } 102 | } 103 | } 104 | match self.fetch_more_bytes() { 105 | Ok(0) => { 106 | // If we get EOF, return None 107 | if self.buf_start == self.buf_end { 108 | return None; 109 | } 110 | if self.in_error_state { 111 | None 112 | } else { 113 | self.in_error_state = true; 114 | Some(Err("Unexpected EOF".into())) 115 | } 116 | } 117 | Ok(ct) => { 118 | self.buf_end += ct; 119 | self.bytes_read += ct; 120 | self.next() 121 | } 122 | Err(e) => { 123 | if self.in_error_state { 124 | None 125 | } else { 126 | self.in_error_state = true; 127 | Some(Err(e)) 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # matching-engine-rs 2 | [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) 3 | [![Version](https://img.shields.io/badge/version-v1.0.0-blue.svg)](https://semver.org/) 4 | [![GitHub Stars](https://img.shields.io/github/stars/amankrx/matching-engine-rs?logo=github&label=Stars&color=yellow)](https://github.com/amankrx/matching-engine-rs) 5 | 6 | This is an attempt to implement a matching engine with Rust. Currently, I have created an implementation of a Limit Order Book. The LOB offers fast processing of the ITCH data clocking at 11.3 Million messages per second (or a latency of 88 ns) as tested on my [local machine](#device-specifications). Checkout the [Performance](#performance) section for more information. 7 | 8 | ## Table of Contents 9 | - [Project Structure](#project-structure) 10 | - [Build, Run, and Test](#build-run-and-test) 11 | - [Device Specifications](#device-specifications) 12 | - [Performance](#performance) 13 | - [ITCH Specifications](#itch-specifications) 14 | - [Contributing](#contributing) 15 | - [Credits](#credits) 16 | - [License](#license) 17 | 18 | ## Project Structure 19 | These project consists of two libraries: 20 | - **[itch-parser](itch-parser)**: This library is responsible for managing the processing of *NASDAQ ITCH 5.0* protocol data. It parses the useful fields that will be required for the Limit Order Book. The remaining fields are skipped using placeholders. Check out the folder's [README](itch-parser/README.md) for more information. 21 | - **[optimized-lob](optimized-lob)**: This library contains a streamlined and efficient implementation of a Limit Order Book (LOB). It is worth noting that the LOB simply stores a few useful fields that will be required for creating a LOB. It just keeps an aggregate quantities at each level. Check out the folder's [README](optimized-lob/README.md) for more information. 22 | 23 | Apart from that, there is a testing suite for both libraries that can be found in the "[tests](tests)" directory. 24 | 25 | ## Build, Run, and Test 26 | Make sure you have Rust installed. Also, you must download the NASDAQ ITCH 5.0 data whose instructions are available in the [ITCH Specifications](#ITCH-Specifications). 27 | All of these operations are performed in the `tests` directory. 28 | ```bash 29 | cd tests 30 | ``` 31 | ### Build 32 | ```bash 33 | cargo build 34 | ``` 35 | or 36 | ```bash 37 | cargo build --release 38 | ``` 39 | 40 | ### Running the LOB 41 | ```bash 42 | ITCH_DATA=PATH_TO_ITCH_DATA_FILE cargo run 43 | ``` 44 | or 45 | ```bash 46 | ITCH_DATA=PATH_TO_ITCH_DATA_FILE cargo run --release 47 | ``` 48 | 49 | ### Running the ITCH parser 50 | ```bash 51 | ITCH_DATA=PATH_TO_ITCH_DATA_FILE cargo run -- --itch-parser 52 | ``` 53 | or 54 | ```bash 55 | ITCH_DATA=PATH_TO_ITCH_DATA_FILE cargo run --release -- --itch-parser 56 | ``` 57 | 58 | ### Testing 59 | ```bash 60 | cargo test 61 | ``` 62 | 63 | 64 | ## Device Specifications 65 | At the time of testing: 66 | ```text 67 | Device: MacBook Air M2 68 | CPU architecture: Apple M2 69 | CPU logical cores: 8 70 | CPU physical cores: 8 71 | RAM total: 16 GB 72 | RAM free: 11.5 GB 73 | ``` 74 | ## Performance 75 | 76 | ### ITCH Processing 77 | 78 | ```text 79 | ITCH Parser Processing... 80 | 81 | Success... 82 | 83 | ITCH Parsing Statistics: 84 | Total Messages: 268744780 85 | Total Time: 6.082 seconds 86 | Speed: 44189583 msg/second 87 | Latency: 22 ns 88 | ``` 89 | 90 | ### LOB Performance 91 | 92 | ```text 93 | LOB Processing... 94 | 95 | Success... 96 | 97 | Performance Metrics: 98 | Total Messages: 268744780 99 | ITCH Latency: 88 ns 100 | Total Time: 23.660 seconds 101 | Speed: 11358746 msg/second 102 | 103 | Orderbook Statistics: 104 | Total Add Orders: 118631456 105 | Total Execute Orders: 5822741 106 | Total Cancel Orders: 2787676 107 | Total Delete Orders: 114360997 108 | Total Replace Orders: 21639067 109 | ``` 110 | ## ITCH Specifications 111 | 112 | The project follows the `Nasdaq TotalView-ITCH 5.0` standard for the processing of data. 113 | 114 | - [Protocol Specifications](http://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHSpecification.pdf) 115 | - [Binary Specification File](http://www.nasdaqtrader.com/content/technicalSupport/specifications/dataproducts/binaryfile.pdf) 116 | - ITCH data can be downloaded from their website: https://emi.nasdaq.com/ITCH/Nasdaq%20ITCH/ 117 | 118 | I have specifically used their `12302019.NASDAQ_ITCH50` data whose compressed file can be downloaded from [here](https://emi.nasdaq.com/ITCH/Nasdaq%20ITCH/12302019.NASDAQ_ITCH50.gz). 119 | ## Contributing 120 | 121 | Contributions to matching-engine-rs are welcome! If you encounter any issues, have suggestions, or would like to add new features, please feel free to open an issue or submit a pull request. Note that I'm still learning my way around Rust and trading systems, so any feedback is appreciated! 122 | 123 | ## License 124 | 125 | This project is licensed under the [MIT License](LICENSE). 126 | -------------------------------------------------------------------------------- /optimized-lob/src/order.rs: -------------------------------------------------------------------------------- 1 | // order.rs 2 | 3 | use crate::{ 4 | level::LevelId, 5 | quantity::Qty, 6 | utils::{BookId, INITIAL_ORDER_COUNT}, 7 | }; 8 | use std::fmt::Debug; 9 | 10 | /// Unique identifier for an order. 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 12 | pub struct OrderId(pub u32); 13 | 14 | /// Represents an order in the trading system. 15 | #[derive(Default, Clone)] 16 | pub struct Order { 17 | level_id: LevelId, 18 | book_id: BookId, 19 | qty: Qty, 20 | } 21 | 22 | impl Debug for Order { 23 | /// Formats the Order for debugging purposes. 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | f.debug_struct("Order") 26 | .field("level_id", &self.level_id) 27 | .field("book_id", &self.book_id) 28 | .field("qty", &self.qty) 29 | .finish() 30 | } 31 | } 32 | 33 | impl PartialEq for Order { 34 | fn eq(&self, other: &Self) -> bool { 35 | self.level_id == other.level_id && self.book_id == other.book_id && self.qty == other.qty 36 | } 37 | } 38 | 39 | impl AsRef for Order { 40 | fn as_ref(&self) -> &Order { 41 | self 42 | } 43 | } 44 | 45 | impl Order { 46 | /// Creates a new order with the given quantity, level ID, and book ID. 47 | #[inline] 48 | pub fn new(qty: Qty, level_id: LevelId, book_id: BookId) -> Self { 49 | Self { 50 | level_id, 51 | book_id, 52 | qty, 53 | } 54 | } 55 | 56 | /// Replaces the contents of the order with another order. 57 | #[inline] 58 | pub fn replace(&mut self, order: Order) { 59 | self.level_id = order.level_id; 60 | self.book_id = order.book_id; 61 | self.qty = order.qty; 62 | } 63 | 64 | /// Gets the quantity of the order. 65 | #[inline] 66 | pub fn qty(&self) -> Qty { 67 | self.qty 68 | } 69 | 70 | /// Gets the book ID associated with the order. 71 | #[inline] 72 | pub fn book_id(&self) -> BookId { 73 | self.book_id 74 | } 75 | 76 | /// Gets the level ID associated with the order. 77 | #[inline] 78 | pub fn level_id(&self) -> LevelId { 79 | self.level_id 80 | } 81 | 82 | /// Sets the quantity of the order. 83 | #[inline] 84 | pub fn set_qty(&mut self, qty: Qty) { 85 | self.qty = qty; 86 | } 87 | 88 | /// Sets the book ID of the order. 89 | #[inline] 90 | pub fn set_book_id(&mut self, book_id: BookId) { 91 | self.book_id = book_id; 92 | } 93 | 94 | /// Sets the level ID of the order. 95 | #[inline] 96 | pub fn set_level_id(&mut self, level_id: LevelId) { 97 | self.level_id = level_id; 98 | } 99 | } 100 | 101 | /// Data structure for mapping OrderIds to Order objects. 102 | pub struct OidMap { 103 | data: Vec>, 104 | } 105 | 106 | impl Default for OidMap { 107 | /// Creates a default OidMap instance. 108 | #[inline] 109 | fn default() -> Self { 110 | Self::new() 111 | } 112 | } 113 | 114 | impl OidMap { 115 | /// Creates a new OidMap with an initial capacity. 116 | #[inline] 117 | pub fn new() -> Self { 118 | OidMap { 119 | data: vec![None; INITIAL_ORDER_COUNT], // Use a fixed-size array 120 | } 121 | } 122 | 123 | /// Reserves space for an OrderId in the map. 124 | #[inline] 125 | pub fn reserve(&mut self, oid: OrderId) { 126 | let idx = oid.0 as usize; 127 | if idx >= self.data.len() { 128 | self.data.resize(idx + 1, None); 129 | } 130 | } 131 | 132 | /// Inserts an Order into the map with a specific OrderId. 133 | #[inline] 134 | pub fn insert(&mut self, oid: OrderId, value: &Order) { 135 | let idx = oid.0 as usize; 136 | if idx >= self.data.len() { 137 | self.data.resize(idx + 1, None); 138 | } 139 | self.data[idx] = Some(value.clone()); // Clone only when necessary 140 | } 141 | 142 | /// Removes an Order from the map by its OrderId. 143 | #[inline] 144 | pub fn remove(&mut self, oid: OrderId) { 145 | let idx = oid.0 as usize; 146 | if idx < self.data.len() { 147 | self.data[idx] = None; 148 | } 149 | } 150 | 151 | /// Updates the quantity of an Order in the map by its OrderId. 152 | #[inline] 153 | pub fn update_qty(&mut self, oid: OrderId, qty: Qty) { 154 | let idx = oid.0 as usize; 155 | if idx < self.data.len() { 156 | if let Some(order) = &mut self.data[idx] { 157 | order.qty -= qty; 158 | } 159 | } 160 | } 161 | 162 | /// Gets a reference to an Order by its OrderId. 163 | #[inline] 164 | pub fn get(&self, oid: OrderId) -> Option<&Order> { 165 | let idx = oid.0 as usize; 166 | self.data.get(idx)?.as_ref() 167 | } 168 | 169 | /// Gets a mutable reference to an Order by its OrderId. 170 | #[inline] 171 | pub fn get_mut(&mut self, oid: OrderId) -> Option<&mut Order> { 172 | let idx = oid.0 as usize; 173 | self.data.get_mut(idx)?.as_mut() 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tests/src/test_lob.rs: -------------------------------------------------------------------------------- 1 | extern crate itch_parser; 2 | extern crate optimized_lob; 3 | 4 | use itch_parser::Body::{ 5 | AddOrder, DeleteOrder, OrderCancelled, OrderExecuted, OrderExecutedWithPrice, ReplaceOrder, 6 | }; 7 | use itch_parser::MessageStream; 8 | use optimized_lob::{ 9 | order::OrderId, orderbook_manager::OrderBookManager, quantity::Qty, utils::BookId, 10 | }; 11 | use std::path::Path; 12 | use std::time::Instant; 13 | 14 | pub fn test_lob(file_path: &str) { 15 | let path_to_market_data = Path::new(file_path); 16 | let stream = MessageStream::from_file(path_to_market_data).unwrap(); 17 | 18 | println!("------------------------------------"); 19 | println!("LOB Processing...\n"); 20 | 21 | // Counters 22 | let mut messages = 0; 23 | let mut add_order_count = 0; 24 | let mut execute_orders_count = 0; 25 | let mut cancel_order_count = 0; 26 | let mut delete_order_count = 0; 27 | let mut replace_order_count = 0; 28 | 29 | let start = Instant::now(); 30 | let mut orderbook = OrderBookManager::new(); // Initialize the orderbook 31 | 32 | // Process messages 33 | for msg in stream { 34 | let unwrapped_msg = msg.unwrap(); 35 | let stock_locate = unwrapped_msg.stock_locate; 36 | 37 | match unwrapped_msg.body { 38 | AddOrder { 39 | order_id, 40 | is_bid, 41 | shares, 42 | stock: _, 43 | price, 44 | } => { 45 | let oid: Option = order_id.try_into().ok(); 46 | 47 | match oid { 48 | Some(id) => { 49 | orderbook.add_order( 50 | OrderId(id), 51 | BookId(stock_locate), 52 | Qty(shares), 53 | price, 54 | is_bid, 55 | ); 56 | } 57 | None => { 58 | // Conversion failed due to overflow, handle the error here 59 | println!("Failed to convert Order ID u32 due to overflow"); 60 | break; 61 | } 62 | } 63 | add_order_count += 1; 64 | } 65 | OrderExecuted { 66 | order_id, 67 | shares, 68 | match_number: _, 69 | } => { 70 | let oid: Option = order_id.try_into().ok(); 71 | match oid { 72 | Some(id) => { 73 | orderbook.execute_order(OrderId(id), Qty(shares)); 74 | } 75 | None => { 76 | // Conversion failed due to overflow, handle the error here 77 | println!("Failed to convert Order ID u32 due to overflow"); 78 | break; 79 | } 80 | } 81 | execute_orders_count += 1; 82 | } 83 | OrderExecutedWithPrice { 84 | order_id, 85 | shares, 86 | match_number: _, 87 | printable: _, 88 | price: _, 89 | } => { 90 | let oid: Option = order_id.try_into().ok(); 91 | match oid { 92 | Some(id) => { 93 | orderbook.execute_order(OrderId(id), Qty(shares)); 94 | } 95 | None => { 96 | // Conversion failed due to overflow, handle the error here 97 | println!("Failed to convert Order ID u32 due to overflow"); 98 | break; 99 | } 100 | } 101 | execute_orders_count += 1; 102 | } 103 | OrderCancelled { order_id, shares } => { 104 | let oid: Option = order_id.try_into().ok(); 105 | match oid { 106 | Some(id) => { 107 | orderbook.cancel_order(OrderId(id), Qty(shares)); 108 | } 109 | None => { 110 | // Conversion failed due to overflow, handle the error here 111 | println!("Failed to convert Order ID u32 due to overflow"); 112 | break; 113 | } 114 | } 115 | cancel_order_count += 1; 116 | } 117 | DeleteOrder { order_id } => { 118 | let oid: Option = order_id.try_into().ok(); 119 | match oid { 120 | Some(id) => { 121 | orderbook.remove_order(OrderId(id)); 122 | } 123 | None => { 124 | // Conversion failed due to overflow, handle the error here 125 | println!("Failed to convert Order ID u32 due to overflow"); 126 | break; 127 | } 128 | } 129 | delete_order_count += 1; 130 | } 131 | ReplaceOrder { 132 | old_order_id, 133 | new_order_id, 134 | shares, 135 | price, 136 | } => { 137 | let old_oid: Option = old_order_id.try_into().ok(); 138 | let new_oid: Option = new_order_id.try_into().ok(); 139 | 140 | match (old_oid, new_oid) { 141 | (Some(id), Some(new_id)) => { 142 | orderbook.replace_order(OrderId(id), OrderId(new_id), Qty(shares), price); 143 | } 144 | _ => { 145 | // Conversion failed due to overflow, handle the error here 146 | println!("Failed to convert Order ID u32 due to overflow"); 147 | break; 148 | } 149 | } 150 | 151 | replace_order_count += 1; 152 | } 153 | _ => {} 154 | } 155 | 156 | messages += 1; 157 | } 158 | 159 | let duration = Instant::now() - start; 160 | let speed = messages as f64 / duration.as_secs_f64(); 161 | println!("Success...\n"); 162 | println!("Performance Metrics:"); 163 | println!("Total Messages: {}", messages); 164 | println!( 165 | "ITCH Latency: {} ns", 166 | duration.as_nanos() / messages as u128 167 | ); 168 | println!("Total Time: {:.3} seconds", duration.as_secs_f64()); 169 | println!("Speed: {} msg/second\n", speed as u32); 170 | println!("Orderbook Statistics:"); 171 | println!("Total Add Orders: {}", add_order_count); 172 | println!("Total Execute Orders: {}", execute_orders_count); 173 | println!("Total Cancel Orders: {}", cancel_order_count); 174 | println!("Total Delete Orders: {}", delete_order_count); 175 | println!("Total Replace Orders: {}", replace_order_count); 176 | println!("------------------------------------"); 177 | } 178 | -------------------------------------------------------------------------------- /optimized-lob/src/orderbook_manager.rs: -------------------------------------------------------------------------------- 1 | // orderbook_manager.rs 2 | 3 | use crate::{ 4 | level::LevelId, 5 | order::{OidMap, Order, OrderId}, 6 | orderbook::OrderBook, 7 | price::Price, 8 | quantity::Qty, 9 | utils::{BookId, MAX_BOOKS}, 10 | }; 11 | 12 | /// Manages multiple order books and orders. 13 | pub struct OrderBookManager { 14 | pub books: Vec>, // A mapping of book IDs to order books. 15 | pub oid_map: OidMap, // A mapping of order IDs to order objects. 16 | } 17 | 18 | impl Default for OrderBookManager { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl OrderBookManager { 25 | /// Creates a new OrderBookManager with empty books and an OidMap. 26 | #[inline] 27 | pub fn new() -> Self { 28 | Self { 29 | books: vec![None; MAX_BOOKS], 30 | oid_map: OidMap::new(), 31 | } 32 | } 33 | 34 | /// Adds a new order to the order book based on the provided parameters. 35 | /// ## Arguments: 36 | /// - `order_id`: The order ID for the order. Represented as unique reference number. 37 | /// - `book_id`: The identifier for the book where the order will be placed. Represents as stock locate. 38 | /// - `qty`: The quantity of the order. Represented as shares in the orderbook. 39 | /// - `price32`: The price of the order as a 32-bit unsigned integer. Return the Price(4) in the orderbook. 40 | /// - `is_bid`: A flag indicating whether the order is a bid (true) or ask (false). Return the Buy/Sell Indicator as boolean. 41 | /// 42 | /// ## Example: 43 | /// ``` 44 | /// let mut orderbook_manager = OrderBookManager::new(); 45 | /// 46 | /// orderbook_manager.add_order( 47 | /// OrderId(0), // Order ID 48 | /// BookId(0), // Book ID 49 | /// Qty(100), // Quantity 50 | /// 600, // Price 51 | /// true, // Is Bid 52 | /// ); 53 | /// ``` 54 | #[inline] 55 | pub fn add_order( 56 | &mut self, 57 | order_id: OrderId, 58 | book_id: BookId, 59 | qty: Qty, 60 | price32: u32, 61 | is_bid: bool, 62 | ) { 63 | let price_i32 = if is_bid { 64 | price32 as i32 65 | } else { 66 | -(price32 as i32) 67 | }; 68 | 69 | // Create a Price(i32) from the adjusted price_i32. 70 | let price = Price(price_i32); 71 | 72 | self.oid_map.reserve(order_id); 73 | 74 | let mut order = Order::new(qty, LevelId(0), book_id); 75 | 76 | // Check if the book for the given book_id exists; if not, create it. 77 | if self.books[book_id.value() as usize].is_none() { 78 | self.books[book_id.value() as usize] = Some(OrderBook::new()); 79 | } 80 | if let Some(orderbook) = self.books.get_mut(book_id.value() as usize).unwrap() { 81 | orderbook.add_order(&mut order, price, qty); 82 | } 83 | self.oid_map.insert(order_id, &order); 84 | } 85 | 86 | /// Removes an order from the order book based on its order ID. 87 | /// ## Arguments: 88 | /// - `order_id`: The order ID for the order. Represented as unique reference number. 89 | /// ## Example: 90 | /// ``` 91 | /// let mut orderbook_manager = OrderBookManager::new(); 92 | /// 93 | /// orderbook.remove_order(OrderId(0)); 94 | /// ``` 95 | #[inline] 96 | pub fn remove_order(&mut self, order_id: OrderId) { 97 | if let Some(order) = self.oid_map.get_mut(order_id) { 98 | if let Some(orderbook) = self 99 | .books 100 | .get_mut(order.book_id().value() as usize) 101 | .unwrap() 102 | { 103 | orderbook.remove_order(order); 104 | } 105 | } 106 | self.oid_map.remove(order_id); 107 | } 108 | 109 | /// Cancels an order by reducing its quantity in the order book. 110 | /// ## Arguments: 111 | /// - `order_id`: The order ID for the order. Represented as unique reference number. 112 | /// - `qty`: The quantity of the order to be cancelled. Represented as shares in the orderbook. 113 | /// ## Example: 114 | /// ``` 115 | /// let mut orderbook_manager = OrderBookManager::new(); 116 | /// 117 | /// orderbook.cancel_order(OrderId(0), Qty(100)); 118 | /// ``` 119 | #[inline] 120 | pub fn cancel_order(&mut self, order_id: OrderId, qty: Qty) { 121 | if let Some(order) = self.oid_map.get_mut(order_id) { 122 | if let Some(orderbook) = self 123 | .books 124 | .get_mut(order.book_id().value() as usize) 125 | .unwrap() 126 | { 127 | orderbook.reduce_order(order, qty); 128 | } 129 | } 130 | self.oid_map.update_qty(order_id, qty); 131 | } 132 | 133 | /// Executes an order by either removing it completely or reducing its quantity. 134 | /// ## Arguments: 135 | /// - `order_id`: The order ID for the order. Represented as unique reference number. 136 | /// - `qty`: The quantity of the order to be executed. Represented as shares in the orderbook. 137 | /// ## Example: 138 | /// ``` 139 | /// let mut orderbook_manager = OrderBookManager::new(); 140 | /// 141 | /// orderbook.execute_order(OrderId(0), Qty(100)); 142 | /// ``` 143 | #[inline] 144 | pub fn execute_order(&mut self, order_id: OrderId, qty: Qty) { 145 | if let Some(order) = self.oid_map.get_mut(order_id) { 146 | if order.qty() == qty { 147 | if let Some(orderbook) = self 148 | .books 149 | .get_mut(order.book_id().value() as usize) 150 | .unwrap() 151 | { 152 | orderbook.remove_order(order); 153 | } 154 | self.oid_map.remove(order_id); 155 | } else { 156 | if let Some(orderbook) = self 157 | .books 158 | .get_mut(order.book_id().value() as usize) 159 | .unwrap() 160 | { 161 | orderbook.reduce_order(order, qty); 162 | } 163 | self.oid_map.update_qty(order_id, qty); 164 | } 165 | } 166 | } 167 | 168 | /// Replaces an existing order with a new order based on order IDs and new parameters. 169 | /// ## Arguments: 170 | /// - `order_id`: The order ID for the order to be replaced. Represented as Original unique reference number. 171 | /// - `new_order_id`: The new order ID for the order that has to be replaced. Represented as the new unique reference number. 172 | /// - `new_qty`: The quantity of the new order. Represented as shares in the orderbook. 173 | /// - `new_price`: The price of the new order as a 32-bit unsigned integer. Return the Price(4) in the orderbook. 174 | /// 175 | /// ## Example: 176 | /// ``` 177 | /// let mut orderbook_manager = OrderBookManager::new(); 178 | /// 179 | /// orderbook_manager.replace_order( 180 | /// OrderId(0), // Old Order ID 181 | /// OrderId(0), // New Order ID 182 | /// Qty(200), // Quantity 183 | /// 500, // Price 184 | /// ); 185 | /// ``` 186 | #[inline] 187 | pub fn replace_order( 188 | &mut self, 189 | order_id: OrderId, 190 | new_order_id: OrderId, 191 | new_qty: Qty, 192 | new_price: u32, 193 | ) { 194 | let order = self.oid_map.get_mut(order_id); 195 | let mut is_bid = true; 196 | let mut book_id = BookId(0); 197 | if let Some(order) = order { 198 | if let Some(book) = self 199 | .books 200 | .get_mut(order.book_id().value() as usize) 201 | .unwrap() 202 | { 203 | is_bid = book 204 | .level_pool 205 | .get(order.level_id()) 206 | .unwrap() 207 | .price() 208 | .is_bid(); 209 | book_id = order.book_id(); 210 | book.remove_order(order); 211 | } 212 | self.oid_map.remove(order_id); 213 | } 214 | self.add_order(new_order_id, book_id, new_qty, new_price, is_bid); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /itch-parser/src/message.rs: -------------------------------------------------------------------------------- 1 | // message.rs 2 | 3 | use super::body::{parse_system_event, Body}; 4 | use super::utils::{be_u48, char_to_bool}; 5 | use nom::{ 6 | bytes::streaming::take, 7 | character::streaming::char, 8 | combinator::map_res, 9 | number::streaming::{be_u16, be_u32, be_u64, be_u8}, 10 | sequence::tuple, 11 | IResult, Parser, 12 | }; 13 | 14 | /// The Message struct. Contains the parsed values of a message. 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub struct Message { 17 | /// Message Type (tag) 18 | pub tag: u8, 19 | /// Integer identifying the underlying instrument updated daily (stock_locate) 20 | pub stock_locate: u16, 21 | /// NASDAQ internal tracking number (tracking_number) 22 | pub tracking_number: u16, 23 | /// Nanoseconds since midnight (timestamp) 24 | pub timestamp: u64, 25 | /// Body of one of the supported message types 26 | pub body: Body, 27 | } 28 | 29 | /// Parses a complete message from input bytes. 30 | /// 31 | /// # Arguments 32 | /// `input` - Input bytes 33 | /// 34 | /// # Returns 35 | /// Returns a `Result` containing the parsed `Message` or an error. 36 | #[inline] 37 | pub fn parse_message(input: &[u8]) -> IResult<&[u8], Message> { 38 | // Parse the first 16 bits as an unsigned 16-bit integer and discard it. 39 | let (input, _) = be_u16(input)?; 40 | 41 | // Parse the next 8 bits as an unsigned 8-bit integer, representing the message tag. 42 | let (input, tag) = be_u8(input)?; 43 | 44 | // Parse the next 96 bits as a tuple containing stock_locate, tracking_number, timestamp, 45 | // and the message body. 46 | let (input, (stock_locate, tracking_number, timestamp, body)) = 47 | tuple((be_u16, be_u16, be_u48, |input| parse_body(input, tag)))(input)?; 48 | 49 | // Create and return a Message struct with the parsed values. 50 | Ok(( 51 | input, 52 | Message { 53 | tag, 54 | stock_locate, 55 | tracking_number, 56 | timestamp, 57 | body, 58 | }, 59 | )) 60 | } 61 | 62 | /// Function to parse the body of a message based on its tag. 63 | #[inline] 64 | fn parse_body(input: &[u8], tag: u8) -> IResult<&[u8], Body> { 65 | match tag { 66 | // Handles the `Add Order` message. 67 | b'A' => { 68 | let (input, (order_id, is_bid, shares, stock, price)) = tuple(( 69 | be_u64, 70 | char('B').map(|_| true).or(char('S').map(|_| false)), 71 | be_u32, 72 | be_u64, 73 | be_u32, 74 | ))(input)?; 75 | Ok(( 76 | input, 77 | Body::AddOrder { 78 | order_id, 79 | is_bid, 80 | shares, 81 | stock, 82 | price, 83 | }, 84 | )) 85 | } 86 | // Handles the `Broken Trade` message. 87 | b'B' => { 88 | let (input, _) = take(8usize)(input)?; 89 | Ok((input, Body::Pass(()))) 90 | } 91 | // Handles the `Order Executed with Price` message. 92 | b'C' => { 93 | let (input, (order_id, shares, match_number, printable, price)) = 94 | tuple((be_u64, be_u32, be_u64, map_res(be_u8, char_to_bool), be_u32))(input)?; 95 | Ok(( 96 | input, 97 | Body::OrderExecutedWithPrice { 98 | order_id, 99 | shares, 100 | match_number, 101 | printable, 102 | price, 103 | }, 104 | )) 105 | } 106 | // Handles the `Order Delete` message. 107 | b'D' => { 108 | let (input, order_id) = be_u64(input)?; 109 | Ok((input, Body::DeleteOrder { order_id })) 110 | } 111 | // Handles the `Order Executed` message. 112 | b'E' => { 113 | let (input, (order_id, shares, match_number)) = tuple((be_u64, be_u32, be_u64))(input)?; 114 | Ok(( 115 | input, 116 | Body::OrderExecuted { 117 | order_id, 118 | shares, 119 | match_number, 120 | }, 121 | )) 122 | } 123 | // Handles the `Add Order with MPID Attribution` message. 124 | b'F' => { 125 | let (input, (order_id, is_bid, shares, stock, price, _m_pid)) = tuple(( 126 | be_u64, 127 | char('B').map(|_| true).or(char('S').map(|_| false)), 128 | be_u32, 129 | be_u64, 130 | be_u32, 131 | be_u32, 132 | ))(input)?; 133 | Ok(( 134 | input, 135 | Body::AddOrder { 136 | order_id, 137 | is_bid, 138 | shares, 139 | stock, 140 | price, 141 | }, 142 | )) 143 | } 144 | // Handles the `Stock Trading Action` message. 145 | b'H' => { 146 | let (input, _) = take(14usize)(input)?; 147 | Ok((input, Body::Pass(()))) 148 | } 149 | // Handles the `Net Order Imbalance Indicator` message. 150 | b'I' => { 151 | let (input, _) = take(39usize)(input)?; 152 | Ok((input, Body::Pass(()))) 153 | } 154 | // Handles the `LULD Auction Collar` message. 155 | b'J' => { 156 | let (input, _) = take(24usize)(input)?; 157 | Ok((input, Body::Pass(()))) 158 | } 159 | // Handles the `Quoting Period Update` message. 160 | b'K' => { 161 | let (input, _) = take(17usize)(input)?; 162 | Ok((input, Body::Pass(()))) 163 | } 164 | // Handles the `Market Participant Position` message. 165 | b'L' => { 166 | let (input, _) = take(15usize)(input)?; 167 | Ok((input, Body::Pass(()))) 168 | } 169 | // Handles the `Retail Price Improvement Indicator` message. 170 | b'N' => { 171 | let (input, _) = take(9usize)(input)?; 172 | Ok((input, Body::Pass(()))) 173 | } 174 | // Handles the `Non-Cross Trade` message. 175 | b'P' => { 176 | let (input, _) = take(33usize)(input)?; 177 | Ok((input, Body::Pass(()))) 178 | } 179 | // Handles the `Cross Trade` message. 180 | b'Q' => { 181 | let (input, _) = take(29usize)(input)?; 182 | Ok((input, Body::Pass(()))) 183 | } 184 | // Handles the `Stock Directory` message. 185 | b'R' => { 186 | let (input, _) = take(28usize)(input)?; 187 | Ok((input, Body::Pass(()))) 188 | } 189 | // Handles the `System Event` message. 190 | b'S' => { 191 | let (input, event_code) = parse_system_event(input)?; 192 | Ok((input, Body::SystemEvent { event: event_code })) 193 | } 194 | // Handles the `Order Replace` message. 195 | b'U' => { 196 | let (input, (old_order_id, new_order_id, shares, price)) = 197 | tuple((be_u64, be_u64, be_u32, be_u32))(input)?; 198 | Ok(( 199 | input, 200 | Body::ReplaceOrder { 201 | old_order_id, 202 | new_order_id, 203 | shares, 204 | price, 205 | }, 206 | )) 207 | } 208 | // Handles the `MWCB Decline Level` message. 209 | b'V' => { 210 | let (input, _) = take(24usize)(input)?; 211 | Ok((input, Body::Pass(()))) 212 | } 213 | // Handles the `MWCB Status` message. 214 | b'W' => { 215 | let (input, _) = take(1usize)(input)?; 216 | Ok((input, Body::Pass(()))) 217 | } 218 | // Handles the `Order Cancel` message. 219 | b'X' => { 220 | let (input, (order_id, shares)) = tuple((be_u64, be_u32))(input)?; 221 | Ok((input, Body::OrderCancelled { order_id, shares })) 222 | } 223 | // Handles the `Reg SHO Short Sale Price Test Restricted Indicator` message. 224 | b'Y' => { 225 | let (input, _) = take(9usize)(input)?; 226 | Ok((input, Body::Pass(()))) 227 | } 228 | // Return an error if the tag doesn't match any known message type. 229 | _ => Err(nom::Err::Error(nom::error::Error::new( 230 | input, 231 | nom::error::ErrorKind::Tag, 232 | ))), 233 | } 234 | } 235 | --------------------------------------------------------------------------------