use std::{
any::Any,
collections::HashMap,
fmt::{Debug, Formatter},
};
use anyhow::Error;
use bincode::{
BorrowDecode,
Decode,
Encode,
de::{BorrowDecoder, Decoder},
enc::Encoder,
error::{DecodeError, EncodeError},
};
use dyn_clone::DynClone;
use hftbacktest_derive::NpyDTyped;
use thiserror::Error;
use crate::{backtest::data::POD, depth::MarketDepth};
#[derive(Clone, Debug, Decode, Encode)]
pub enum Value {
String(String),
Int(i64),
Float(f64),
Bool(bool),
List(Vec<Value>),
Map(HashMap<String, Value>),
Empty,
}
impl Value {
pub fn get_str(&self) -> Option<&str> {
if let Value::String(val) = self {
Some(val.as_str())
} else {
None
}
}
pub fn get_int(&self) -> Option<i64> {
if let Value::Int(val) = self {
Some(*val)
} else {
None
}
}
pub fn get_float(&self) -> Option<f64> {
if let Value::Float(val) = self {
Some(*val)
} else {
None
}
}
pub fn get_bool(&self) -> Option<bool> {
if let Value::Bool(val) = self {
Some(*val)
} else {
None
}
}
pub fn get_list(&self) -> Option<&Vec<Value>> {
if let Value::List(val) = self {
Some(val)
} else {
None
}
}
pub fn get_map(&self) -> Option<&HashMap<String, Value>> {
if let Value::Map(val) = self {
Some(val)
} else {
None
}
}
}
impl From<anyhow::Error> for Value {
fn from(value: Error) -> Self {
Value::String(value.to_string())
}
}
#[derive(Clone, Debug, Decode, Encode)]
pub struct LiveError {
pub kind: ErrorKind,
pub value: Value,
}
impl LiveError {
pub fn new(kind: ErrorKind) -> LiveError {
Self {
kind,
value: Value::Empty,
}
}
pub fn with(kind: ErrorKind, value: Value) -> LiveError {
Self { kind, value }
}
pub fn value(&self) -> &Value {
&self.value
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Decode, Encode)]
pub enum ErrorKind {
ConnectionInterrupted,
CriticalConnectionError,
OrderError,
Custom(i64),
}
#[derive(Clone, Debug, Decode, Encode)]
pub enum LiveEvent {
BatchStart,
BatchEnd,
Feed {
symbol: String,
event: Event,
},
Order {
symbol: String,
order: Order,
},
Position {
symbol: String,
qty: f64,
exch_ts: i64,
},
Error(LiveError),
}
pub const BUY_EVENT: u64 = 1 << 29;
pub const SELL_EVENT: u64 = 1 << 28;
pub const DEPTH_EVENT: u64 = 1;
pub const TRADE_EVENT: u64 = 2;
pub const DEPTH_CLEAR_EVENT: u64 = 3;
pub const DEPTH_SNAPSHOT_EVENT: u64 = 4;
pub const DEPTH_BBO_EVENT: u64 = 5;
pub const ADD_ORDER_EVENT: u64 = 10;
pub const CANCEL_ORDER_EVENT: u64 = 11;
pub const MODIFY_ORDER_EVENT: u64 = 12;
pub const FILL_EVENT: u64 = 13;
pub const EXCH_EVENT: u64 = 1 << 31;
pub const LOCAL_EVENT: u64 = 1 << 30;
pub const LOCAL_DEPTH_CLEAR_EVENT: u64 = DEPTH_CLEAR_EVENT | LOCAL_EVENT;
pub const EXCH_DEPTH_CLEAR_EVENT: u64 = DEPTH_CLEAR_EVENT | EXCH_EVENT;
pub const LOCAL_BID_DEPTH_EVENT: u64 = DEPTH_EVENT | BUY_EVENT | LOCAL_EVENT;
pub const LOCAL_ASK_DEPTH_EVENT: u64 = DEPTH_EVENT | SELL_EVENT | LOCAL_EVENT;
pub const LOCAL_BID_DEPTH_CLEAR_EVENT: u64 = DEPTH_CLEAR_EVENT | BUY_EVENT | LOCAL_EVENT;
pub const LOCAL_ASK_DEPTH_CLEAR_EVENT: u64 = DEPTH_CLEAR_EVENT | SELL_EVENT | LOCAL_EVENT;
pub const LOCAL_BID_DEPTH_SNAPSHOT_EVENT: u64 = DEPTH_SNAPSHOT_EVENT | BUY_EVENT | LOCAL_EVENT;
pub const LOCAL_ASK_DEPTH_SNAPSHOT_EVENT: u64 = DEPTH_SNAPSHOT_EVENT | SELL_EVENT | LOCAL_EVENT;
pub const LOCAL_BID_DEPTH_BBO_EVENT: u64 = DEPTH_BBO_EVENT | BUY_EVENT | LOCAL_EVENT;
pub const LOCAL_ASK_DEPTH_BBO_EVENT: u64 = DEPTH_BBO_EVENT | SELL_EVENT | LOCAL_EVENT;
pub const LOCAL_TRADE_EVENT: u64 = TRADE_EVENT | LOCAL_EVENT;
pub const LOCAL_BUY_TRADE_EVENT: u64 = LOCAL_TRADE_EVENT | BUY_EVENT;
pub const LOCAL_SELL_TRADE_EVENT: u64 = LOCAL_TRADE_EVENT | SELL_EVENT;
pub const EXCH_BID_DEPTH_EVENT: u64 = DEPTH_EVENT | BUY_EVENT | EXCH_EVENT;
pub const EXCH_ASK_DEPTH_EVENT: u64 = DEPTH_EVENT | SELL_EVENT | EXCH_EVENT;
pub const EXCH_BID_DEPTH_CLEAR_EVENT: u64 = DEPTH_CLEAR_EVENT | BUY_EVENT | EXCH_EVENT;
pub const EXCH_ASK_DEPTH_CLEAR_EVENT: u64 = DEPTH_CLEAR_EVENT | SELL_EVENT | EXCH_EVENT;
pub const EXCH_BID_DEPTH_SNAPSHOT_EVENT: u64 = DEPTH_SNAPSHOT_EVENT | BUY_EVENT | EXCH_EVENT;
pub const EXCH_ASK_DEPTH_SNAPSHOT_EVENT: u64 = DEPTH_SNAPSHOT_EVENT | SELL_EVENT | EXCH_EVENT;
pub const EXCH_BID_DEPTH_BBO_EVENT: u64 = DEPTH_BBO_EVENT | BUY_EVENT | EXCH_EVENT;
pub const EXCH_ASK_DEPTH_BBO_EVENT: u64 = DEPTH_BBO_EVENT | SELL_EVENT | EXCH_EVENT;
pub const EXCH_TRADE_EVENT: u64 = TRADE_EVENT | EXCH_EVENT;
pub const EXCH_BUY_TRADE_EVENT: u64 = EXCH_TRADE_EVENT | BUY_EVENT;
pub const EXCH_SELL_TRADE_EVENT: u64 = EXCH_TRADE_EVENT | SELL_EVENT;
pub const LOCAL_ADD_ORDER_EVENT: u64 = LOCAL_EVENT | ADD_ORDER_EVENT;
pub const LOCAL_BID_ADD_ORDER_EVENT: u64 = BUY_EVENT | LOCAL_ADD_ORDER_EVENT;
pub const LOCAL_ASK_ADD_ORDER_EVENT: u64 = SELL_EVENT | LOCAL_ADD_ORDER_EVENT;
pub const LOCAL_CANCEL_ORDER_EVENT: u64 = LOCAL_EVENT | CANCEL_ORDER_EVENT;
pub const LOCAL_MODIFY_ORDER_EVENT: u64 = LOCAL_EVENT | MODIFY_ORDER_EVENT;
pub const LOCAL_FILL_EVENT: u64 = LOCAL_EVENT | FILL_EVENT;
pub const EXCH_ADD_ORDER_EVENT: u64 = EXCH_EVENT | ADD_ORDER_EVENT;
pub const EXCH_BID_ADD_ORDER_EVENT: u64 = BUY_EVENT | EXCH_ADD_ORDER_EVENT;
pub const EXCH_ASK_ADD_ORDER_EVENT: u64 = SELL_EVENT | EXCH_ADD_ORDER_EVENT;
pub const EXCH_CANCEL_ORDER_EVENT: u64 = EXCH_EVENT | CANCEL_ORDER_EVENT;
pub const EXCH_MODIFY_ORDER_EVENT: u64 = EXCH_EVENT | MODIFY_ORDER_EVENT;
pub const EXCH_FILL_EVENT: u64 = EXCH_EVENT | FILL_EVENT;
pub const UNTIL_END_OF_DATA: i64 = i64::MAX;
pub type OrderId = u64;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum WaitOrderResponse {
None,
Any,
Specified { asset_no: usize, order_id: OrderId },
}
#[repr(C, align(64))]
#[derive(Clone, PartialEq, Debug, NpyDTyped, Decode, Encode)]
pub struct Event {
pub ev: u64,
pub exch_ts: i64,
pub local_ts: i64,
pub px: f64,
pub qty: f64,
pub order_id: u64,
pub ival: i64,
pub fval: f64,
}
unsafe impl POD for Event {}
impl Event {
#[inline(always)]
pub fn is(&self, event: u64) -> bool {
if (self.ev & event) != event {
false
} else {
let event_kind = event & 0xff;
if event_kind == 0 {
true
} else {
self.ev & 0xff == event_kind
}
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Decode, Encode)]
#[repr(i8)]
pub enum Side {
Buy = 1,
Sell = -1,
None = 0,
Unsupported = 127,
}
impl AsRef<f64> for Side {
fn as_ref(&self) -> &f64 {
match self {
Side::Buy => &1.0f64,
Side::Sell => &-1.0f64,
Side::None => panic!("Side::None"),
Side::Unsupported => panic!("Side::Unsupported"),
}
}
}
impl AsRef<str> for Side {
fn as_ref(&self) -> &'static str {
match self {
Side::Buy => "BUY",
Side::Sell => "SELL",
Side::None => panic!("Side::None"),
Side::Unsupported => panic!("Side::Unsupported"),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Decode, Encode)]
#[repr(u8)]
pub enum Status {
None = 0,
New = 1,
Expired = 2,
Filled = 3,
Canceled = 4,
PartiallyFilled = 5,
Rejected = 6,
Replaced = 7,
Unsupported = 255,
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Decode, Encode)]
#[repr(u8)]
pub enum TimeInForce {
GTC = 0,
GTX = 1,
FOK = 2,
IOC = 3,
Unsupported = 255,
}
impl AsRef<str> for TimeInForce {
fn as_ref(&self) -> &'static str {
match self {
TimeInForce::GTC => "GTC",
TimeInForce::GTX => "GTX",
TimeInForce::FOK => "FOK",
TimeInForce::IOC => "IOC",
TimeInForce::Unsupported => panic!("TimeInForce::Unsupported"),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Decode, Encode)]
#[repr(u8)]
pub enum OrdType {
Limit = 0,
Market = 1,
Unsupported = 255,
}
impl AsRef<str> for OrdType {
fn as_ref(&self) -> &'static str {
match self {
OrdType::Limit => "LIMIT",
OrdType::Market => "MARKET",
OrdType::Unsupported => panic!("OrdType::Unsupported"),
}
}
}
pub trait AnyClone: DynClone {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
dyn_clone::clone_trait_object!(AnyClone);
impl AnyClone for () {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Clone)]
#[repr(C)]
pub struct Order {
pub qty: f64,
pub leaves_qty: f64,
pub exec_qty: f64,
pub exec_price_tick: i64,
pub price_tick: i64,
pub tick_size: f64,
pub exch_timestamp: i64,
pub local_timestamp: i64,
pub order_id: u64,
pub q: Box<dyn AnyClone + Send>,
pub maker: bool,
pub order_type: OrdType,
pub req: Status,
pub status: Status,
pub side: Side,
pub time_in_force: TimeInForce,
}
impl Order {
pub fn new(
order_id: u64,
price_tick: i64,
tick_size: f64,
qty: f64,
side: Side,
order_type: OrdType,
time_in_force: TimeInForce,
) -> Self {
Self {
qty,
leaves_qty: qty,
price_tick,
tick_size,
side,
time_in_force,
exch_timestamp: 0,
status: Status::None,
local_timestamp: 0,
req: Status::None,
exec_price_tick: 0,
exec_qty: 0.0,
order_id,
q: Box::new(()),
maker: false,
order_type,
}
}
pub fn price(&self) -> f64 {
self.price_tick as f64 * self.tick_size
}
pub fn exec_price(&self) -> f64 {
self.exec_price_tick as f64 * self.tick_size
}
pub fn cancellable(&self) -> bool {
(self.status == Status::New || self.status == Status::PartiallyFilled)
&& self.req == Status::None
}
pub fn active(&self) -> bool {
self.status == Status::New || self.status == Status::PartiallyFilled
}
pub fn pending(&self) -> bool {
self.req != Status::None
}
pub fn update(&mut self, order: &Order) {
if order.exch_timestamp < self.exch_timestamp {
println!(
"Warning: Perhaps an inaccurate order response update occurs: an order previously \
updated by a later exchange timestamp is updated by an earlier one. \
This issue is primarily caused by incorrect or inconsistent timestamp ordering \
across the files.\n \
order={:?}, \
response={:?}",
&self, &order
);
}
self.qty = order.qty;
self.leaves_qty = order.leaves_qty;
self.price_tick = order.price_tick;
self.tick_size = order.tick_size;
self.side = order.side;
self.time_in_force = order.time_in_force;
if order.exch_timestamp > 0 {
self.exch_timestamp = order.exch_timestamp;
}
self.status = order.status;
self.req = order.req;
self.exec_price_tick = order.exec_price_tick;
self.exec_qty = order.exec_qty;
self.order_id = order.order_id;
self.q = order.q.clone();
self.maker = order.maker;
self.order_type = order.order_type;
}
}
impl Debug for Order {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Order")
.field("qty", &self.qty)
.field("leaves_qty", &self.leaves_qty)
.field("price_tick", &self.price_tick)
.field("tick_size", &self.tick_size)
.field("side", &self.side)
.field("time_in_force", &self.time_in_force)
.field("exch_timestamp", &self.exch_timestamp)
.field("status", &self.status)
.field("local_timestamp", &self.local_timestamp)
.field("req", &self.req)
.field("exec_price_tick", &self.exec_price_tick)
.field("exec_qty", &self.exec_qty)
.field("order_id", &self.order_id)
.field("maker", &self.maker)
.field("order_type", &self.order_type)
.finish()
}
}
impl<Context> Decode<Context> for Order {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(Self {
qty: Decode::decode(decoder)?,
leaves_qty: Decode::decode(decoder)?,
exec_qty: Decode::decode(decoder)?,
exec_price_tick: Decode::decode(decoder)?,
price_tick: Decode::decode(decoder)?,
tick_size: Decode::decode(decoder)?,
exch_timestamp: Decode::decode(decoder)?,
local_timestamp: Decode::decode(decoder)?,
order_id: Decode::decode(decoder)?,
q: Box::new(()),
maker: Decode::decode(decoder)?,
order_type: Decode::decode(decoder)?,
req: Decode::decode(decoder)?,
status: Decode::decode(decoder)?,
side: Decode::decode(decoder)?,
time_in_force: Decode::decode(decoder)?,
})
}
}
impl<'de, Context> BorrowDecode<'de, Context> for Order {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(Self {
qty: Decode::decode(decoder)?,
leaves_qty: Decode::decode(decoder)?,
exec_qty: Decode::decode(decoder)?,
exec_price_tick: Decode::decode(decoder)?,
price_tick: Decode::decode(decoder)?,
tick_size: Decode::decode(decoder)?,
exch_timestamp: Decode::decode(decoder)?,
local_timestamp: Decode::decode(decoder)?,
order_id: Decode::decode(decoder)?,
q: Box::new(()),
maker: Decode::decode(decoder)?,
order_type: Decode::decode(decoder)?,
req: Decode::decode(decoder)?,
status: Decode::decode(decoder)?,
side: Decode::decode(decoder)?,
time_in_force: Decode::decode(decoder)?,
})
}
}
impl Encode for Order {
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
self.qty.encode(encoder)?;
self.leaves_qty.encode(encoder)?;
self.exec_qty.encode(encoder)?;
self.exec_price_tick.encode(encoder)?;
self.price_tick.encode(encoder)?;
self.tick_size.encode(encoder)?;
self.exch_timestamp.encode(encoder)?;
self.local_timestamp.encode(encoder)?;
self.order_id.encode(encoder)?;
self.maker.encode(encoder)?;
self.order_type.encode(encoder)?;
self.req.encode(encoder)?;
self.status.encode(encoder)?;
self.side.encode(encoder)?;
self.time_in_force.encode(encoder)?;
Ok(())
}
}
#[derive(Clone, Debug, Encode, Decode)]
pub enum LiveRequest {
Order { symbol: String, order: Order },
RegisterInstrument {
symbol: String,
tick_size: f64,
lot_size: f64,
},
}
#[repr(C)]
#[derive(PartialEq, Clone, Debug, Default)]
pub struct StateValues {
pub position: f64,
pub balance: f64,
pub fee: f64,
pub num_trades: i64,
pub trading_volume: f64,
pub trading_value: f64,
}
#[derive(Error, Debug)]
pub enum BuildError {
#[error("`{0}` is required")]
BuilderIncomplete(&'static str),
#[error("{0}")]
InvalidArgument(&'static str),
#[error("`{0}/{1}` already exists")]
Duplicate(String, String),
#[error("`{0}` is not found")]
ConnectorNotFound(String),
#[error("{0:?}")]
Error(#[from] anyhow::Error),
}
#[derive(Decode, Encode)]
pub struct OrderRequest {
pub order_id: u64,
pub price: f64,
pub qty: f64,
pub side: Side,
pub time_in_force: TimeInForce,
pub order_type: OrdType,
}
pub trait Bot<MD>
where
MD: MarketDepth,
{
type Error;
fn current_timestamp(&self) -> i64;
fn num_assets(&self) -> usize;
fn position(&self, asset_no: usize) -> f64;
fn state_values(&self, asset_no: usize) -> &StateValues;
fn depth(&self, asset_no: usize) -> &MD;
fn last_trades(&self, asset_no: usize) -> &[Event];
fn clear_last_trades(&mut self, asset_no: Option<usize>);
fn orders(&self, asset_no: usize) -> &HashMap<OrderId, Order>;
#[allow(clippy::too_many_arguments)]
fn submit_buy_order(
&mut self,
asset_no: usize,
order_id: OrderId,
price: f64,
qty: f64,
time_in_force: TimeInForce,
order_type: OrdType,
wait: bool,
) -> Result<ElapseResult, Self::Error>;
#[allow(clippy::too_many_arguments)]
fn submit_sell_order(
&mut self,
asset_no: usize,
order_id: OrderId,
price: f64,
qty: f64,
time_in_force: TimeInForce,
order_type: OrdType,
wait: bool,
) -> Result<ElapseResult, Self::Error>;
fn submit_order(
&mut self,
asset_no: usize,
order: OrderRequest,
wait: bool,
) -> Result<ElapseResult, Self::Error>;
fn modify(
&mut self,
asset_no: usize,
order_id: OrderId,
price: f64,
qty: f64,
wait: bool,
) -> Result<ElapseResult, Self::Error>;
fn cancel(
&mut self,
asset_no: usize,
order_id: OrderId,
wait: bool,
) -> Result<ElapseResult, Self::Error>;
fn clear_inactive_orders(&mut self, asset_no: Option<usize>);
fn wait_order_response(
&mut self,
asset_no: usize,
order_id: OrderId,
timeout: i64,
) -> Result<ElapseResult, Self::Error>;
fn wait_next_feed(
&mut self,
include_order_resp: bool,
timeout: i64,
) -> Result<ElapseResult, Self::Error>;
fn elapse(&mut self, duration: i64) -> Result<ElapseResult, Self::Error>;
fn elapse_bt(&mut self, duration: i64) -> Result<ElapseResult, Self::Error>;
fn close(&mut self) -> Result<(), Self::Error>;
fn feed_latency(&self, asset_no: usize) -> Option<(i64, i64)>;
fn order_latency(&self, asset_no: usize) -> Option<(i64, i64, i64)>;
}
pub trait Recorder {
type Error;
fn record<MD, I>(&mut self, hbt: &I) -> Result<(), Self::Error>
where
I: Bot<MD>,
MD: MarketDepth;
}
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum ElapseResult {
Ok,
EndOfData,
MarketFeed,
OrderResponse,
}
#[cfg(test)]
mod tests {
use crate::{
prelude::LOCAL_EVENT,
types::{
BUY_EVENT,
Event,
LOCAL_BID_DEPTH_CLEAR_EVENT,
LOCAL_BID_DEPTH_EVENT,
LOCAL_BID_DEPTH_SNAPSHOT_EVENT,
LOCAL_BUY_TRADE_EVENT,
},
};
#[test]
fn test_event_is() {
let event = Event {
ev: LOCAL_BID_DEPTH_CLEAR_EVENT | (1 << 20),
exch_ts: 0,
local_ts: 0,
order_id: 0,
px: 0.0,
qty: 0.0,
ival: 0,
fval: 0.0,
};
assert!(!event.is(LOCAL_BID_DEPTH_EVENT));
assert!(!event.is(LOCAL_BUY_TRADE_EVENT));
assert!(event.is(LOCAL_BID_DEPTH_CLEAR_EVENT));
let event = Event {
ev: LOCAL_EVENT | BUY_EVENT | 0xff,
exch_ts: 0,
local_ts: 0,
order_id: 0,
px: 0.0,
qty: 0.0,
ival: 0,
fval: 0.0,
};
assert!(!event.is(LOCAL_BID_DEPTH_EVENT));
assert!(!event.is(LOCAL_BUY_TRADE_EVENT));
assert!(!event.is(LOCAL_BID_DEPTH_CLEAR_EVENT));
assert!(!event.is(LOCAL_BID_DEPTH_SNAPSHOT_EVENT));
assert!(event.is(LOCAL_EVENT));
assert!(event.is(BUY_EVENT));
}
}