-4

I'm very, very new to Rust and I'm trying to understand it. For me, it would be best if someone will show me by example how to do something like this:

Let us have OrderBook with Asks and Bids. Let us have Market that have it's OrderBook. Let us have Order that can be one of four types: BuyForAmount, BuyForTotal, SellForAmount and SellForTotal. Let Order have Market it'll be placed on. Let Order have calculate method that takes OrderBook from Market to do it's calculations (and let's assume Order can recalculate itself using new OrderBook data from Market).

Quite simple, isn't? ;-)

In some sort of pseudo-code I would write it probably like this:

class OrderBook {}

class Market { order_book: OrderBook }

class Order {
  virtual calculate()

  calc_from_amount(ref order_book)
  calc_from_total(ref order_book)
}

class BuyForAmount : Order {
  calculate() { 
    base.calc_from_amount(market.order_book.asks)
  }
}

class SellForAmount : Order {
  calculate() { 
    base.calc_from_amount(market.order_book.bids)
  }
}

class BuyForTotal : Order {
  calculate() { 
    base.calc_from_total(market.order_book.asks)
  }
}

class SellForTotal : Order {
  calculate() { 
    base.calc_from_total(market.order_book.bids)
  }
}

How would you do this in Rust? (I would like to avoid using Enums and if/else.)

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
dar7man
  • 1
  • 1
  • 3
  • 1
    I don't know how much you've researched already, but did you consider Traits? https://doc.rust-lang.org/book/ch10-02-traits.html – Tobias K. Aug 03 '21 at 21:21
  • 3
    *"I would like to avoid using Enums and `if`/`else`"* - why? It seems misguided considering that you are *"very very new"* to Rust. Perhaps you should look at [Should I use enums or boxed trait objects to emulate polymorphism?](https://stackoverflow.com/questions/52240099/should-i-use-enums-or-boxed-trait-objects-to-emulate-polymorphism) You should also clarify what you need help on. An answer going into depth on branchless programming would be futile if you don't know how to create a `struct` for example. – kmdreko Aug 03 '21 at 21:53
  • @kmdreko Thank you. Very interesting reading! – dar7man Aug 04 '21 at 04:55

1 Answers1

0

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)

Dominic
  • 62,658
  • 20
  • 139
  • 163