initial commit

This commit is contained in:
2025-08-14 14:07:05 +01:00
commit 6685a4d04a
6 changed files with 395 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
/.vs

7
Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "rust_orderbook"
version = "0.1.0"

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "rust_orderbook"
version = "0.1.0"
edition = "2024"
[dependencies]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Shay Patel
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# rust-orderbook
This repository contains the source code for a basic orderbook implementation in Rust, exposing functionality to place both BUY and SELL limit and market orders.
## Motivation
The primary motivation for writing this code was (and is) to learn the basics of Rust syntax and it's unique features, as well as expand and apply my understanding of fundamentally key trading and market dynamics.
If you spot any issues or errors within the code, or think I could've done something differently, please raise an issue or feel free to send in a PR - I'd love to learn how I could've approached things from a different perspective, and I can take these considerations into account for future projects!
## Building and running
You can run the code by cloning the repository, `cd`ing into the cloned folder, and simply running `cargo run`. This will both build and run the project in a single step.
## Usage
Once the program is running, it will populate the order book with some dummy BUY and SELL orders (`populate_orderbook`), and then print out the highest and lowest 5 BUY and SELL orders, whilst also displaying the spread between the two.
You can run one of three commands:
- `BUY [quantity] (price)` - buy `quantity` at market rate. Optional `price` parameter will place a limit BUY order.
- `SELL [quantity] (price)` - sell `quantity` at market rate. Optional `price` parameter will place a limit SELL order.
- `EXIT` -> exits the program
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

336
src/main.rs Normal file
View File

@@ -0,0 +1,336 @@
use std::io;
use std::collections::BTreeMap;
use std::str::SplitWhitespace;
struct Orderbook {
// both of these maps map a price to a quantity
bids: BTreeMap<i32, i32>,
asks: BTreeMap<i32, i32>,
}
fn read_in_quantity_and_price(split: &mut SplitWhitespace<'_>, quantity: &mut i32, price: &mut Option<i32>) {
let quantity_opt_str: Option<&str> = split.next();
let price_opt_str: Option<&str> = split.next();
// ensure quantity_str is a valid integer
*quantity = quantity_opt_str.expect("No quantity provided.").trim().parse::<i32>().expect("Invalid quantity provided.");
// ensure quantity is positive
assert!(*quantity > 0, "Quantity must be positive.");
// see if a price has been supplied
match price_opt_str {
None => {},
Some(price_str) => {
// ensure price_str is a valid number
let price_num = price_str.trim().parse::<i32>().expect("Invalid price provided.");
// ensure price_num is positive
assert!(price_num > 0, "Price must be positive.");
*price = Some(price_num);
}
};
()
}
fn gen_hashtag_loop(n: usize) -> String {
let hashtags: String = std::iter::repeat('#').take(n).collect();
return hashtags;
}
fn list_orders(ob: &Orderbook) {
println!("===============================");
println!("{:<5} {:>13}", "PRICE", "QUANTITY");
println!();
if ob.asks.len() < 5 {
for _ in 0..(5 - ob.asks.len()) { println!(); };
};
for ask in ob.asks.iter().take(5).rev() {
println!("${:<4.2} {:>4} {}", ask.0, ask.1, gen_hashtag_loop((*ask.1).try_into().expect("Failed to format i32 as usize.")));
};
println!("-------------------------------");
println!("{:.2}bps", (100.0 * ((*ob.asks.first_key_value().expect("One-sided order book.").0 as f32 / *ob.bids.last_key_value().expect("One-sided order book.").0 as f32) - 1.0)));
println!("-------------------------------");
for bid in ob.bids.iter().rev().take(5) {
println!("${:<4.2} {:>4} {}", bid.0, bid.1, gen_hashtag_loop((*bid.1).try_into().expect("Failed to format i32 as usize.")));
};
if ob.bids.len() < 5 {
for _ in 0..(5 - ob.bids.len()) { println!(); };
};
println!();
}
fn market_buy(ob: &mut Orderbook, quantity: i32) {
println!("MARKET BUY placed: {} @ market price", quantity);
let mut left_to_buy: i32 = quantity;
let mut total_value: i32 = 0;
while left_to_buy > 0 {
let (p, q) = ob.asks.pop_first().expect("Insufficient sell volume.");
if q > left_to_buy {
// push back sell order with reduced quantity
ob.asks.insert(p, q-left_to_buy);
// increase total_value
total_value += left_to_buy * p;
// reduce left_to_buy
left_to_buy = 0;
} else {
// increase total_value
total_value += q*p;
// reduce left_to_buy
left_to_buy -= q;
}
}
println!("Bought {} at an average price of ${:.2}", quantity, ((total_value as f32) / (quantity as f32)));
}
fn market_sell(ob: &mut Orderbook, quantity: i32) {
println!("MARKET SELL placed: {} @ market price", quantity);
let mut left_to_sell: i32 = quantity;
let mut total_value: i32 = 0;
while left_to_sell > 0 {
let (p, q) = ob.bids.pop_last().expect("Insufficient buy volume.");
if q > left_to_sell {
// push back buy order with reduced quantity
ob.bids.insert(p, q-left_to_sell);
// increase total_value
total_value += left_to_sell * p;
// reduce left_to_sell
left_to_sell = 0;
} else {
// increase total_value
total_value += q*p;
// reduce left_to_sell
left_to_sell -= q;
}
}
println!("Sold {} at an average price of ${:.2}", quantity, ((total_value as f32) / (quantity as f32)));
}
fn create_buy_order(ob: &mut Orderbook, quantity: i32, price: i32) {
println!("Placed BUY order ({} @ ${})", quantity, price);
match ob.bids.get(&price) {
Some(prev_quantity) => {
// add to existing order
ob.bids.insert(price, quantity + prev_quantity);
},
None => {
// create new order
ob.bids.insert(price, quantity);
}
};
}
fn create_sell_order(ob: &mut Orderbook, quantity: i32, price: i32) {
println!("Placed SELL order ({} @ ${})", quantity, price);
match ob.asks.get(&price) {
Some(prev_quantity) => {
// add to existing order
ob.asks.insert(price, quantity + prev_quantity);
},
None => {
// create new order
ob.asks.insert(price, quantity);
}
};
}
fn limit_buy(ob: &mut Orderbook, quantity: i32, price: i32) {
println!("LIMIT BUY placed: {} @ ${}", quantity, price);
let mut left_to_buy: i32 = quantity;
let mut total_value: i32 = 0;
let mut total_quantity: i32 = 0;
// check the cheapest sell order - if it doesn't exist, or it does but the price is too high, create a new buy order
while left_to_buy > 0 {
match ob.asks.pop_first() {
None => {
// there are no sell orders, so create a new buy order
create_buy_order(ob, left_to_buy, price);
left_to_buy = 0;
},
Some((p, q)) => {
// check if this sell order is cheap enough
if p <= price {
// cheap enough, fill order as much as possible
if q > left_to_buy {
// push back sell order with reduced quantity
ob.asks.insert(p, q-left_to_buy);
// increase total_value
total_value += left_to_buy * p;
total_quantity += left_to_buy;
// reduce left_to_buy
left_to_buy = 0;
} else {
// increase total_value
total_value += q*p;
total_quantity += q;
// reduce left_to_buy
left_to_buy -= q;
}
} else {
ob.asks.insert(p, q);
create_buy_order(ob, left_to_buy, price);
left_to_buy = 0;
}
}
}
}
println!();
println!("Bought {} at an average price of ${:.2}", total_quantity, ((total_value as f32) / (total_quantity as f32)));
}
fn limit_sell(ob: &mut Orderbook, quantity: i32, price: i32) {
println!("LIMIT SELL placed: {} @ ${}", quantity, price);
let mut left_to_sell: i32 = quantity;
let mut total_value: i32 = 0;
let mut total_quantity: i32 = 0;
// check the most appealing buy order - if it doesn't exist, or it does but the price is too low, create a new sell order
while left_to_sell > 0 {
match ob.bids.pop_last() {
None => {
// there are no buy orders, so create a new sell order
create_sell_order(ob, left_to_sell, price);
left_to_sell = 0;
},
Some((p, q)) => {
// check if this buy order is high enough
if p >= price {
// fill order as much as possible
if q > left_to_sell {
// push back buy order with reduced quantity
ob.bids.insert(p, q-left_to_sell);
// increase total_value
total_value += left_to_sell * p;
total_quantity += left_to_sell;
// reduce left_to_sell
left_to_sell = 0;
} else {
// increase total_value
total_value += q*p;
total_quantity += q;
// reduce left_to_sell
left_to_sell -= q;
}
} else {
ob.bids.insert(p, q);
create_sell_order(ob, left_to_sell, price);
left_to_sell = 0;
}
}
}
}
println!();
println!("Sold {} at an average price of ${:.2}", total_quantity, ((total_value as f32) / (total_quantity as f32)));
}
fn populate_orderbook(ob: &mut Orderbook) {
limit_buy(ob, 5, 8);
limit_buy(ob, 4, 7);
limit_buy(ob, 2, 3);
limit_buy(ob, 6, 8);
limit_buy(ob, 5, 15);
limit_buy(ob, 10, 10);
limit_buy(ob, 8, 9);
limit_buy(ob, 1, 2);
limit_buy(ob, 12, 14);
limit_buy(ob, 7, 5);
limit_sell(ob, 2, 15);
limit_sell(ob, 3, 16);
limit_sell(ob, 3, 17);
limit_sell(ob, 4, 18);
limit_sell(ob, 5, 20);
limit_sell(ob, 6, 18);
limit_sell(ob, 9, 21);
limit_sell(ob, 15, 25);
limit_sell(ob, 2, 19);
limit_sell(ob, 11, 22);
}
fn main() {
let mut input_string = String::new();
let mut ob = Orderbook {
bids: BTreeMap::new(),
asks: BTreeMap::new(),
};
populate_orderbook(&mut ob);
list_orders(&ob);
while input_string.trim() != "EXIT" {
input_string.clear();
println!("Enter a command:");
io::stdin().read_line(&mut input_string).unwrap();
println!();
let mut split = input_string.split_whitespace();
let command: Option<&str> = split.next();
match command.expect("No command specified.") {
"EXIT" => {},
"BUY" => {
let mut quantity: i32 = 0;
let mut price_opt: Option<i32> = None;
read_in_quantity_and_price(&mut split.clone(), &mut quantity, &mut price_opt);
match price_opt {
None => {
market_buy(&mut ob, quantity);
},
Some(price) => {
limit_buy(&mut ob, quantity, price);
}
};
},
"SELL" => {
let mut quantity: i32 = 0;
let mut price_opt: Option<i32> = None;
read_in_quantity_and_price(&mut split.clone(), &mut quantity, &mut price_opt);
match price_opt {
None => {
market_sell(&mut ob, quantity);
},
Some(price) => {
limit_sell(&mut ob, quantity, price);
}
};
},
_ => {
panic!("Unknown command.");
}
};
println!();
list_orders(&ob);
}
println!("Program terminating.");
}