From 25f2b047f486c0e1fe4ada3443f8fc85fcf89ca5 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Wed, 30 Oct 2024 20:23:40 +0000 Subject: [PATCH 01/13] added anyhow --- rotala/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/rotala/Cargo.toml b/rotala/Cargo.toml index de7aa48..7c1806b 100644 --- a/rotala/Cargo.toml +++ b/rotala/Cargo.toml @@ -19,6 +19,7 @@ csv = "1.1.6" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" tokio = { version = "1.35.1", features = ["full"] } +anyhow = "1.0.91" [dev-dependencies] criterion = { version = "0.5.1", features = ["async_tokio"] } From 3f696d636ed7828a3bc11c4beac88c1c275d0de0 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Wed, 30 Oct 2024 20:33:15 +0000 Subject: [PATCH 02/13] added modify/cancel order to orderbook, testing, error conditions --- rotala/src/exchange/uist_v2.rs | 129 ++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/rotala/src/exchange/uist_v2.rs b/rotala/src/exchange/uist_v2.rs index bc0abe9..1f5c58b 100644 --- a/rotala/src/exchange/uist_v2.rs +++ b/rotala/src/exchange/uist_v2.rs @@ -1,5 +1,9 @@ -use std::collections::{HashMap, VecDeque}; +use std::{ + collections::{HashMap, VecDeque}, + fmt::Display, +}; +use anyhow::{Error, Result}; use serde::{Deserialize, Serialize}; use crate::input::athena::{DateDepth, Depth, Level}; @@ -225,6 +229,19 @@ pub struct InnerOrder { pub order_id: OrderId, } +#[derive(Debug)] +pub enum OrderBookError { + OrderIdNotFound, +} + +impl Display for OrderBookError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "OrderBookError") + } +} + +impl std::error::Error for OrderBookError {} + #[derive(Debug)] pub struct OrderBook { inner: VecDeque, @@ -247,6 +264,17 @@ impl OrderBook { } } + //Used for testing + pub fn get_total_order_qty_by_symbol(&self, symbol: &str) -> f64 { + let mut total = 0.0; + for order in &self.inner { + if order.symbol == symbol { + total += order.qty + } + } + total + } + pub fn with_latency(latency: i64) -> Self { Self { inner: std::collections::VecDeque::new(), @@ -396,10 +424,59 @@ impl OrderBook { } } self.inner = new_inner; - print!("{:?}", self.inner); - trade_results } + + //Users will either want to change the quantity or cancel, so we can accept qty_change argument + //and there is no other behaviour we need to support + pub fn modify_order(&mut self, order_id: OrderId, qty_change: f64) -> Result<()> { + let mut position: Option = None; + + for (i, order) in self.inner.iter().enumerate() { + if order.order_id == order_id { + position = Some(i); + break; + } + } + + match position { + Some(pos) => { + //Can unwrap safely because this is produced above + let mut order_copied = self.inner.get(pos).unwrap().clone(); + + let mut new_order_qty = order_copied.qty; + + if qty_change > 0.0 { + new_order_qty += qty_change + } else { + let qty_left = order_copied.qty + qty_change; + if qty_left > 0.0 { + new_order_qty += qty_change + } else { + // We are trying to remove more than the total number of shares + // left on the order so will assume user wants to cancel + self.inner.remove(pos); + } + } + + order_copied.qty = new_order_qty; + self.inner.remove(pos); + self.inner.insert(pos, order_copied); + Ok(()) + } + None => Err(Error::new(OrderBookError::OrderIdNotFound)), + } + } + + pub fn cancel_order(&mut self, order_id: OrderId) -> Result<()> { + for (i, order) in self.inner.iter().enumerate() { + if order.order_id == order_id { + self.inner.remove(i); + return Ok(()); + } + } + Err(Error::new(OrderBookError::OrderIdNotFound)) + } } #[cfg(test)] @@ -411,6 +488,52 @@ mod tests { input::athena::{DateDepth, Depth, Level}, }; + #[test] + fn test_that_nonexistent_buy_order_cancel_throws_error() { + let mut orderbook = OrderBook::new(); + let res = orderbook.cancel_order(10); + assert!(res.is_err()) + } + + #[test] + fn test_that_nonexistent_buy_order_modify_throws_error() { + let mut orderbook = OrderBook::new(); + let res = orderbook.modify_order(10, 100.0); + assert!(res.is_err()) + } + + #[test] + fn test_that_buy_order_can_be_cancelled_and_modified() { + let bid_level = Level { + price: 100.0, + size: 100.0, + }; + + let ask_level = Level { + price: 102.0, + size: 100.0, + }; + + let mut depth = Depth::new(100, "ABC"); + depth.add_level(bid_level, crate::input::athena::Side::Bid); + depth.add_level(ask_level, crate::input::athena::Side::Ask); + + let mut quotes: DateDepth = HashMap::new(); + quotes.insert("ABC".to_string(), depth); + + let mut orderbook = OrderBook::new(); + + let order = Order::market_buy("ABC", 100.0); + let oid = orderbook.insert_order(order, 100).order_id; + let _res = orderbook.cancel_order(oid); + assert!(orderbook.get_total_order_qty_by_symbol("ABC") == 0.0); + + let order1 = Order::market_buy("ABC", 200.0); + let oid1 = orderbook.insert_order(order1, 100).order_id; + let _res1 = orderbook.modify_order(oid1, 100.0); + assert!(orderbook.get_total_order_qty_by_symbol("ABC") == 300.0); + } + #[test] fn test_that_buy_order_will_lift_all_volume_when_order_is_equal_to_depth_size() { let bid_level = Level { From 9b855d6c2e9a94156d73f5079130622df3989de2 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Wed, 30 Oct 2024 20:42:55 +0000 Subject: [PATCH 03/13] modify/cancel order added to exchange executing immediately --- rotala/src/exchange/uist_v2.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rotala/src/exchange/uist_v2.rs b/rotala/src/exchange/uist_v2.rs index 1f5c58b..bc34a28 100644 --- a/rotala/src/exchange/uist_v2.rs +++ b/rotala/src/exchange/uist_v2.rs @@ -125,6 +125,14 @@ impl UistV2 { }) } + pub fn modify_order(&mut self, order_id: OrderId, qty_change: f64) -> Result<()> { + self.orderbook.modify_order(order_id, qty_change) + } + + pub fn cancel_order(&mut self, order_id: OrderId) -> Result<()> { + self.orderbook.cancel_order(order_id) + } + pub fn insert_order(&mut self, order: Order) { // Orders are only inserted into the book when tick is called, this is to ensure proper // ordering of trades From a150ab06628b500bad9fc0353ba2ba71b9a00872 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Wed, 30 Oct 2024 20:47:52 +0000 Subject: [PATCH 04/13] order modification enum created, buffer to hold these events --- rotala/src/exchange/uist_v2.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/rotala/src/exchange/uist_v2.rs b/rotala/src/exchange/uist_v2.rs index bc34a28..d3859ba 100644 --- a/rotala/src/exchange/uist_v2.rs +++ b/rotala/src/exchange/uist_v2.rs @@ -33,6 +33,12 @@ impl From for Quote { } } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum OrderModification { + CancelOrder(OrderId), + ModifyOrder(OrderId, f64), +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum OrderType { MarketSell, @@ -107,6 +113,7 @@ pub struct UistV2 { trade_log: Vec, //This is cleared on every tick order_buffer: Vec, + order_modification_buffer: Vec, } impl UistV2 { @@ -115,6 +122,7 @@ impl UistV2 { orderbook: OrderBook::default(), trade_log: Vec::new(), order_buffer: Vec::new(), + order_modification_buffer: Vec::new(), } } @@ -125,12 +133,14 @@ impl UistV2 { }) } - pub fn modify_order(&mut self, order_id: OrderId, qty_change: f64) -> Result<()> { - self.orderbook.modify_order(order_id, qty_change) + pub fn modify_order(&mut self, order_id: OrderId, qty_change: f64) { + let order_mod = OrderModification::ModifyOrder(order_id, qty_change); + self.order_modification_buffer.push(order_mod); } - pub fn cancel_order(&mut self, order_id: OrderId) -> Result<()> { - self.orderbook.cancel_order(order_id) + pub fn cancel_order(&mut self, order_id: OrderId) { + let order_mod = OrderModification::CancelOrder(order_id); + self.order_modification_buffer.push(order_mod); } pub fn insert_order(&mut self, order: Order) { From 866632ca15b5346eca8e3e677b70a968edcbc557 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Wed, 30 Oct 2024 20:54:31 +0000 Subject: [PATCH 05/13] order modifications stored on the exchange and run after insertion --- rotala/src/exchange/uist_v2.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rotala/src/exchange/uist_v2.rs b/rotala/src/exchange/uist_v2.rs index d3859ba..c8911cb 100644 --- a/rotala/src/exchange/uist_v2.rs +++ b/rotala/src/exchange/uist_v2.rs @@ -167,6 +167,15 @@ impl UistV2 { inserted_orders.push(inner_order); } + for order_mod in &self.order_modification_buffer { + let _res = match order_mod { + OrderModification::CancelOrder(order_id) => self.orderbook.cancel_order(*order_id), + OrderModification::ModifyOrder(order_id, qty_change) => { + self.orderbook.modify_order(*order_id, *qty_change) + } + }; + } + self.order_buffer.clear(); (executed_trades, inserted_orders) } From 37ea3207e99c1ed6903f2a42ca14e4d7b201feff Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 09:04:32 +0000 Subject: [PATCH 06/13] add cancel and modify to http --- rotala-http/src/http/uist_v2.rs | 86 +++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/rotala-http/src/http/uist_v2.rs b/rotala-http/src/http/uist_v2.rs index f5d51d6..1f8cf73 100644 --- a/rotala-http/src/http/uist_v2.rs +++ b/rotala-http/src/http/uist_v2.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; use anyhow::Result; use serde::{Deserialize, Serialize}; -use rotala::exchange::uist_v2::{InnerOrder, Order, Trade, UistV2}; +use rotala::exchange::uist_v2::{InnerOrder, Order, OrderId, Trade, UistV2}; use rotala::input::athena::{Athena, DateBBO, DateDepth}; pub type BacktestId = u64; @@ -125,6 +125,27 @@ impl AppState { None } + pub fn modify_order( + &mut self, + order_id: OrderId, + quantity_change: f64, + backtest_id: BacktestId, + ) -> Option<()> { + if let Some(backtest) = self.backtests.get_mut(&backtest_id) { + backtest.exchange.modify_order(order_id, quantity_change); + return Some(()); + } + None + } + + pub fn cancel_order(&mut self, order_id: OrderId, backtest_id: BacktestId) -> Option<()> { + if let Some(backtest) = self.backtests.get_mut(&backtest_id) { + backtest.exchange.cancel_order(order_id); + return Some(()); + } + None + } + pub fn new_backtest(&mut self, dataset_name: &str) -> Option { let new_id = self.last + 1; @@ -161,6 +182,17 @@ pub struct InsertOrderRequest { pub order: Order, } +#[derive(Debug, Deserialize, Serialize)] +pub struct ModifyOrderRequest { + pub order_id: OrderId, + pub quantity_change: f64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CancelOrderRequest { + pub order_id: OrderId, +} + #[derive(Debug, Deserialize, Serialize)] pub struct FetchQuotesResponse { pub quotes: DateBBO, @@ -221,6 +253,17 @@ pub trait Client { order: Order, backtest_id: BacktestId, ) -> impl Future>; + fn modify_order( + &mut self, + order_id: OrderId, + quantity_change: f64, + backtest_id: BacktestId, + ) -> impl Future>; + fn cancel_order( + &mut self, + order_id: OrderId, + backtest_id: BacktestId, + ) -> impl Future>; fn fetch_quotes( &mut self, backtest_id: BacktestId, @@ -240,8 +283,9 @@ pub mod server { use actix_web::{get, post, web}; use super::{ - BacktestId, FetchDepthResponse, FetchQuotesResponse, InfoResponse, InitResponse, - InsertOrderRequest, NowResponse, TickResponse, UistState, UistV2Error, + BacktestId, CancelOrderRequest, FetchDepthResponse, FetchQuotesResponse, InfoResponse, + InitResponse, InsertOrderRequest, ModifyOrderRequest, NowResponse, TickResponse, UistState, + UistV2Error, }; #[get("/backtest/{backtest_id}/tick")] @@ -278,6 +322,40 @@ pub mod server { } } + #[post("/backtest/{backtest_id}/modify_order")] + pub async fn modify_order( + app: web::Data, + path: web::Path<(BacktestId,)>, + modify_order: web::Json, + ) -> Result, UistV2Error> { + let mut uist = app.lock().unwrap(); + let (backtest_id,) = path.into_inner(); + if let Some(()) = uist.modify_order( + modify_order.order_id.clone(), + modify_order.quantity_change, + backtest_id, + ) { + Ok(web::Json(())) + } else { + Err(UistV2Error::UnknownBacktest) + } + } + + #[post("/backtest/{backtest_id}/cancel_order")] + pub async fn cancel_order( + app: web::Data, + path: web::Path<(BacktestId,)>, + cancel_order: web::Json, + ) -> Result, UistV2Error> { + let mut uist = app.lock().unwrap(); + let (backtest_id,) = path.into_inner(); + if let Some(()) = uist.cancel_order(cancel_order.order_id.clone(), backtest_id) { + Ok(web::Json(())) + } else { + Err(UistV2Error::UnknownBacktest) + } + } + #[get("/backtest/{backtest_id}/fetch_quotes")] pub async fn fetch_quotes( app: web::Data, @@ -402,6 +480,8 @@ mod tests { .service(fetch_depth) .service(tick) .service(insert_order) + .service(modify_order) + .service(cancel_order) .service(now), ) .await; From 458bb9252caedf9e40a14b736c5b770f2671fa67 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 09:08:15 +0000 Subject: [PATCH 07/13] added to client --- rotala-client/src/client/uist_v2.rs | 67 +++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/rotala-client/src/client/uist_v2.rs b/rotala-client/src/client/uist_v2.rs index 7f21117..b8ae76a 100644 --- a/rotala-client/src/client/uist_v2.rs +++ b/rotala-client/src/client/uist_v2.rs @@ -1,10 +1,11 @@ use anyhow::{Error, Result}; use reqwest; -use rotala::exchange::uist_v2::Order; +use rotala::exchange::uist_v2::{Order, OrderId}; use rotala::input::athena::Athena; use rotala_http::http::uist_v2::{ - AppState, BacktestId, Client, FetchDepthResponse, FetchQuotesResponse, InfoResponse, - InitResponse, InsertOrderRequest, NowResponse, TickResponse, UistV2Error, + AppState, BacktestId, CancelOrderRequest, Client, FetchDepthResponse, FetchQuotesResponse, + InfoResponse, InitResponse, InsertOrderRequest, ModifyOrderRequest, NowResponse, TickResponse, + UistV2Error, }; use std::future::{self, Future}; @@ -37,6 +38,38 @@ impl Client for HttpClient { .await?) } + async fn modify_order( + &mut self, + order_id: OrderId, + quantity_change: f64, + backtest_id: BacktestId, + ) -> Result<()> { + let req = ModifyOrderRequest { + order_id, + quantity_change, + }; + Ok(self + .client + .post(self.path.clone() + format!("/backtest/{backtest_id}/modify_order").as_str()) + .json(&req) + .send() + .await? + .json::<()>() + .await?) + } + + async fn cancel_order(&mut self, order_id: OrderId, backtest_id: BacktestId) -> Result<()> { + let req = CancelOrderRequest { order_id }; + Ok(self + .client + .post(self.path.clone() + format!("/backtest/{backtest_id}/cancel_order").as_str()) + .json(&req) + .send() + .await? + .json::<()>() + .await?) + } + async fn fetch_quotes(&mut self, backtest_id: BacktestId) -> Result { Ok(self .client @@ -134,6 +167,34 @@ impl Client for TestClient { } } + fn modify_order( + &mut self, + order_id: OrderId, + quantity_change: f64, + backtest_id: BacktestId, + ) -> impl Future> { + if let Some(()) = self + .state + .modify_order(order_id, quantity_change, backtest_id) + { + future::ready(Ok(())) + } else { + future::ready(Err(Error::new(UistV2Error::UnknownBacktest))) + } + } + + fn cancel_order( + &mut self, + order_id: OrderId, + backtest_id: BacktestId, + ) -> impl Future> { + if let Some(()) = self.state.cancel_order(order_id, backtest_id) { + future::ready(Ok(())) + } else { + future::ready(Err(Error::new(UistV2Error::UnknownBacktest))) + } + } + fn fetch_quotes( &mut self, backtest_id: BacktestId, From a33dfd3782c45c520b086f06175eabde31c8b89b Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 17:04:08 +0000 Subject: [PATCH 08/13] add routes to binary --- rotala-http/src/bin/uist_server_v2.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rotala-http/src/bin/uist_server_v2.rs b/rotala-http/src/bin/uist_server_v2.rs index ffb41a0..8adb7dc 100644 --- a/rotala-http/src/bin/uist_server_v2.rs +++ b/rotala-http/src/bin/uist_server_v2.rs @@ -29,6 +29,8 @@ async fn main() -> std::io::Result<()> { .service(fetch_depth) .service(tick) .service(insert_order) + .service(modify_order) + .service(cancel_order) }) .bind((address, port))? .run() From 3dec0c11f07d9138a6579d7cf49b4f531a22bf2c Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 17:25:14 +0000 Subject: [PATCH 09/13] clippy --- rotala-http/src/http/uist_v2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rotala-http/src/http/uist_v2.rs b/rotala-http/src/http/uist_v2.rs index 1f8cf73..effc1ec 100644 --- a/rotala-http/src/http/uist_v2.rs +++ b/rotala-http/src/http/uist_v2.rs @@ -331,7 +331,7 @@ pub mod server { let mut uist = app.lock().unwrap(); let (backtest_id,) = path.into_inner(); if let Some(()) = uist.modify_order( - modify_order.order_id.clone(), + modify_order.order_id, modify_order.quantity_change, backtest_id, ) { @@ -349,7 +349,7 @@ pub mod server { ) -> Result, UistV2Error> { let mut uist = app.lock().unwrap(); let (backtest_id,) = path.into_inner(); - if let Some(()) = uist.cancel_order(cancel_order.order_id.clone(), backtest_id) { + if let Some(()) = uist.cancel_order(cancel_order.order_id, backtest_id) { Ok(web::Json(())) } else { Err(UistV2Error::UnknownBacktest) From 5b12b845d27209ccee4085497b3992ca6aa9299d Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 17:30:36 +0000 Subject: [PATCH 10/13] added modify and cancel to python client --- rotala-python/src/http.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rotala-python/src/http.py b/rotala-python/src/http.py index 87bd8ec..4b78aad 100644 --- a/rotala-python/src/http.py +++ b/rotala-python/src/http.py @@ -33,6 +33,30 @@ def insert_order(self, order): ) return r.json() + def modify_order(self, order_id, quantity_change): + if self.backtest_id is None: + raise ValueError("Called before init") + + val = f'{{"order_id": {order_id}, "quantity_change": {quantity_change}' + r = requests.post( + f"{self.base_url}/backtest/{self.backtest_id}/modify_order", + data=val, + headers={"Content-type": "application/json"}, + ) + return r.json() + + def cancel_order(self, order_id): + if self.backtest_id is None: + raise ValueError("Called before init") + + val = f'{{"order_id": {order_id}' + r = requests.post( + f"{self.base_url}/backtest/{self.backtest_id}/cancel_order", + data=val, + headers={"Content-type": "application/json"}, + ) + return r.json() + def fetch_quotes(self): if self.backtest_id is None: raise ValueError("Called before init") From 7aa69535fc4e64c522dec5eb26321ecb0c9ab936 Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 17:44:05 +0000 Subject: [PATCH 11/13] tick returns modified orders --- rotala/src/exchange/uist_v2.rs | 52 ++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/rotala/src/exchange/uist_v2.rs b/rotala/src/exchange/uist_v2.rs index c8911cb..99d6e26 100644 --- a/rotala/src/exchange/uist_v2.rs +++ b/rotala/src/exchange/uist_v2.rs @@ -91,6 +91,18 @@ impl Order { } } +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum ModifyResultType { + Modify, + Cancel, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ModifyResult { + pub modify_type: ModifyResultType, + pub order_id: OrderId, +} + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub enum TradeType { Buy, @@ -151,15 +163,19 @@ impl UistV2 { self.order_buffer.push(order); } - pub fn tick(&mut self, quotes: &DateDepth, now: i64) -> (Vec, Vec) { + pub fn tick( + &mut self, + quotes: &DateDepth, + now: i64, + ) -> (Vec, Vec, Vec) { //To eliminate lookahead bias, we only insert new orders after we have executed any orders //that were on the stack first let executed_trades = self.orderbook.execute_orders(quotes, now); for executed_trade in &executed_trades { self.trade_log.push(executed_trade.clone()); } - let mut inserted_orders = Vec::new(); + self.sort_order_buffer(); //TODO: remove this overhead, shouldn't need a clone here for order in self.order_buffer.iter() { @@ -167,17 +183,37 @@ impl UistV2 { inserted_orders.push(inner_order); } + let mut modified_orders = Vec::new(); for order_mod in &self.order_modification_buffer { - let _res = match order_mod { + let res = match order_mod { OrderModification::CancelOrder(order_id) => self.orderbook.cancel_order(*order_id), OrderModification::ModifyOrder(order_id, qty_change) => { self.orderbook.modify_order(*order_id, *qty_change) } }; + + //If we didn't succeed then we tried to modify an order that didn't exist so we just + //ignore this totally as a no-op and move on + if let Ok(..) = res { + let modification_result_destructure = match order_mod { + OrderModification::CancelOrder(order_id) => { + (order_id, ModifyResultType::Cancel) + } + OrderModification::ModifyOrder(order_id, _qty_change) => { + (order_id, ModifyResultType::Modify) + } + }; + + let modification_result = ModifyResult { + order_id: *modification_result_destructure.0, + modify_type: modification_result_destructure.1, + }; + modified_orders.push(modification_result); + } } self.order_buffer.clear(); - (executed_trades, inserted_orders) + (executed_trades, inserted_orders, modified_orders) } } @@ -456,7 +492,7 @@ impl OrderBook { //Users will either want to change the quantity or cancel, so we can accept qty_change argument //and there is no other behaviour we need to support - pub fn modify_order(&mut self, order_id: OrderId, qty_change: f64) -> Result<()> { + pub fn modify_order(&mut self, order_id: OrderId, qty_change: f64) -> Result { let mut position: Option = None; for (i, order) in self.inner.iter().enumerate() { @@ -489,17 +525,17 @@ impl OrderBook { order_copied.qty = new_order_qty; self.inner.remove(pos); self.inner.insert(pos, order_copied); - Ok(()) + Ok(order_id) } None => Err(Error::new(OrderBookError::OrderIdNotFound)), } } - pub fn cancel_order(&mut self, order_id: OrderId) -> Result<()> { + pub fn cancel_order(&mut self, order_id: OrderId) -> Result { for (i, order) in self.inner.iter().enumerate() { if order.order_id == order_id { self.inner.remove(i); - return Ok(()); + return Ok(order_id); } } Err(Error::new(OrderBookError::OrderIdNotFound)) From aa06d785dc21d701c340c267fe5d17d0bc11ea0a Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 17:48:01 +0000 Subject: [PATCH 12/13] return modified orders through http/client --- rotala-client/src/client/uist_v2.rs | 1 + rotala-http/src/http/uist_v2.rs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/rotala-client/src/client/uist_v2.rs b/rotala-client/src/client/uist_v2.rs index b8ae76a..8aa19fd 100644 --- a/rotala-client/src/client/uist_v2.rs +++ b/rotala-client/src/client/uist_v2.rs @@ -146,6 +146,7 @@ impl Client for TestClient { fn tick(&mut self, backtest_id: BacktestId) -> impl Future> { if let Some(resp) = self.state.tick(backtest_id) { future::ready(Ok(TickResponse { + modified_orders: resp.3, inserted_orders: resp.2, executed_trades: resp.1, has_next: resp.0, diff --git a/rotala-http/src/http/uist_v2.rs b/rotala-http/src/http/uist_v2.rs index effc1ec..d3f823e 100644 --- a/rotala-http/src/http/uist_v2.rs +++ b/rotala-http/src/http/uist_v2.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; use anyhow::Result; use serde::{Deserialize, Serialize}; -use rotala::exchange::uist_v2::{InnerOrder, Order, OrderId, Trade, UistV2}; +use rotala::exchange::uist_v2::{InnerOrder, ModifyResult, Order, OrderId, Trade, UistV2}; use rotala::input::athena::{Athena, DateBBO, DateDepth}; pub type BacktestId = u64; @@ -56,15 +56,21 @@ impl AppState { } } - pub fn tick(&mut self, backtest_id: BacktestId) -> Option<(bool, Vec, Vec)> { + pub fn tick( + &mut self, + backtest_id: BacktestId, + ) -> Option<(bool, Vec, Vec, Vec)> { if let Some(backtest) = self.backtests.get_mut(&backtest_id) { if let Some(dataset) = self.datasets.get(&backtest.dataset_name) { let mut has_next = false; + //TODO: this has perf implications, not quite sure why memory is being created here let mut executed_trades = Vec::new(); let mut inserted_orders = Vec::new(); + let mut modified_orders = Vec::new(); if let Some(quotes) = dataset.get_quotes(&backtest.date) { let mut res = backtest.exchange.tick(quotes, backtest.date); + modified_orders.append(&mut res.2); executed_trades.append(&mut res.0); inserted_orders.append(&mut res.1); } @@ -75,7 +81,7 @@ impl AppState { backtest.date = *dataset.get_date(new_pos).unwrap(); } backtest.pos = new_pos; - return Some((has_next, executed_trades, inserted_orders)); + return Some((has_next, executed_trades, inserted_orders, modified_orders)); } } None @@ -175,6 +181,7 @@ pub struct TickResponse { pub has_next: bool, pub executed_trades: Vec, pub inserted_orders: Vec, + pub modified_orders: Vec, } #[derive(Debug, Deserialize, Serialize)] @@ -298,6 +305,7 @@ pub mod server { if let Some(result) = uist.tick(backtest_id) { Ok(web::Json(TickResponse { + modified_orders: result.3, inserted_orders: result.2, executed_trades: result.1, has_next: result.0, From 607f27aff65b18d751bd0411242263776b31f61d Mon Sep 17 00:00:00 2001 From: Calum Russell Date: Fri, 1 Nov 2024 17:53:08 +0000 Subject: [PATCH 13/13] clippy --- rotala-http/src/http/uist_v2.rs | 6 ++---- rotala/src/exchange/uist_v2.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rotala-http/src/http/uist_v2.rs b/rotala-http/src/http/uist_v2.rs index d3f823e..389fd9d 100644 --- a/rotala-http/src/http/uist_v2.rs +++ b/rotala-http/src/http/uist_v2.rs @@ -9,6 +9,7 @@ use rotala::exchange::uist_v2::{InnerOrder, ModifyResult, Order, OrderId, Trade, use rotala::input::athena::{Athena, DateBBO, DateDepth}; pub type BacktestId = u64; +pub type TickResponseType = (bool, Vec, Vec, Vec); pub struct BacktestState { pub id: BacktestId, @@ -56,10 +57,7 @@ impl AppState { } } - pub fn tick( - &mut self, - backtest_id: BacktestId, - ) -> Option<(bool, Vec, Vec, Vec)> { + pub fn tick(&mut self, backtest_id: BacktestId) -> Option { if let Some(backtest) = self.backtests.get_mut(&backtest_id) { if let Some(dataset) = self.datasets.get(&backtest.dataset_name) { let mut has_next = false; diff --git a/rotala/src/exchange/uist_v2.rs b/rotala/src/exchange/uist_v2.rs index 99d6e26..65dbcc3 100644 --- a/rotala/src/exchange/uist_v2.rs +++ b/rotala/src/exchange/uist_v2.rs @@ -194,7 +194,7 @@ impl UistV2 { //If we didn't succeed then we tried to modify an order that didn't exist so we just //ignore this totally as a no-op and move on - if let Ok(..) = res { + if res.is_ok() { let modification_result_destructure = match order_mod { OrderModification::CancelOrder(order_id) => { (order_id, ModifyResultType::Cancel)