Skip to content

Commit

Permalink
dev: doc string and refactor.
Browse files Browse the repository at this point in the history
  • Loading branch information
nkaz001 committed May 3, 2024
1 parent d41d1f2 commit 0df6c5a
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 151 deletions.
3 changes: 2 additions & 1 deletion rust/examples/algo.rs
@@ -1,6 +1,7 @@
use std::{collections::HashMap, fmt::Debug};
use tracing::info;

use hftbacktest::prelude::*;
use tracing::info;

pub fn gridtrading<Q, MD, I: Interface<Q, MD>>(
hbt: &mut I,
Expand Down
2 changes: 1 addition & 1 deletion rust/examples/gridtrading_backtest.rs
Expand Up @@ -9,8 +9,8 @@ use hftbacktest::{
MultiAssetMultiExchangeBacktest,
},
depth::HashMapMarketDepth,
prelude::Interface,
};
use hftbacktest::prelude::Interface;

mod algo;

Expand Down
48 changes: 31 additions & 17 deletions rust/src/backtest/backtest.rs
Expand Up @@ -6,7 +6,7 @@ use crate::{
proc::{LocalProcessor, Processor},
reader::{UNTIL_END_OF_DATA, WAIT_ORDER_RESPONSE_NONE},
Asset,
Error,
BacktestError,
},
depth::{HashMapMarketDepth, MarketDepth},
types::{BuildError, Event, Interface, OrdType, Order, Side, StateValues, TimeInForce},
Expand Down Expand Up @@ -46,7 +46,9 @@ where
}
}

/// Multi-asset multi-exchange model backtester
/// This backtester provides multi-asset and multi-exchange model backtesting, allowing you to
/// configure different setups such as queue models or asset types for each asset. However, this may
/// result in slightly slower performance compared to [`MultiAssetSingleExchangeBacktest`].
pub struct MultiAssetMultiExchangeBacktest<Q, MD> {
cur_ts: i64,
evs: EventSet,
Expand Down Expand Up @@ -81,11 +83,11 @@ where
}
}

fn initialize_evs(&mut self) -> Result<(), Error> {
fn initialize_evs(&mut self) -> Result<(), BacktestError> {
for (asset_no, local) in self.local.iter_mut().enumerate() {
match local.initialize_data() {
Ok(ts) => self.evs.update_local_data(asset_no, ts),
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_local_data(asset_no);
}
Err(e) => {
Expand All @@ -96,7 +98,7 @@ where
for (asset_no, exch) in self.exch.iter_mut().enumerate() {
match exch.initialize_data() {
Ok(ts) => self.evs.update_exch_data(asset_no, ts),
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_exch_data(asset_no);
}
Err(e) => {
Expand All @@ -107,7 +109,11 @@ where
Ok(())
}

pub fn goto(&mut self, timestamp: i64, wait_order_response: i64) -> Result<bool, Error> {
pub fn goto(
&mut self,
timestamp: i64,
wait_order_response: i64,
) -> Result<bool, BacktestError> {
loop {
match self.evs.next() {
Some(ev) => {
Expand All @@ -122,7 +128,7 @@ where
Ok((next_ts, _)) => {
self.evs.update_local_data(ev.asset_no, next_ts);
}
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_local_data(ev.asset_no);
}
Err(e) => {
Expand All @@ -144,7 +150,7 @@ where
Ok((next_ts, _)) => {
self.evs.update_exch_data(ev.asset_no, next_ts);
}
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_exch_data(ev.asset_no);
}
Err(e) => {
Expand Down Expand Up @@ -179,7 +185,7 @@ where
Q: Clone,
MD: MarketDepth,
{
type Error = Error;
type Error = BacktestError;

#[inline]
fn current_timestamp(&self) -> i64 {
Expand Down Expand Up @@ -343,6 +349,7 @@ where
}
}

/// `MultiAssetSingleExchangeBacktest` builder.
pub struct MultiAssetSingleExchangeBacktestBuilder<Q, Local, Exchange> {
local: Vec<Local>,
exch: Vec<Exchange>,
Expand Down Expand Up @@ -383,7 +390,10 @@ where
}
}

/// Multi-asset single-exchange model backtester
/// This backtester provides multi-asset and single-exchange model backtesting, meaning all assets
/// have the same setups for models such as asset type or queue model. However, this can be slightly
/// faster than [`MultiAssetMultiExchangeBacktest`]. If you need to configure different models for
/// each asset, use [`MultiAssetMultiExchangeBacktest`].
pub struct MultiAssetSingleExchangeBacktest<Q, MD, Local, Exchange> {
cur_ts: i64,
evs: EventSet,
Expand Down Expand Up @@ -423,11 +433,11 @@ where
}
}

fn initialize_evs(&mut self) -> Result<(), Error> {
fn initialize_evs(&mut self) -> Result<(), BacktestError> {
for (asset_no, local) in self.local.iter_mut().enumerate() {
match local.initialize_data() {
Ok(ts) => self.evs.update_local_data(asset_no, ts),
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_local_data(asset_no);
}
Err(e) => {
Expand All @@ -438,7 +448,7 @@ where
for (asset_no, exch) in self.exch.iter_mut().enumerate() {
match exch.initialize_data() {
Ok(ts) => self.evs.update_exch_data(asset_no, ts),
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_exch_data(asset_no);
}
Err(e) => {
Expand All @@ -449,7 +459,11 @@ where
Ok(())
}

pub fn goto(&mut self, timestamp: i64, wait_order_response: i64) -> Result<bool, Error> {
pub fn goto(
&mut self,
timestamp: i64,
wait_order_response: i64,
) -> Result<bool, BacktestError> {
loop {
match self.evs.next() {
Some(ev) => {
Expand All @@ -464,7 +478,7 @@ where
Ok((next_ts, _)) => {
self.evs.update_local_data(ev.asset_no, next_ts);
}
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_local_data(ev.asset_no);
}
Err(e) => {
Expand All @@ -486,7 +500,7 @@ where
Ok((next_ts, _)) => {
self.evs.update_exch_data(ev.asset_no, next_ts);
}
Err(Error::EndOfData) => {
Err(BacktestError::EndOfData) => {
self.evs.invalidate_exch_data(ev.asset_no);
}
Err(e) => {
Expand Down Expand Up @@ -524,7 +538,7 @@ where
Local: LocalProcessor<Q, MD>,
Exchange: Processor,
{
type Error = Error;
type Error = BacktestError;

#[inline]
fn current_timestamp(&self) -> i64 {
Expand Down
9 changes: 5 additions & 4 deletions rust/src/backtest/mod.rs
Expand Up @@ -21,7 +21,7 @@ pub mod assettype;
mod backtest;
pub use backtest::*;

use crate::types::BuildError;
use crate::{backtest::reader::Data, types::BuildError};

/// Latency and queue position models
pub mod models;
Expand All @@ -40,7 +40,7 @@ pub mod state;
mod evs;

#[derive(Error, Debug)]
pub enum Error {
pub enum BacktestError {
#[error("Order related to a given order id already exists")]
OrderIdExist,
#[error("Order request is in process")]
Expand All @@ -57,9 +57,10 @@ pub enum Error {
DataError(#[from] IoError),
}

#[derive(Clone, Debug)]
pub enum DataSource {
File(String),
Array,
Data(Data<Event>),
}

pub struct Asset<L: ?Sized, E: ?Sized> {
Expand Down Expand Up @@ -120,7 +121,7 @@ where
DataSource::File(filename) => {
self.reader.add_file(filename);
}
DataSource::Array => {
DataSource::Data(data) => {
todo!();
}
}
Expand Down
53 changes: 27 additions & 26 deletions rust/src/backtest/models/latencies.rs
Expand Up @@ -10,22 +10,22 @@ pub trait LatencyModel {
}

/// Provides constant order latency.
///
/// If latency has a negative value, it indicates an order rejection by the exchange and its
/// value represents the latency that the local experiences when receiving the rejection
/// notification.
#[derive(Clone)]
pub struct ConstantLatency {
entry_latency: i64,
response_latency: i64,
}

impl ConstantLatency {
/// Constructs [`ConstantLatency`].
/// Constructs an instance of `ConstantLatency`.
///
/// `entry_latency` and `response_latency` should match the time unit of the data's timestamps.
/// Using nanoseconds across all datasets is recommended, since the live [`crate::live::Bot`]
/// uses nanoseconds.
///
/// If latency has a negative value, it indicates an order rejection by the exchange and its
/// value represents the latency that the local experiences when receiving the rejection
/// notification.
/// Using nanoseconds across all datasets is recommended, since the live
/// [Bot](crate::live::Bot) uses nanoseconds.
pub fn new(entry_latency: i64, response_latency: i64) -> Self {
Self {
entry_latency,
Expand Down Expand Up @@ -60,9 +60,27 @@ pub struct OrderLatencyRow {

/// Provides order latency based on actual historical order latency data through interpolation.
///
/// However, if you don't actual order latency history, you can generate order latencies
/// However, if you don't have the actual order latency history, you can generate order latencies
/// artificially based on feed latency or using a custom model such as a regression model, which
/// incorporates factors like feed latency, trading volume, and the number of events.
///
/// In historical order latency data, negative latencies should not exist. This means that there
/// should be no instances where `exch_timestamp - req_timestamp < 0` or
/// `resp_timestamp - exch_timestamp < 0`. However, it's worth noting that exchanges may
/// inadequately handle or reject orders during overload situations or for technical reasons,
/// resulting in exchange timestamps being zero. In such cases, [entry()](Self::entry()) or
/// [response()](Self::response()) returns negative latency, indicating an order rejection by the
/// exchange, and its value represents the latency that the local experiences when receiving the
/// rejection notification.
///
/// **Example**
/// ```
/// use hftbacktest::backtest::{reader::read_npz, models::IntpOrderLatency};
///
/// let latency_model = IntpOrderLatency::new(
/// read_npz("latency_20240215.npz").unwrap()
/// );
/// ```
#[derive(Clone)]
pub struct IntpOrderLatency {
entry_rn: usize,
Expand All @@ -71,24 +89,7 @@ pub struct IntpOrderLatency {
}

impl IntpOrderLatency {
/// Constructs [`IntpOrderLatency`].
///
/// In historical order latency data, negative latencies should not exist. This means that there
/// should be no instances where `exch_timestamp - req_timestamp < 0` or
/// `resp_timestamp - exch_timestamp < 0`. However, it's worth noting that exchanges may
/// inadequately handle or reject orders during overload situations or for technical reasons,
/// resulting in exchange timestamps being zero. In such cases, [`Self::entry()`] or
/// [`Self::response()`] returns negative latency, indicating an order rejection by the
/// exchange, and its value represents the latency that the local experiences when receiving the
/// rejection notification.
///
/// ```
/// use hftbacktest::backtest::{reader::read_npz, models::IntpOrderLatency};
///
/// let latency_model = IntpOrderLatency::new(
/// read_npz("latency_20240215.npz").unwrap()
/// );
/// ```
/// Constructs an instance of `IntpOrderLatency`.
pub fn new(data: Data<OrderLatencyRow>) -> Self {
if data.len() == 0 {
panic!();
Expand Down
16 changes: 8 additions & 8 deletions rust/src/backtest/models/queue.rs
Expand Up @@ -79,8 +79,8 @@ pub trait Probability {
}

/// Provides a probability-based queue position model as described in
/// https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book
/// https://rigtorp.se/2013/06/08/estimating-order-queue-position.html
/// * https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book
/// * https://rigtorp.se/2013/06/08/estimating-order-queue-position.html
///
/// Your order's queue position advances when a trade occurs at the same price level or the quantity
/// at the level decreases. The advancement in queue position depends on the probability based on
Expand All @@ -99,7 +99,7 @@ impl<P, MD> ProbQueueModel<P, MD>
where
P: Probability,
{
/// Constructs [`ProbQueueModel`] with a [`Probability`] model.
/// Constructs an instance of `ProbQueueModel` with a [`Probability`] model.
pub fn new(prob: P) -> Self {
Self {
prob,
Expand Down Expand Up @@ -163,7 +163,7 @@ pub struct PowerProbQueueFunc {
}

impl PowerProbQueueFunc {
/// Constructs [`PowerProbQueueFunc`].
/// Constructs an instance of `PowerProbQueueFunc`.
pub fn new(n: f32) -> Self {
Self { n }
}
Expand All @@ -184,7 +184,7 @@ impl Probability for PowerProbQueueFunc {
pub struct LogProbQueueFunc(());

impl LogProbQueueFunc {
/// Constructs [`LogProbQueueFunc`].
/// Constructs an instance of `LogProbQueueFunc`.
pub fn new() -> Self {
Self(())
}
Expand All @@ -205,7 +205,7 @@ impl Probability for LogProbQueueFunc {
pub struct LogProbQueueFunc2(());

impl LogProbQueueFunc2 {
/// Constructs [`LogProbQueueFunc2`].
/// Constructs an instance of `LogProbQueueFunc2`.
pub fn new() -> Self {
Self(())
}
Expand All @@ -228,7 +228,7 @@ pub struct PowerProbQueueFunc2 {
}

impl PowerProbQueueFunc2 {
/// Constructs [`PowerProbQueueFunc2`].
/// Constructs an instance of `PowerProbQueueFunc2`.
pub fn new(n: f32) -> Self {
Self { n }
}
Expand All @@ -251,7 +251,7 @@ pub struct PowerProbQueueFunc3 {
}

impl PowerProbQueueFunc3 {
/// Constructs [`PowerProbQueueFunc3`].
/// Constructs an instance of `PowerProbQueueFunc3`.
pub fn new(n: f32) -> Self {
Self { n }
}
Expand Down

0 comments on commit 0df6c5a

Please sign in to comment.