From 167d5be1d2ab1047b24a9ecf329f6341659f88ee Mon Sep 17 00:00:00 2001 From: Marco-Andrea Buchmann Date: Sat, 30 Aug 2025 11:44:57 +0200 Subject: [PATCH 1/5] Add last trade information and mutable state values to backtest/mod.rs --- hftbacktest/src/backtest/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hftbacktest/src/backtest/mod.rs b/hftbacktest/src/backtest/mod.rs index 6a004102..ac30b1d1 100644 --- a/hftbacktest/src/backtest/mod.rs +++ b/hftbacktest/src/backtest/mod.rs @@ -889,6 +889,11 @@ where self.local.get(asset_no).unwrap().state_values() } + #[inline] + fn mutable_state_values(&mut self, asset_no: usize) -> &mut StateValues { + self.local.get_mut(asset_no).unwrap().state_values_mut() + } + fn depth(&self, asset_no: usize) -> &MD { self.local.get(asset_no).unwrap().depth() } @@ -897,6 +902,14 @@ where self.local.get(asset_no).unwrap().last_trades() } + fn last_trade_price(&self, asset_no: usize) -> f64 { + self.local.get(asset_no).unwrap().last_trade_price() + } + + fn last_trade_size(&self, asset_no: usize) -> f64 { + self.local.get(asset_no).unwrap().last_trade_size() + } + #[inline] fn clear_last_trades(&mut self, asset_no: Option) { match asset_no { From d9f350508f223580abff155a2aa7333627d1f6e2 Mon Sep 17 00:00:00 2001 From: Marco-Andrea Buchmann Date: Sat, 30 Aug 2025 11:52:26 +0200 Subject: [PATCH 2/5] Adapt proc files: add last trade information and message counting --- hftbacktest/src/backtest/proc/l3_local.rs | 30 +++++++++++++++++++++-- hftbacktest/src/backtest/proc/local.rs | 30 +++++++++++++++++++++-- hftbacktest/src/backtest/proc/mod.rs | 9 +++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/hftbacktest/src/backtest/proc/l3_local.rs b/hftbacktest/src/backtest/proc/l3_local.rs index 4cc2041e..6c59b9f6 100644 --- a/hftbacktest/src/backtest/proc/l3_local.rs +++ b/hftbacktest/src/backtest/proc/l3_local.rs @@ -46,6 +46,8 @@ where trades: Vec, last_feed_latency: Option<(i64, i64)>, last_order_latency: Option<(i64, i64, i64)>, + last_trade_price: f64, + last_trade_size: f64, } impl L3Local @@ -70,6 +72,8 @@ where trades: Vec::with_capacity(trade_len), last_feed_latency: None, last_order_latency: None, + last_trade_price: 0.0, + last_trade_size: 0.0, } } } @@ -113,6 +117,8 @@ where self.order_l2e.request(order, |order| { order.req = Status::Rejected; }); + self.state_values_mut().num_messages += 1; + self.state_values_mut().num_creations += 1; Ok(()) } @@ -148,6 +154,8 @@ where order.price_tick = orig_price_tick; order.qty = orig_qty; }); + self.state_values_mut().num_messages += 1; + self.state_values_mut().num_modifications += 1; Ok(()) } @@ -168,6 +176,8 @@ where self.order_l2e.request(order.clone(), |order| { order.req = Status::Rejected; }); + self.state_values_mut().num_messages += 1; + self.state_values_mut().num_cancellations += 1; Ok(()) } @@ -188,6 +198,10 @@ where self.state.values() } + fn state_values_mut(&mut self) -> &mut StateValues { + self.state.values_mut() + } + fn depth(&self) -> &MD { &self.depth } @@ -200,6 +214,14 @@ where self.trades.as_slice() } + fn last_trade_price(&self) -> f64 { + self.last_trade_price + } + + fn last_trade_size(&self) -> f64 { + self.last_trade_size + } + fn clear_last_trades(&mut self) { self.trades.clear(); } @@ -246,8 +268,12 @@ where self.depth.delete_order(ev.order_id, ev.local_ts)?; } // Processes a trade event - else if ev.is(LOCAL_TRADE_EVENT) && self.trades.capacity() > 0 { - self.trades.push(ev.clone()); + else if ev.is(LOCAL_TRADE_EVENT) { + if self.trades.capacity() > 0 { + self.trades.push(ev.clone()); + } + self.last_trade_size = ev.qty; + self.last_trade_price = ev.px; } // Stores the current feed latency diff --git a/hftbacktest/src/backtest/proc/local.rs b/hftbacktest/src/backtest/proc/local.rs index e84513fc..e7f980a6 100644 --- a/hftbacktest/src/backtest/proc/local.rs +++ b/hftbacktest/src/backtest/proc/local.rs @@ -46,6 +46,8 @@ where trades: Vec, last_feed_latency: Option<(i64, i64)>, last_order_latency: Option<(i64, i64, i64)>, + last_trade_price: f64, + last_trade_size: f64, } impl Local @@ -70,6 +72,8 @@ where trades: Vec::with_capacity(last_trades_cap), last_feed_latency: None, last_order_latency: None, + last_trade_size: 0.0, + last_trade_price: 0.0, } } @@ -175,6 +179,8 @@ where order.req = Status::Rejected; }); + self.state_values_mut().num_messages += 1; + self.state_values_mut().num_creations += 1; Ok(()) } @@ -210,6 +216,8 @@ where order.qty = orig_qty; }); + self.state_values_mut().num_messages += 1; + self.state_values_mut().num_modifications += 1; Ok(()) } @@ -230,6 +238,8 @@ where order.req = Status::Rejected; }); + self.state_values_mut().num_messages += 1; + self.state_values_mut().num_cancellations += 1; Ok(()) } @@ -249,6 +259,10 @@ where self.state.values() } + fn state_values_mut(&mut self) -> &mut StateValues { + self.state.values_mut() + } + fn depth(&self) -> &MD { &self.depth } @@ -261,6 +275,14 @@ where self.trades.as_slice() } + fn last_trade_price(&self) -> f64 { + self.last_trade_price + } + + fn last_trade_size(&self) -> f64 { + self.last_trade_size + } + fn clear_last_trades(&mut self) { self.trades.clear(); } @@ -299,8 +321,12 @@ where self.depth.update_ask_depth(ev.px, ev.qty, ev.local_ts); } // Processes a trade event - else if ev.is(LOCAL_TRADE_EVENT) && self.trades.capacity() > 0 { - self.trades.push(ev.clone()); + else if ev.is(LOCAL_TRADE_EVENT) { + if self.trades.capacity() > 0 { + self.trades.push(ev.clone()); + } + self.last_trade_size = ev.qty; + self.last_trade_price = ev.px; } // Stores the current feed latency diff --git a/hftbacktest/src/backtest/proc/mod.rs b/hftbacktest/src/backtest/proc/mod.rs index 44814c28..8f42f724 100644 --- a/hftbacktest/src/backtest/proc/mod.rs +++ b/hftbacktest/src/backtest/proc/mod.rs @@ -80,6 +80,9 @@ where /// Returns the state's values such as balance, fee, and so on. fn state_values(&self) -> &StateValues; + /// Returns the state's values such as balance, fee, and so on, mutable. + fn state_values_mut(&mut self) -> &mut StateValues; + /// Returns the [`MarketDepth`]. fn depth(&self) -> &MD; @@ -89,6 +92,12 @@ where /// Returns the last market trades. fn last_trades(&self) -> &[Event]; + /// Returns the last trade's price + fn last_trade_price(&self) -> f64; + + /// Returns the last trade's size + fn last_trade_size(&self) -> f64; + /// Clears the last market trades from the buffer. fn clear_last_trades(&mut self); From d578b1363a02863b7924512b97a60e1ce6adc8be Mon Sep 17 00:00:00 2001 From: Marco-Andrea Buchmann Date: Sat, 30 Aug 2025 11:53:48 +0200 Subject: [PATCH 3/5] Adapt state for backtest --- hftbacktest/src/backtest/state.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hftbacktest/src/backtest/state.rs b/hftbacktest/src/backtest/state.rs index cdfa2dd3..97d6eb78 100644 --- a/hftbacktest/src/backtest/state.rs +++ b/hftbacktest/src/backtest/state.rs @@ -28,6 +28,10 @@ where num_trades: 0, trading_volume: 0.0, trading_value: 0.0, + num_messages: 0, + num_creations: 0, + num_modifications: 0, + num_cancellations: 0, }, fee_model, asset_type, @@ -41,6 +45,7 @@ where self.state_values.balance -= amount * AsRef::::as_ref(&order.side); self.state_values.fee += self.fee_model.amount(order, amount); self.state_values.num_trades += 1; + self.state_values.num_messages += 1; self.state_values.trading_volume += order.exec_qty; self.state_values.trading_value += amount; } @@ -59,4 +64,9 @@ where pub fn values(&self) -> &StateValues { &self.state_values } + + #[inline] + pub fn values_mut(&mut self) -> &mut StateValues { + &mut self.state_values + } } From 1e24a40f09bdaf053d8747fe6dca509c3e7f6ebe Mon Sep 17 00:00:00 2001 From: Marco-Andrea Buchmann Date: Sat, 30 Aug 2025 11:56:17 +0200 Subject: [PATCH 4/5] Adapt live files --- hftbacktest/src/live/bot.rs | 27 +++++++++++++++++++++++++-- hftbacktest/src/live/mod.rs | 4 ++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/hftbacktest/src/live/bot.rs b/hftbacktest/src/live/bot.rs index 29f18eb0..87812041 100644 --- a/hftbacktest/src/live/bot.rs +++ b/hftbacktest/src/live/bot.rs @@ -172,12 +172,12 @@ impl LiveBotBuilder { /// Provides the same interface as the backtesters in [`backtest`](`crate::backtest`). /// /// ``` -/// use hftbacktest::{live::{Instrument, LiveBot}, prelude::HashMapMarketDepth}; +/// use hftbacktest::{live::{Instrument, LiveBotBuilder, ipc::iceoryx::IceoryxUnifiedChannel}, prelude::HashMapMarketDepth}; /// /// let tick_size = 0.1; /// let lot_size = 1.0; /// -/// let mut hbt = LiveBot::builder() +/// let hbt: hftbacktest::live::LiveBot = LiveBotBuilder::new() /// .register(Instrument::new( /// "connector_name", /// "symbol", @@ -439,6 +439,11 @@ where &self.instruments.get(asset_no).unwrap().state } + #[inline] + fn mutable_state_values(&mut self, asset_no: usize) -> &mut StateValues { + &mut self.instruments.get_mut(asset_no).unwrap().state + } + #[inline] fn depth(&self, asset_no: usize) -> &MD { &self.instruments.get(asset_no).unwrap().depth @@ -453,6 +458,24 @@ where .as_slice() } + #[inline] + fn last_trade_size(&self, asset_no: usize) -> f64 { + self.instruments + .get(asset_no) + .unwrap() + .last_trade_size + .clone() + } + + #[inline] + fn last_trade_price(&self, asset_no: usize) -> f64 { + self.instruments + .get(asset_no) + .unwrap() + .last_trade_price + .clone() + } + fn clear_last_trades(&mut self, asset_no: Option) { match asset_no { Some(asset_no) => { diff --git a/hftbacktest/src/live/mod.rs b/hftbacktest/src/live/mod.rs index ca839dcb..e70b230f 100644 --- a/hftbacktest/src/live/mod.rs +++ b/hftbacktest/src/live/mod.rs @@ -24,6 +24,8 @@ pub struct Instrument { last_feed_latency: Option<(i64, i64)>, last_order_latency: Option<(i64, i64, i64)>, state: StateValues, + last_trade_size: f64, + last_trade_price: f64, } impl Instrument { @@ -53,6 +55,8 @@ impl Instrument { last_feed_latency: None, last_order_latency: None, state: Default::default(), + last_trade_price: 0.0, + last_trade_size: 0.0, } } } From fc6409325b616fa0a62319d6d0bf02391222d680 Mon Sep 17 00:00:00 2001 From: Marco-Andrea Buchmann Date: Sat, 30 Aug 2025 11:57:44 +0200 Subject: [PATCH 5/5] Update types --- hftbacktest/src/types.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/hftbacktest/src/types.rs b/hftbacktest/src/types.rs index f181973c..f7e8ed21 100644 --- a/hftbacktest/src/types.rs +++ b/hftbacktest/src/types.rs @@ -459,6 +459,15 @@ impl AsRef for OrdType { /// /// **Usage:** /// ``` +/// use hftbacktest::types::AnyClone; +/// use std::any::Any; +/// +/// // Define your custom data type +/// #[derive(Clone)] +/// struct QueuePos { +/// pos: usize, +/// } +/// /// impl AnyClone for QueuePos { /// fn as_any(&self) -> &dyn Any { /// self @@ -746,6 +755,14 @@ pub struct StateValues { pub trading_volume: f64, /// Backtest only pub trading_value: f64, + /// Number of messages (backtest only) + pub num_messages: i64, + /// Number of quote cancellation messages (backtest only) + pub num_cancellations: i64, + /// Number of quote creation messages (backtest only) + pub num_creations: i64, + /// Number of modification messages (backtest only) + pub num_modifications: i64, } /// Provides errors that can occur in builders. @@ -796,6 +813,9 @@ where /// Returns the state's values such as balance, fee, and so on. fn state_values(&self, asset_no: usize) -> &StateValues; + /// Returns the state's values such as balance, fee, and so on, mutable. + fn mutable_state_values(&mut self, asset_no: usize) -> &mut StateValues; + /// Returns the [`MarketDepth`]. /// /// * `asset_no` - Asset number from which the market depth will be retrieved. @@ -806,6 +826,16 @@ where /// * `asset_no` - Asset number from which the last market trades will be retrieved. fn last_trades(&self, asset_no: usize) -> &[Event]; + /// Returns the last trade's price + /// + /// * `asset_no` - Asset number from which the last market trades will be retrieved. + fn last_trade_price(&self, asset_no: usize) -> f64; + + /// Returns the last trade's size + /// + /// * `asset_no` - Asset number from which the last market trades will be retrieved. + fn last_trade_size(&self, asset_no: usize) -> f64; + /// Clears the last market trades from the buffer. /// /// * `asset_no` - Asset number at which this command will be executed. If `None`, all last