├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── rust.yml ├── src ├── main.rs ├── order.rs ├── account.rs └── book.rs ├── README.md ├── Cargo.toml ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jmcph4] 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod account; 2 | mod order; 3 | mod book; 4 | 5 | fn main() { 6 | println!("Hello, world!"); 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ironlobe # 2 | 3 | Ironlobe is a fast price-time-quantity limit order book (LOB) matching engine written in Rust. 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ironlobe" 3 | version = "0.1.0" 4 | authors = ["jmcph4 "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | chrono = "0.4.9" 11 | ordered-float = "1.0.2" 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jack McPherson 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /src/order.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | 3 | use chrono::{DateTime, Utc}; 4 | 5 | use crate::account; 6 | 7 | pub enum OrderError { 8 | OrderStillActive 9 | } 10 | 11 | pub type OrderId = u128; 12 | 13 | #[derive(Debug, Clone, PartialEq)] 14 | #[allow(dead_code)] 15 | pub enum OrderType { 16 | Bid, 17 | Ask 18 | } 19 | 20 | #[derive(Debug, Clone, PartialEq)] 21 | pub struct Order { 22 | id: u128, 23 | owner: account::Account, 24 | ticker: String, 25 | order_type: OrderType, 26 | price: f64, 27 | quantity: u128, 28 | created: DateTime, 29 | modified: DateTime, 30 | cancelled: DateTime, 31 | active: bool 32 | } 33 | 34 | #[allow(dead_code)] 35 | impl Order { 36 | pub fn new(id: u128, owner: account::Account, ticker: String, 37 | order_type: OrderType, price: f64, quantity: u128) -> Order { 38 | Order { 39 | id: id, 40 | owner: owner, 41 | ticker: ticker.clone(), 42 | order_type: order_type, 43 | price: price, 44 | quantity: quantity, 45 | created: Utc::now(), 46 | modified: Utc::now(), 47 | cancelled: Utc::now(), 48 | active: true 49 | } 50 | } 51 | 52 | pub fn get_id(&self) -> u128 { 53 | self.id 54 | } 55 | 56 | pub fn get_owner(&self) -> account::Account { 57 | self.owner.clone() 58 | } 59 | 60 | pub fn get_owner_mut(&mut self) -> &mut account::Account { 61 | &mut self.owner 62 | } 63 | 64 | pub fn get_ticker(&self) -> String { 65 | self.ticker.clone() 66 | } 67 | 68 | pub fn get_order_type(&self) -> OrderType { 69 | self.order_type.clone() 70 | } 71 | 72 | pub fn get_price(&self) -> f64 { 73 | self.price 74 | } 75 | 76 | pub fn get_quantity(&self) -> u128 { 77 | self.quantity 78 | } 79 | 80 | pub fn get_created(&self) -> DateTime { 81 | self.created 82 | } 83 | 84 | pub fn get_modified(&self) -> DateTime { 85 | self.modified 86 | } 87 | 88 | pub fn get_cancelled(&self) -> Result, OrderError> { 89 | if self.active { 90 | Ok(self.cancelled) 91 | } else { 92 | Err(OrderError::OrderStillActive) 93 | } 94 | } 95 | 96 | pub fn active(&self) -> bool { 97 | self.active 98 | } 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/account.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub type AccountId = u128; 4 | 5 | #[derive(Debug)] 6 | pub enum AccountError { 7 | AssetNotFound, 8 | } 9 | 10 | #[derive(Debug, Default, Clone, PartialEq)] 11 | pub struct Account { 12 | id: AccountId, 13 | name: String, 14 | balance: f64, 15 | holdings: HashMap 16 | } 17 | 18 | #[allow(dead_code)] 19 | impl Account { 20 | pub fn new(id: AccountId, name: String, balance: f64, 21 | holdings: HashMap) -> Account { 22 | Account {id, name, balance, holdings} 23 | } 24 | 25 | pub fn get_id(&self) -> AccountId { 26 | self.id 27 | } 28 | 29 | pub fn set_id(&mut self, id: AccountId) { 30 | self.id = id; 31 | } 32 | 33 | pub fn get_name(&self) -> String { 34 | self.name.clone() 35 | } 36 | 37 | pub fn set_name(&mut self, name: String) { 38 | self.name = name; 39 | } 40 | 41 | pub fn get_balance(&self) -> f64 { 42 | self.balance 43 | } 44 | 45 | pub fn set_balance(&mut self, balance: f64) { 46 | self.balance = balance 47 | } 48 | 49 | pub fn add_balance(&mut self, balance: f64) { 50 | self.balance += balance 51 | } 52 | 53 | pub fn take_balance(&mut self, balance: f64) { 54 | self.balance -= balance; 55 | } 56 | 57 | pub fn holds(&self, ticker: String) -> bool { 58 | self.holdings.contains_key(&ticker) 59 | } 60 | 61 | pub fn get_holding(&self, ticker: String) -> Result { 62 | if self.holds(ticker.clone()) { 63 | Ok(self.holdings[&ticker]) 64 | } else { 65 | Err(AccountError::AssetNotFound) 66 | } 67 | } 68 | 69 | pub fn set_holding(&mut self, ticker: String, quantity: u128) -> 70 | Result<(), AccountError> { 71 | if self.holds(ticker.clone()) { 72 | self.holdings.remove(&ticker); 73 | self.holdings.insert(ticker, quantity); 74 | } else { 75 | return Err(AccountError::AssetNotFound); 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | pub fn add_holding(&mut self, ticker: String, quantity: u128) -> Result<(), AccountError> { 82 | if self.holds(ticker.clone()) { 83 | self.set_holding(ticker.clone(), self.get_holding(ticker.clone())? + quantity)?; 84 | } else { 85 | return Err(AccountError::AssetNotFound); 86 | } 87 | 88 | Ok(()) 89 | } 90 | 91 | pub fn take_holding(&mut self, ticker: String, quantity: u128) -> Result<(), AccountError> { 92 | if self.holds(ticker.clone()) { 93 | self.set_holding(ticker.clone(), self.get_holding(ticker.clone())? - quantity)?; 94 | } else { 95 | return Err(AccountError::AssetNotFound); 96 | } 97 | 98 | Ok(()) 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "0.1.7" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "chrono" 10 | version = "0.4.9" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "ironlobe" 21 | version = "0.1.0" 22 | dependencies = [ 23 | "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "libc" 29 | version = "0.2.65" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "num-integer" 34 | version = "0.1.41" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 39 | ] 40 | 41 | [[package]] 42 | name = "num-traits" 43 | version = "0.2.9" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | dependencies = [ 46 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "ordered-float" 51 | version = "1.0.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 55 | ] 56 | 57 | [[package]] 58 | name = "redox_syscall" 59 | version = "0.1.56" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | 62 | [[package]] 63 | name = "time" 64 | version = "0.1.42" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "winapi" 74 | version = "0.3.8" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "winapi-i686-pc-windows-gnu" 83 | version = "0.4.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "winapi-x86_64-pc-windows-gnu" 88 | version = "0.4.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | 91 | [metadata] 92 | "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 93 | "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" 94 | "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" 95 | "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 96 | "checksum num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "443c53b3c3531dfcbfa499d8893944db78474ad7a1d87fa2d94d1a2231693ac6" 97 | "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" 98 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 99 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 100 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 101 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 102 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 103 | -------------------------------------------------------------------------------- /src/book.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, BTreeMap, VecDeque}; 2 | use std::iter::FromIterator; 3 | extern crate ordered_float; 4 | 5 | use ordered_float::OrderedFloat; 6 | use crate::order::*; 7 | 8 | #[derive(Debug)] 9 | #[allow(dead_code)] 10 | pub enum BookError { 11 | OrderNotFound, 12 | SideEmpty, 13 | NoTrades, 14 | } 15 | 16 | pub type BookId = u128; 17 | pub type PriceKey = OrderedFloat; 18 | 19 | #[derive(Debug)] 20 | pub struct Book<'a> { 21 | id: BookId, 22 | name: String, 23 | ticker: String, 24 | orders: HashMap, 25 | bids: BTreeMap>, 26 | asks: BTreeMap>, 27 | ltp: f64, 28 | has_traded: bool 29 | } 30 | 31 | #[allow(dead_code, unused_variables)] 32 | impl Book<'_> { 33 | pub fn new(id: u128, name: String, ticker: String) -> Book<'static> { 34 | Book { 35 | id: id, 36 | name: name, 37 | ticker: ticker, 38 | orders: HashMap::new(), 39 | bids: BTreeMap::new(), 40 | asks: BTreeMap::new(), 41 | ltp: 0.00, 42 | has_traded: false 43 | } 44 | } 45 | 46 | pub fn get_id(&self) -> BookId { 47 | self.id 48 | } 49 | 50 | pub fn get_name(&self) -> String { 51 | self.name.clone() 52 | } 53 | 54 | pub fn get_ticker(&self) -> String { 55 | self.ticker.clone() 56 | } 57 | 58 | pub fn get_order(&self, id: OrderId) -> Result<&Order, BookError> { 59 | match self.orders.get(&id) { 60 | Some(order) => Ok(order), 61 | None => Err(BookError::OrderNotFound) 62 | } 63 | } 64 | 65 | pub fn get_order_mut(&mut self, id: OrderId) -> 66 | Result<&mut Order, BookError> { 67 | match self.orders.get_mut(&id) { 68 | Some(order) => Ok(order), 69 | None => Err(BookError::OrderNotFound) 70 | } 71 | } 72 | 73 | pub fn get_ltp(&self) -> Result { 74 | if self.has_traded { 75 | Ok(self.ltp) 76 | } else { 77 | Err(BookError::NoTrades) 78 | } 79 | } 80 | 81 | pub fn submit(&mut self, mut order: Order) -> Result<(), BookError> { 82 | let order_id: OrderId = order.get_id(); 83 | let order_type: OrderType = order.get_order_type(); 84 | let order_price: f64 = order.get_price(); 85 | let order_quantity: u128 = order.get_quantity(); 86 | let order_ticker: String = order.get_ticker(); 87 | 88 | let &mut Book { 89 | ref mut id, 90 | ref mut name, 91 | ref mut ticker, 92 | ref mut orders, 93 | ref mut bids, 94 | ref mut asks, 95 | .. } = self; 96 | 97 | match order_type { 98 | OrderType::Bid => { 99 | let matched: bool = Book::match_order(orders, asks, &mut order)?; 100 | 101 | if !matched { 102 | orders.insert(order_id, order); 103 | 104 | if !bids.contains_key(&OrderedFloat::from(order_price)) { 105 | bids.insert(OrderedFloat::from(order_price), 106 | VecDeque::from_iter(vec![])); 107 | } 108 | } 109 | }, 110 | OrderType::Ask => { 111 | let matched: bool = Book::match_order(orders, bids, &mut order)?; 112 | 113 | if !matched { 114 | orders.insert(order_id, order); 115 | 116 | if !asks.contains_key(&OrderedFloat::from(order_price)) { 117 | asks.insert(OrderedFloat::from(order_price), 118 | VecDeque::from_iter(vec![])); 119 | } 120 | } 121 | } 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | pub fn cancel(&mut self, id: OrderId) -> Result<(), BookError> { 128 | unimplemented!(); 129 | } 130 | 131 | fn execute_order(order: &mut Order) -> Result<(), BookError> { 132 | Book::partially_execute_order(order, order.get_quantity()) 133 | } 134 | 135 | fn partially_execute_order(order: &mut Order, quantity: u128) -> 136 | Result<(), BookError> { 137 | let order_type: OrderType = order.get_order_type(); 138 | let ticker: String = order.get_ticker(); 139 | let price: f64 = order.get_price(); 140 | 141 | match order_type { 142 | OrderType::Bid => { 143 | order.get_owner_mut().take_balance(price * quantity as f64); 144 | order.get_owner_mut().add_holding(ticker, quantity).unwrap(); 145 | }, 146 | OrderType::Ask => { 147 | order.get_owner_mut().add_balance(price * quantity as f64); 148 | order.get_owner_mut().take_holding(ticker, quantity).unwrap(); 149 | } 150 | } 151 | 152 | Ok(()) 153 | } 154 | 155 | fn match_order(orders: &mut HashMap, 156 | side: &mut BTreeMap, VecDeque<&mut Order>>, 157 | mut order: &mut Order) -> Result { 158 | let order_price: f64 = order.get_price(); 159 | let order_quantity: u128 = order.get_quantity(); 160 | let mut matched: bool = false; 161 | 162 | for (level_price, level_orders) in side.iter_mut() { 163 | if level_price <= &OrderedFloat::from(order_price) { 164 | for counter_order in level_orders.iter_mut() { 165 | let counter_price: f64 = counter_order.get_price(); 166 | let counter_quantity: u128 = counter_order.get_quantity(); 167 | 168 | if counter_quantity < order_quantity { 169 | Book::execute_order(counter_order)?; 170 | orders.remove(&counter_order.get_id()); 171 | 172 | Book::partially_execute_order(&mut order, counter_quantity)?; 173 | } else if counter_quantity == order_quantity { 174 | Book::execute_order(counter_order)?; 175 | orders.remove(&counter_order.get_id()); 176 | 177 | Book::execute_order(&mut order)?; 178 | matched = true; 179 | break; 180 | } else if counter_quantity > order_quantity { 181 | Book::partially_execute_order(counter_order, order_quantity)?; 182 | 183 | Book::execute_order(&mut order)?; 184 | matched = true; 185 | break; 186 | } 187 | } 188 | 189 | if matched { 190 | break; 191 | } 192 | } 193 | } 194 | 195 | Ok(matched) 196 | } 197 | 198 | } 199 | 200 | 201 | impl PartialEq for Book<'_> { 202 | fn eq(&self, other: &Self) -> bool { 203 | self.id == other.id && 204 | self.name == other.name && 205 | self.ticker == other.ticker && 206 | self.ltp == other.ltp && 207 | self.has_traded == other.has_traded && 208 | self.bids.iter().len() == other.bids.iter().len() && 209 | self.asks.iter().len() == other.asks.iter().len() && 210 | Vec::new().extend(self.bids.iter().map(|x| x)) == 211 | Vec::new().extend(other.bids.iter().map(|x| x)) && 212 | Vec::new().extend(self.asks.iter().map(|x| x)) == 213 | Vec::new().extend(other.asks.iter().map(|x| x)) 214 | } 215 | } 216 | 217 | 218 | #[cfg(test)] 219 | mod tests { 220 | use super::*; 221 | use std::collections::HashMap; 222 | use crate::account::*; 223 | 224 | #[test] 225 | fn test_new() -> Result<(), BookError> { 226 | let id: u128 = 1; 227 | let name: String = "Book".to_string(); 228 | let ticker: String = "BOOK".to_string(); 229 | 230 | let actual_book: Book = Book::new(id, name.clone(), ticker.clone()); 231 | let expected_book: Book = Book{ 232 | id: id, 233 | name: name.clone(), 234 | ticker: ticker.clone(), 235 | orders: HashMap::new(), 236 | bids: BTreeMap::new(), 237 | asks: BTreeMap::new(), 238 | ltp: 0.00, 239 | has_traded: false 240 | }; 241 | 242 | assert_eq!(actual_book, expected_book); 243 | Ok(()) 244 | } 245 | 246 | #[test] 247 | fn test_submit_single_bid() -> Result<(), BookError> { 248 | /* build account */ 249 | let account_id: AccountId = 1; 250 | let account_name: String = "Account".to_string(); 251 | let account_balance: f64 = 12000.00; 252 | let account_holdings: HashMap = HashMap::new(); 253 | let actual_account: Account = Account::new(account_id, 254 | account_name, 255 | account_balance, 256 | account_holdings); 257 | 258 | /* build order */ 259 | let order_id: OrderId = 1; 260 | let order_owner: Account = actual_account; 261 | let order_ticker: String = "BOOK".to_string(); 262 | let order_type: OrderType = OrderType::Bid; 263 | let order_price: f64 = 12.00; 264 | let order_quantity: u128 = 33; 265 | let actual_order: Order = Order::new(order_id, 266 | order_owner, 267 | order_ticker, 268 | order_type, 269 | order_price, 270 | order_quantity); 271 | 272 | /* build book */ 273 | let book_id: BookId = 1; 274 | let book_name: String = "Book".to_string(); 275 | let book_ticker: String = "BOOK".to_string(); 276 | let mut actual_book: Book = Book::new(book_id, 277 | book_name.clone(), 278 | book_ticker.clone()); 279 | 280 | /* we need to build this field of the expected book due to movement 281 | * of values */ 282 | let mut expected_orders: HashMap = HashMap::new(); 283 | expected_orders.insert(order_id, actual_order.clone()); 284 | 285 | /* submit order to book */ 286 | actual_book.submit(actual_order)?; 287 | 288 | /* build expected fields */ 289 | let mut cloned_expected_orders: HashMap = 290 | expected_orders.clone(); 291 | let mut expected_bids: BTreeMap, 292 | VecDeque<&mut Order>> = 293 | BTreeMap::new(); 294 | expected_bids.insert(OrderedFloat::from(order_price), 295 | VecDeque::from_iter( 296 | vec![cloned_expected_orders.get_mut(&order_id).unwrap()])); 297 | 298 | let expected_asks: BTreeMap, 299 | VecDeque<&mut Order>> = 300 | BTreeMap::new(); 301 | 302 | let expected_book: Book = Book { 303 | id: book_id, 304 | name: book_name.clone(), 305 | ticker: book_ticker.clone(), 306 | orders: expected_orders, 307 | bids: expected_bids, 308 | asks: expected_asks, 309 | ltp: 0.00, 310 | has_traded: false 311 | }; 312 | 313 | assert_eq!(actual_book, expected_book); 314 | Ok(()) 315 | } 316 | 317 | #[test] 318 | fn test_submit_single_ask() -> Result<(), BookError> { 319 | /* build account */ 320 | let account_id: AccountId = 1; 321 | let account_name: String = "Account".to_string(); 322 | let account_balance: f64 = 12000.00; 323 | let account_holdings: HashMap = HashMap::new(); 324 | let actual_account: Account = Account::new(account_id, 325 | account_name, 326 | account_balance, 327 | account_holdings); 328 | 329 | /* build order */ 330 | let order_id: OrderId = 1; 331 | let order_owner: Account = actual_account; 332 | let order_ticker: String = "BOOK".to_string(); 333 | let order_type: OrderType = OrderType::Ask; 334 | let order_price: f64 = 12.00; 335 | let order_quantity: u128 = 33; 336 | let actual_order: Order = Order::new(order_id, 337 | order_owner, 338 | order_ticker, 339 | order_type, 340 | order_price, 341 | order_quantity); 342 | 343 | /* build book */ 344 | let book_id: BookId = 1; 345 | let book_name: String = "Book".to_string(); 346 | let book_ticker: String = "BOOK".to_string(); 347 | let mut actual_book: Book = Book::new(book_id, 348 | book_name.clone(), 349 | book_ticker.clone()); 350 | 351 | /* we need to build this field of the expected book due to movement 352 | * of values */ 353 | let mut expected_orders: HashMap = HashMap::new(); 354 | expected_orders.insert(order_id, actual_order.clone()); 355 | 356 | /* submit order to book */ 357 | actual_book.submit(actual_order)?; 358 | 359 | /* build expected fields */ 360 | let expected_bids: BTreeMap, 361 | VecDeque<&mut Order>> = 362 | BTreeMap::new(); 363 | 364 | let mut cloned_expected_orders: HashMap = 365 | expected_orders.clone(); 366 | let mut expected_asks: BTreeMap, 367 | VecDeque<&mut Order>> = 368 | BTreeMap::new(); 369 | expected_asks.insert(OrderedFloat::from(order_price), 370 | VecDeque::from_iter( 371 | vec![cloned_expected_orders.get_mut(&order_id).unwrap()])); 372 | 373 | let expected_book: Book = Book { 374 | id: book_id, 375 | name: book_name.clone(), 376 | ticker: book_ticker.clone(), 377 | orders: expected_orders, 378 | bids: expected_bids, 379 | asks: expected_asks, 380 | ltp: 0.00, 381 | has_traded: false 382 | }; 383 | 384 | assert_eq!(actual_book, expected_book); 385 | Ok(()) 386 | } 387 | } 388 | 389 | --------------------------------------------------------------------------------