use crate::{
error::OrderError,
order::id::{OrderId, VenueOrderId},
};
use chrono::{DateTime, Utc};
use derive_more::{Constructor, From};
use rust_decimal::Decimal;
use rustrade_instrument::{
asset::{AssetIndex, name::AssetNameExchange},
instrument::{InstrumentIndex, name::InstrumentNameExchange},
};
use serde::{Deserialize, Serialize};
pub type UnindexedOrderState = OrderState<AssetNameExchange, InstrumentNameExchange>;
pub type UnindexedInactiveOrderState =
InactiveOrderState<AssetNameExchange, InstrumentNameExchange>;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, From)]
pub enum OrderState<AssetKey = AssetIndex, InstrumentKey = InstrumentIndex> {
Active(ActiveOrderState),
Inactive(InactiveOrderState<AssetKey, InstrumentKey>),
}
impl<AssetKey, InstrumentKey> OrderState<AssetKey, InstrumentKey> {
pub fn active<S>(state: S) -> Self
where
S: Into<ActiveOrderState>,
{
OrderState::Active(state.into())
}
pub fn inactive<S>(state: S) -> Self
where
S: Into<InactiveOrderState<AssetKey, InstrumentKey>>,
{
OrderState::Inactive(state.into())
}
pub fn fully_filled(filled: Filled) -> Self {
Self::Inactive(InactiveOrderState::FullyFilled(filled))
}
pub fn expired(expired: Expired) -> Self {
Self::Inactive(InactiveOrderState::Expired(expired))
}
pub fn time_exchange(&self) -> Option<DateTime<Utc>> {
match self {
Self::Active(active) => match active {
ActiveOrderState::OpenInFlight(_) => None,
ActiveOrderState::Open(state) => Some(state.time_exchange),
ActiveOrderState::CancelInFlight(state) => {
state.order.as_ref().map(|order| order.time_exchange)
}
},
Self::Inactive(inactive) => match inactive {
InactiveOrderState::Cancelled(state) => Some(state.time_exchange),
InactiveOrderState::FullyFilled(state) => Some(state.time_exchange),
InactiveOrderState::Expired(state) => Some(state.time_exchange),
InactiveOrderState::OpenFailed(_) => None,
},
}
}
pub fn is_accepted(&self) -> bool {
!self.is_failed()
}
pub fn is_failed(&self) -> bool {
matches!(self, Self::Inactive(InactiveOrderState::OpenFailed(_)))
}
pub fn is_outcome_unknown(&self) -> bool {
matches!(
self,
Self::Inactive(InactiveOrderState::OpenFailed(error)) if error.is_outcome_unknown()
)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, From)]
pub enum ActiveOrderState {
OpenInFlight(OpenInFlight),
Open(Open),
CancelInFlight(CancelInFlight),
}
impl ActiveOrderState {
pub fn time_sent(&self) -> Option<DateTime<Utc>> {
match self {
Self::OpenInFlight(open) => Some(open.time_sent),
Self::Open(_) => None,
Self::CancelInFlight(cancel) => Some(cancel.time_sent),
}
}
pub fn open_meta(&self) -> Option<&Open> {
match self {
Self::OpenInFlight(_) => None,
Self::Open(open) => Some(open),
Self::CancelInFlight(cancel) => cancel.order.as_ref(),
}
}
}
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
)]
pub struct OpenInFlight {
pub time_sent: DateTime<Utc>,
}
#[derive(
Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
)]
pub struct Open {
pub id: VenueOrderId,
pub time_exchange: DateTime<Utc>,
pub filled_quantity: Decimal,
}
impl Open {
pub fn quantity_remaining(&self, initial_quantity: Decimal) -> Decimal {
initial_quantity - self.filled_quantity
}
pub fn is_superseded_by(&self, update: &Self) -> bool {
if update.filled_quantity < self.filled_quantity {
return false;
}
self.time_exchange <= update.time_exchange || update.filled_quantity > self.filled_quantity
}
}
#[derive(
Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
)]
pub struct Filled {
pub id: OrderId,
pub time_exchange: DateTime<Utc>,
pub filled_quantity: Decimal,
pub avg_price: Option<Decimal>,
}
#[derive(
Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
)]
pub struct CancelInFlight {
pub order: Option<Open>,
pub time_sent: DateTime<Utc>,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, From)]
pub enum InactiveOrderState<AssetKey, InstrumentKey> {
Cancelled(Cancelled),
FullyFilled(Filled),
OpenFailed(OrderError<AssetKey, InstrumentKey>),
Expired(Expired),
}
#[derive(
Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
)]
pub struct Cancelled {
pub id: OrderId,
pub time_exchange: DateTime<Utc>,
pub filled_quantity: Option<Decimal>,
}
#[derive(
Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
)]
pub struct Expired {
pub id: OrderId,
pub time_exchange: DateTime<Utc>,
pub filled_quantity: Option<Decimal>,
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeDelta;
use rust_decimal_macros::dec;
fn open(secs: i64, filled: Decimal) -> Open {
Open {
id: VenueOrderId::Assigned(OrderId::new("1")),
time_exchange: DateTime::<Utc>::MIN_UTC + TimeDelta::seconds(secs),
filled_quantity: filled,
}
}
#[test]
fn a_later_stamp_supersedes() {
assert!(open(0, dec!(0)).is_superseded_by(&open(1, dec!(0))));
}
#[test]
fn an_equal_stamp_supersedes() {
assert!(open(1, dec!(0)).is_superseded_by(&open(1, dec!(0))));
}
#[test]
fn an_earlier_stamp_alone_does_not_supersede() {
assert!(!open(1, dec!(0)).is_superseded_by(&open(0, dec!(0))));
}
#[test]
fn more_filled_supersedes_an_earlier_stamp() {
assert!(open(1, dec!(0.3)).is_superseded_by(&open(0, dec!(0.6))));
}
#[test]
fn less_filled_is_refused_despite_a_later_stamp() {
assert!(!open(0, dec!(0.6)).is_superseded_by(&open(1, dec!(0.3))));
}
#[test]
fn an_unmoved_cumulative_leaves_ordering_to_the_stamp() {
assert!(!open(1, dec!(0.3)).is_superseded_by(&open(0, dec!(0.3))));
assert!(open(0, dec!(0.3)).is_superseded_by(&open(1, dec!(0.3))));
}
}