From 87a6ae5be72aa07064f9d40fa7d7397786f14f65 Mon Sep 17 00:00:00 2001 From: nkaz001 Date: Mon, 17 Jun 2024 08:09:04 -0400 Subject: [PATCH] feat(rust): add L3MultiAssetSingleExchangebacktest. --- rust/examples/algo.rs | 4 +- rust/examples/gridtrading_backtest_args.rs | 11 ++-- rust/src/backtest/backtest.rs | 45 ++++++++------- rust/src/backtest/mod.rs | 5 +- rust/src/backtest/proc/l3_local.rs | 7 +-- rust/src/backtest/proc/local.rs | 2 +- rust/src/backtest/proc/mod.rs | 2 + rust/src/backtest/proc/proc.rs | 64 +++++++++++++++++++++- rust/src/backtest/recorder.rs | 6 +- rust/src/depth/mod.rs | 2 +- rust/src/live/bot.rs | 12 ++-- rust/src/types.rs | 10 ++-- 12 files changed, 120 insertions(+), 50 deletions(-) diff --git a/rust/examples/algo.rs b/rust/examples/algo.rs index da34dbc..262acd6 100644 --- a/rust/examples/algo.rs +++ b/rust/examples/algo.rs @@ -14,7 +14,7 @@ pub fn gridtrading( ) -> Result<(), i64> where MD: MarketDepth, - I: Interface + BotDepth, + I: Interface + BotTypedDepth, ::Error: Debug, R: Recorder, ::Error: Debug, @@ -29,7 +29,7 @@ where recorder.record(hbt).unwrap(); } - let depth = hbt.depth_concrete(0); + let depth = hbt.depth_typed(0); let position = hbt.position(0); if depth.best_bid_tick() == INVALID_MIN || depth.best_ask_tick() == INVALID_MAX { diff --git a/rust/examples/gridtrading_backtest_args.rs b/rust/examples/gridtrading_backtest_args.rs index 09f327d..ccbe547 100644 --- a/rust/examples/gridtrading_backtest_args.rs +++ b/rust/examples/gridtrading_backtest_args.rs @@ -1,15 +1,16 @@ -use algo::gridtrading; use clap::Parser; + +use algo::gridtrading; use hftbacktest::{ backtest::{ - assettype::LinearAsset, - models::{IntpOrderLatency, PowerProbQueueFunc3, ProbQueueModel, QueuePos}, - reader::read_npz, - recorder::BacktestRecorder, AssetBuilder, + assettype::LinearAsset, DataSource, ExchangeKind, + models::{IntpOrderLatency, PowerProbQueueFunc3, ProbQueueModel}, MultiAssetMultiExchangeBacktest, + reader::read_npz, + recorder::BacktestRecorder, }, prelude::{ApplySnapshot, HashMapMarketDepth, Interface}, }; diff --git a/rust/src/backtest/backtest.rs b/rust/src/backtest/backtest.rs index 190c1d3..6a1a2e0 100644 --- a/rust/src/backtest/backtest.rs +++ b/rust/src/backtest/backtest.rs @@ -8,9 +8,9 @@ use crate::{ BacktestError, }, depth::{HashMapMarketDepth, MarketDepth}, - prelude::{BotDepth, OrderRequest}, + prelude::{BotTypedDepth, OrderRequest}, types::{ - BotTrade, + BotTypedTrade, BuildError, Event, Interface, @@ -23,16 +23,21 @@ use crate::{ WAIT_ORDER_RESPONSE_NONE, }, }; +#[cfg(feature = "unstable_l3")] +use crate::{ + backtest::proc::GenLocalProcessor, + depth::L3MarketDepth +}; /// [`MultiAssetMultiExchangeBacktest`] builder. pub struct MultiAssetMultiExchangeBacktestBuilder { - local: Vec>>, + local: Vec>>, exch: Vec>, } impl MultiAssetMultiExchangeBacktestBuilder { /// Adds [`Asset`], which will undergo simulation within the backtester. - pub fn add(self, asset: Asset, dyn Processor>) -> Self { + pub fn add(self, asset: Asset, dyn Processor>) -> Self { let mut self_ = Self { ..self }; self_.local.push(asset.local); self_.exch.push(asset.exch); @@ -60,7 +65,7 @@ impl MultiAssetMultiExchangeBacktestBuilder { pub struct MultiAssetMultiExchangeBacktest { cur_ts: i64, evs: EventSet, - local: Vec>>, + local: Vec>>, exch: Vec>, } @@ -75,7 +80,7 @@ where } } - pub fn new(local: Vec>>, exch: Vec>) -> Self { + pub fn new(local: Vec>>, exch: Vec>) -> Self { let num_assets = local.len(); if local.len() != num_assets || exch.len() != num_assets { panic!(); @@ -509,22 +514,22 @@ where } } -impl BotDepth for MultiAssetMultiExchangeBacktest +impl BotTypedDepth for MultiAssetMultiExchangeBacktest where MD: MarketDepth, { #[inline] - fn depth_concrete(&self, asset_no: usize) -> &MD { + fn depth_typed(&self, asset_no: usize) -> &MD { &self.local.get(asset_no).unwrap().depth() } } -impl BotTrade for MultiAssetMultiExchangeBacktest +impl BotTypedTrade for MultiAssetMultiExchangeBacktest where MD: MarketDepth, { #[inline] - fn trade_concrete(&self, asset_no: usize) -> &Vec { + fn trade_typed(&self, asset_no: usize) -> &Vec { let local = self.local.get(asset_no).unwrap(); local.trade() } @@ -538,7 +543,7 @@ pub struct MultiAssetSingleExchangeBacktestBuilder { impl MultiAssetSingleExchangeBacktestBuilder where - Local: LocalProcessor + 'static, + Local: LocalProcessor + 'static, Exchange: Processor + 'static, { /// Adds [`Asset`], which will undergo simulation within the backtester. @@ -583,7 +588,7 @@ pub struct MultiAssetSingleExchangeBacktest { impl MultiAssetSingleExchangeBacktest where MD: MarketDepth, - Local: LocalProcessor, + Local: LocalProcessor, Exchange: Processor, { pub fn builder() -> MultiAssetSingleExchangeBacktestBuilder { @@ -716,7 +721,7 @@ where impl Interface for MultiAssetSingleExchangeBacktest where MD: MarketDepth, - Local: LocalProcessor, + Local: LocalProcessor, Exchange: Processor, { type Error = BacktestError; @@ -1025,27 +1030,27 @@ where } } -impl BotDepth for MultiAssetSingleExchangeBacktest +impl BotTypedDepth for MultiAssetSingleExchangeBacktest where MD: MarketDepth, - Local: LocalProcessor, + Local: LocalProcessor, Exchange: Processor, { #[inline] - fn depth_concrete(&self, asset_no: usize) -> &MD { + fn depth_typed(&self, asset_no: usize) -> &MD { &self.local.get(asset_no).unwrap().depth() } } -impl BotTrade for MultiAssetSingleExchangeBacktest +impl BotTypedTrade for MultiAssetSingleExchangeBacktest where MD: MarketDepth, - Local: LocalProcessor, + Local: LocalProcessor, Exchange: Processor, { #[inline] - fn trade_concrete(&self, asset_no: usize) -> &Vec { + fn trade_typed(&self, asset_no: usize) -> &Vec { let local = self.local.get(asset_no).unwrap(); local.trade() } -} +} \ No newline at end of file diff --git a/rust/src/backtest/mod.rs b/rust/src/backtest/mod.rs index e3651d4..0213d1a 100644 --- a/rust/src/backtest/mod.rs +++ b/rust/src/backtest/mod.rs @@ -38,6 +38,9 @@ pub mod recorder; mod evs; +#[cfg(feature = "unstable_l3")] +mod l3backtest; + #[derive(Error, Debug)] pub enum BacktestError { #[error("Order related to a given order id already exists")] @@ -206,7 +209,7 @@ where } /// Builds an `Asset`. - pub fn build(self) -> Result, dyn Processor>, BuildError> { + pub fn build(self) -> Result, dyn Processor>, BuildError> { let ob_local_to_exch = OrderBus::new(); let ob_exch_to_local = OrderBus::new(); diff --git a/rust/src/backtest/proc/l3_local.rs b/rust/src/backtest/proc/l3_local.rs index bd248f3..f331fa0 100644 --- a/rust/src/backtest/proc/l3_local.rs +++ b/rust/src/backtest/proc/l3_local.rs @@ -107,7 +107,7 @@ where } } -impl LocalProcessor for L3Local +impl LocalProcessor for L3Local where AT: AssetType, LM: LatencyModel, @@ -215,9 +215,8 @@ where &self.orders } - fn trade(&self) -> &Vec { - todo!() - // &self.trades + fn trade(&self) -> &Vec { + &self.trades } fn clear_last_trades(&mut self) { diff --git a/rust/src/backtest/proc/local.rs b/rust/src/backtest/proc/local.rs index db5af81..e572a02 100644 --- a/rust/src/backtest/proc/local.rs +++ b/rust/src/backtest/proc/local.rs @@ -107,7 +107,7 @@ where } } -impl LocalProcessor for Local +impl LocalProcessor for Local where AT: AssetType, LM: LatencyModel, diff --git a/rust/src/backtest/proc/mod.rs b/rust/src/backtest/proc/mod.rs index d1fe016..7cc0026 100644 --- a/rust/src/backtest/proc/mod.rs +++ b/rust/src/backtest/proc/mod.rs @@ -18,3 +18,5 @@ mod l3_nopartialfillexchange; pub use l3_local::L3Local; #[cfg(feature = "unstable_l3")] pub use l3_nopartialfillexchange::L3NoPartialFillExchange; +#[cfg(feature = "unstable_l3")] +pub use proc::GenLocalProcessor; \ No newline at end of file diff --git a/rust/src/backtest/proc/proc.rs b/rust/src/backtest/proc/proc.rs index a0251a1..7addff7 100644 --- a/rust/src/backtest/proc/proc.rs +++ b/rust/src/backtest/proc/proc.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::collections::HashMap; use crate::{ @@ -7,7 +8,7 @@ use crate::{ }; /// Provides local-specific interaction. -pub trait LocalProcessor: Processor +pub trait LocalProcessor : Processor where MD: MarketDepth, { @@ -56,7 +57,7 @@ where fn orders(&self) -> &HashMap; /// Returns the last market trades. - fn trade(&self) -> &Vec; + fn trade(&self) -> &Vec; /// Clears the last market trades from the buffer. fn clear_last_trades(&mut self); @@ -96,3 +97,62 @@ pub trait Processor { /// the corresponding processor. fn earliest_send_order_timestamp(&self) -> i64; } + +pub trait GenLocalProcessor : Processor { + /// Submits a new order. + /// + /// * `order_id` - The unique order ID; there should not be any existing order with the same ID + /// on both local and exchange sides. + /// * `price` - Order price. + /// * `qty` - Quantity to buy. + /// * `order_type` - Available [`OrdType`] options vary depending on the exchange model. See to + /// the exchange model for details. + /// * `time_in_force` - Available [`TimeInForce`] options vary depending on the exchange model. + /// See to the exchange model for details. + /// * `current_timestamp` - The current backtesting timestamp. + fn submit_order( + &mut self, + order_id: i64, + side: Side, + price: f32, + qty: f32, + order_type: OrdType, + time_in_force: TimeInForce, + current_timestamp: i64, + ) -> Result<(), BacktestError>; + + /// Cancels the specified order. + /// + /// * `order_id` - Order ID to cancel. + /// * `current_timestamp` - The current backtesting timestamp. + fn cancel(&mut self, order_id: i64, current_timestamp: i64) -> Result<(), BacktestError>; + + /// Clears inactive orders from the local orders whose status is neither + /// [`Status::New`] nor [`Status::PartiallyFilled`]. + fn clear_inactive_orders(&mut self); + + /// Returns the position you currently hold. + fn position(&self) -> f64; + + /// Returns the state's values such as balance, fee, and so on. + fn state_values(&self) -> StateValues; + + /// Returns the [`MarketDepth`]. + fn depth(&self) -> &dyn Any; + + /// Returns a hash map of order IDs and their corresponding [`Order`]s. + fn orders(&self) -> &HashMap; + + /// Returns the last market trades. + fn trade(&self) -> Vec<&dyn Any>; + + /// Clears the last market trades from the buffer. + fn clear_last_trades(&mut self); + + /// Returns the last feed's exchange timestamp and local receipt timestamp. + fn feed_latency(&self) -> Option<(i64, i64)>; + + /// Returns the last order's request timestamp, exchange timestamp, and response receipt + /// timestamp. + fn order_latency(&self) -> Option<(i64, i64, i64)>; +} \ No newline at end of file diff --git a/rust/src/backtest/recorder.rs b/rust/src/backtest/recorder.rs index 6948d83..1163de8 100644 --- a/rust/src/backtest/recorder.rs +++ b/rust/src/backtest/recorder.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ depth::MarketDepth, - types::{BotDepth, Interface, Recorder}, + types::{BotTypedDepth, Interface, Recorder}, }; /// Provides recording of the backtesting strategy's state values, which are needed to compute @@ -20,12 +20,12 @@ impl Recorder for BacktestRecorder { fn record(&mut self, hbt: &mut I) -> Result<(), Self::Error> where - I: Interface + BotDepth, + I: Interface + BotTypedDepth, MD: MarketDepth, { let timestamp = hbt.current_timestamp(); for asset_no in 0..hbt.num_assets() { - let depth = hbt.depth_concrete(asset_no); + let depth = hbt.depth_typed(asset_no); let mid_price = (depth.best_bid() + depth.best_ask()) / 2.0; let state_values = hbt.state_values(asset_no); let values = unsafe { self.values.get_unchecked_mut(asset_no) }; diff --git a/rust/src/depth/mod.rs b/rust/src/depth/mod.rs index 6b49ad6..757e3c1 100644 --- a/rust/src/depth/mod.rs +++ b/rust/src/depth/mod.rs @@ -80,7 +80,7 @@ pub trait ApplySnapshot { } #[cfg(feature = "unstable_l3")] -pub trait L3MarketDepth: MarketDepth { +pub trait L3MarketDepth : MarketDepth { type Error; fn add_buy_order( diff --git a/rust/src/live/bot.rs b/rust/src/live/bot.rs index fd0c0b3..f341e74 100644 --- a/rust/src/live/bot.rs +++ b/rust/src/live/bot.rs @@ -20,8 +20,8 @@ use crate::{ live::Asset, prelude::OrderRequest, types::{ - BotDepth, - BotTrade, + BotTypedDepth, + BotTypedTrade, BuildError, Error as ErrorEvent, Error, @@ -710,20 +710,20 @@ where } } -impl BotDepth for Bot +impl BotTypedDepth for Bot where MD: MarketDepth, { - fn depth_concrete(&self, asset_no: usize) -> &MD { + fn depth_typed(&self, asset_no: usize) -> &MD { self.depth.get(asset_no).unwrap() } } -impl BotTrade for Bot +impl BotTypedTrade for Bot where MD: MarketDepth, { - fn trade_concrete(&self, asset_no: usize) -> &Vec { + fn trade_typed(&self, asset_no: usize) -> &Vec { self.trade.get(asset_no).unwrap() } } diff --git a/rust/src/types.rs b/rust/src/types.rs index 78a4edf..d899e07 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -820,26 +820,26 @@ pub trait Interface { } /// Provides an interface for a backtester or a bot. -pub trait BotDepth { +pub trait BotTypedDepth { /// Returns the [MarketDepth](crate::depth::MarketDepth). /// /// * `asset_no` - Asset number from which the market depth will be retrieved. - fn depth_concrete(&self, asset_no: usize) -> &MD; + fn depth_typed(&self, asset_no: usize) -> &MD; } /// Provides an interface for a backtester or a bot. -pub trait BotTrade { +pub trait BotTypedTrade { /// Returns the last market trades. /// /// * `asset_no` - Asset number from which the last market trades will be retrieved. - fn trade_concrete(&self, asset_no: usize) -> &Vec; + fn trade_typed(&self, asset_no: usize) -> &Vec; } pub trait Recorder { type Error; fn record(&mut self, hbt: &mut I) -> Result<(), Self::Error> where - I: Interface + BotDepth, + I: Interface + BotTypedDepth, MD: MarketDepth; }