Rust has enum
which are as you would expect but more powerful, and for classes you can create a struct
and impl
functions for it. There are also Traits
which are like interfaces that structs can adhere to.
So your Order struct can implement new
(typical for a non-trivial struct) which returns a new Order, and have a fn called fill
for example, which can be called on any order. So you could have a Market/Engine struct per instrument which has 2 Orderbook structs (bid + ask), which have many Level structs, and each Level has many Order structs.
For example an Order struct could look something like:
use super::{OrderSide, OrderStatus};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug)]
pub struct Order {
pub order_id: u64,
pub side: OrderSide,
pub price: u64,
pub quantity: u64,
pub quantity_left: u64,
pub quantity_filled: u64,
pub status: OrderStatus,
pub acum_amount: u64,
pub avg_fill_price: f64,
pub timestamp: u128,
}
impl Order {
pub fn new(order_id: u64, side: OrderSide, price: u64, quantity: u64) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!")
.as_millis();
Self {
order_id,
side,
price,
quantity,
quantity_left: quantity,
quantity_filled: 0,
status: OrderStatus::Open,
acum_amount: 0,
avg_fill_price: 0.0,
timestamp,
}
}
// Note fill should never be called with a quantity
// > quantity_left. We omit it here as an order is
// only intended for a level which does the check
// already.
pub fn fill(&mut self, quantity: u64, price: u64) -> &OrderStatus {
self.acum_amount += quantity * price;
self.quantity_left -= quantity;
self.quantity_filled += quantity;
self.avg_fill_price = (self.acum_amount / self.quantity_filled) as f64;
if self.quantity_left == 0 {
self.status = OrderStatus::Filled;
} else {
self.status = OrderStatus::PartialFill;
}
&self.status
}
pub fn finished(&self) -> bool {
self.status == OrderStatus::Cancelled || self.status == OrderStatus::Filled
}
}
#[cfg(test)]
mod order_test {
use super::{Order, OrderSide, OrderStatus};
const FAKE_ID: u64 = 553311;
#[test]
fn test_order() {
// Order 1000 lots for $2 per unit.
let mut order = Order::new(FAKE_ID, OrderSide::Bid, 200_000_000, 1000);
// Side should be bid with an open status.
assert_eq!(order.side, OrderSide::Bid);
assert_eq!(order.status, OrderStatus::Open);
// Fill 500 lots at $1.9 per unit.
order.fill(500, 190_000_000);
assert_eq!(order.quantity_left, 500, "Should have 500 units left.");
assert_eq!(
order.avg_fill_price, 190_000_000.0,
"Average fill price should be $1.9"
);
assert_eq!(
order.status,
OrderStatus::PartialFill,
"Status should be PartialFill."
);
// Fill up the order. NOTE that we cannot overfill
// the order otherwise it will break the u64 type
// on quantity_left. Since Level ensures we don't
// overfill this is OK and more efficient.
order.fill(500, 200_000_000);
assert_eq!(order.quantity_left, 0, "quantity_left should be 0");
assert_eq!(order.status, OrderStatus::Filled, "Status should be Filled")
}
}
P.S You will run into some interesting problems with designing a matching engine in Rust since in order to most efficiently do add/update/cancel/amend many algos use two data structures which point to the same orders, and even though matching engines are almost always single threaded Rust will force you to write thread safe code which accesses the underlying orders through fat/smart pointers (not ideal for high frequency trading but a simple one like Rc
should be OK)