Lets write an orderbook in rust.

  1. Lets not write the matching engine yet, just populate the bids and the asks assuming there will be no matches.
  2. Lets assume there is a single market we support (SOL/USDC).
  3. Lets assume user provides us their user_id as input in the body. No need to implement auth.
cargo init
cargo add actix_web
use actix_web::{delete, get, post, Responder};

#[get("/depth")]
pub async fn get_depth() -> impl Responder {
    "Hello, world!"
}

#[post("/order")]
pub async fn create_order() -> impl Responder {
    "Hello, world!"
}

#[delete("/order")]
pub async fn delete_order() -> impl Responder {
    "Hello, world!"
}
cargo add serde serde_json
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateOrder {
    pub price: f64,
    pub quantity: f64,
    pub user_id: String,
    pub side: Side,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum Side {
    Buy,
    Sell
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DeleteOrder {
    pub order_id: String,
    pub user_id: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateOrderResponse {
    pub order_id: String,
    pub filled_quantity: f64,
    pub remaining_quantity: f64,
    pub average_price: f64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DeleteOrderResponse {
    pub success: bool,
    pub remaining_quantity: f64,
    pub filled_quantity: f64,
    pub average_price: f64,
}
use std::collections::{BTreeMap, HashMap};

use serde::{Deserialize, Serialize};

use crate::types::{CreateOrder, DeleteOrder, Side};

#[derive(Clone)]
pub struct OpenOrder {
    pub price: f64,
    pub quantity: f64,
    pub side: Side,
    pub user_id: String,
    pub order_id: String,
    pub filled_quantity: f64,
}

pub struct Orderbook {  
    pub bids: HashMap<String, Vec<OpenOrder>>,
    pub asks: HashMap<String, Vec<OpenOrder>>,
    pub order_id_index: u64,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Depth {
    pub price: f64,
    pub quantity: f64,
}

pub struct DepthResponse {
    pub bids: Vec<Depth>,
    pub asks: Vec<Depth>,
}

impl Default for Orderbook {
    fn default() -> Self {
        Self {
            bids: HashMap::new(),
            asks: HashMap::new(),
            order_id_index: 0
        }
    }
}

impl Orderbook {
    pub fn create_order(&mut self, order: CreateOrder) {
        let order_id = self.order_id_index.to_string();
        self.order_id_index += 1;
        match order.side {
            Side::Buy => {
                let open_order = OpenOrder {
                    price: order.price,
                    quantity: order.quantity,
                    side: order.side,
                    user_id: order.user_id,
                    order_id: order_id,
                    filled_quantity: 0.0,
                };
                self.bids.entry(order.price.to_string()).or_insert(Vec::new()).push(open_order);
            }
            Side::Sell => {
                let open_order = OpenOrder {
                    price: order.price,
                    quantity: order.quantity,
                    side: order.side,
                    user_id: order.user_id,
                    order_id: order_id,
                    filled_quantity: 0.0,
                };
                self.asks.entry(order.price.to_string()).or_insert(Vec::new()).push(open_order);
            }
        }
    }

    pub fn delete_order(&mut self, order: DeleteOrder) {
        // Find and remove from bids
        if let Some(price) = self.bids.iter().find_map(|(price, orders)| {
            if orders.iter().any(|o| o.order_id == order.order_id) {
                Some(price.clone())
            } else {
                None
            }
        }) {
            if let Some(orders) = self.bids.get_mut(&price) {
                orders.retain(|o| o.order_id != order.order_id);
            }
        }

        // Find and remove from asks
        if let Some(price) = self.asks.iter().find_map(|(price, orders)| {
            if orders.iter().any(|o| o.order_id == order.order_id) {
                Some(price.clone())
            } else {
                None
            }
        }) {
            if let Some(orders) = self.asks.get_mut(&price) {
                orders.retain(|o| o.order_id != order.order_id);
            }
        }
    }

    pub fn get_depth(&self) -> DepthResponse {
        let mut bids = Vec::new();
        let mut asks = Vec::new();
        for (price, orders) in self.bids.iter() {
            bids.push(Depth { price: price.parse().unwrap(), quantity: orders.iter().map(|o| o.quantity).sum() });
        }
        for (price, orders) in self.asks.iter() {
            asks.push(Depth { price: price.parse().unwrap(), quantity: orders.iter().map(|o| o.quantity).sum() });  
        }
        DepthResponse { bids, asks }
    }
}
use std::sync::{Arc, Mutex};

use actix_web::{delete, get, post, web::{Data, Json}, HttpResponse, Responder};

use crate::{orderbook::Orderbook, types::{CreateOrder, DeleteOrder}};

#[get("/depth")]
pub async fn get_depth(orderbook: Data<Arc<Mutex<Orderbook>>>) -> impl Responder {
    let orderbook = orderbook.lock().unwrap();
    let depth = orderbook.get_depth();
    HttpResponse::Ok().json(depth)
}

#[post("/order")]
pub async fn create_order(orderbook: Data<Arc<Mutex<Orderbook>>>, order: Json<CreateOrder>) -> impl Responder {
    let mut orderbook = orderbook.lock().unwrap();
    let orderbook = orderbook.create_order(order.0);
    HttpResponse::Ok().json(orderbook)
}

#[delete("/order")]
pub async fn delete_order(orderbook: Data<Arc<Mutex<Orderbook>>>, order: Json<DeleteOrder>) -> impl Responder {
    let mut orderbook = orderbook.lock().unwrap();
    let orderbook = orderbook.delete_order(order.0);
    HttpResponse::Ok().json(orderbook)
}