use super::super::RunnerResult;
use crate::types::*;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
PartialEq,
Eq,
Hash,
strum::Display,
strum::EnumString,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum Side {
Yes,
No,
}
#[allow(non_upper_case_globals)]
impl Side {
pub const Back: Self = Self::Yes;
pub const Lay: Self = Self::No;
pub const Buy: Self = Self::Yes;
pub const Sell: Self = Self::No;
}
pub type BinarySide = Side;
pub type BinaryTimeInForce = TimeInForce;
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
PartialEq,
Eq,
Hash,
strum::Display,
strum::EnumString,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum Persistence {
Lapse,
Persist,
MarketOnClose,
}
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
PartialEq,
Eq,
Hash,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TimeInForce {
Gtc,
FillOrKill { min_fill: Option<Quantity> },
}
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
PartialEq,
Eq,
Hash,
strum::Display,
strum::EnumString,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum MarketState {
Open,
Suspended,
Closed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Command {
#[serde(default)]
pub correlation_id: Option<CorrelationId>,
#[serde(default)]
pub metadata: Option<serde_json::Value>,
pub market_id: MarketId,
pub kind: CommandKind,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CommandKind {
CreateMarket {
name: String,
market_model: MarketModel,
market_kind: MarketKind,
#[serde(default)]
market_phase: MarketPhase,
runner_ids: Vec<RunnerId>,
runner_labels: Vec<String>,
},
PlaceOrder {
runner_id: RunnerId,
account_id: AccountId,
client_order_id: Option<ClientOrderId>,
side: Side,
odds: OddsX10000,
stake: Money,
persistence: Persistence,
time_in_force: TimeInForce,
},
PlaceBinaryOrder {
account_id: AccountId,
client_order_id: Option<ClientOrderId>,
side: Side,
price_ticks: u16,
qty_shares: u64,
time_in_force: TimeInForce,
},
CancelOrder {
account_id: AccountId,
order_id: OrderId,
},
ReplaceOrder {
account_id: AccountId,
order_id: OrderId,
new_odds: Option<OddsX10000>,
new_stake: Option<Money>,
},
ReplaceBinaryOrder {
account_id: AccountId,
order_id: OrderId,
new_price_ticks: Option<u16>,
new_qty_shares: Option<u64>,
},
SetMarketState { state: MarketState },
SetMarketPhase { phase: MarketPhase },
CloseMarket,
ContinueCloseMarket,
BatchCancelOrders {
from_created_at_inclusive: Option<DateTime>,
to_created_at_inclusive: Option<DateTime>,
account_id: Option<AccountId>,
runner_id: Option<RunnerId>,
batch_max_events: u16,
reason: String,
},
ContinueBatchCancelOrders,
ContinueLapseOrders,
ContinueVoidOrders,
RemoveRunner {
runner_id: RunnerId,
reduction_factor_bps: Option<u32>,
},
VoidTradesFromTime {
from_matched_at_inclusive: DateTime,
reason: String,
},
VoidMarket { reason: String },
SettleMarket {
runner_results: Vec<(RunnerId, RunnerResult)>,
dead_heat_divisor: Option<u32>,
},
HaltMarket { reason: u32 },
ResumeMarket,
RemoveMarket,
}
impl Command {
pub fn market_id(&self) -> MarketId {
self.market_id
}
}
impl CommandKind {
#[inline]
pub fn is_internal_batch_continue(&self) -> bool {
matches!(
self,
Self::ContinueCloseMarket
| Self::ContinueBatchCancelOrders
| Self::ContinueLapseOrders
| Self::ContinueVoidOrders
)
}
#[inline]
pub fn may_affect_batch_scheduler(&self) -> bool {
matches!(
self,
Self::SetMarketState {
state: MarketState::Closed | MarketState::Suspended
} | Self::SetMarketPhase { .. }
| Self::CloseMarket
| Self::ContinueCloseMarket
| Self::BatchCancelOrders { .. }
| Self::ContinueBatchCancelOrders
| Self::ContinueLapseOrders
| Self::ContinueVoidOrders
| Self::VoidMarket { .. }
)
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)
}
}
impl fmt::Display for CommandKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
CommandKind::CreateMarket { .. } => "CREATE_MARKET",
CommandKind::PlaceOrder { .. } => "PLACE_ORDER",
CommandKind::PlaceBinaryOrder { .. } => "PLACE_BINARY_ORDER",
CommandKind::CancelOrder { .. } => "CANCEL_ORDER",
CommandKind::ReplaceOrder { .. } => "REPLACE_ORDER",
CommandKind::ReplaceBinaryOrder { .. } => "REPLACE_BINARY_ORDER",
CommandKind::SetMarketState { .. } => "SET_MARKET_STATE",
CommandKind::SetMarketPhase { .. } => "SET_MARKET_PHASE",
CommandKind::CloseMarket => "CLOSE_MARKET",
CommandKind::ContinueCloseMarket => "CONTINUE_CLOSE_MARKET",
CommandKind::BatchCancelOrders { .. } => "BATCH_CANCEL_ORDERS",
CommandKind::ContinueBatchCancelOrders => "CONTINUE_BATCH_CANCEL_ORDERS",
CommandKind::ContinueLapseOrders => "CONTINUE_LAPSE_ORDERS",
CommandKind::ContinueVoidOrders => "CONTINUE_VOID_ORDERS",
CommandKind::RemoveRunner { .. } => "REMOVE_RUNNER",
CommandKind::VoidTradesFromTime { .. } => "VOID_TRADES_FROM_TIME",
CommandKind::VoidMarket { .. } => "VOID_MARKET",
CommandKind::SettleMarket { .. } => "SETTLE_MARKET",
CommandKind::HaltMarket { .. } => "HALT_MARKET",
CommandKind::ResumeMarket => "RESUME_MARKET",
CommandKind::RemoveMarket => "REMOVE_MARKET",
};
write!(f, "{s}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn may_affect_batch_scheduler_flags_only_batch_relevant_commands() {
assert!(CommandKind::CloseMarket.may_affect_batch_scheduler());
assert!(CommandKind::ContinueCloseMarket.may_affect_batch_scheduler());
assert!(
CommandKind::BatchCancelOrders {
from_created_at_inclusive: None,
to_created_at_inclusive: None,
account_id: None,
runner_id: None,
batch_max_events: 16,
reason: "x".to_string(),
}
.may_affect_batch_scheduler()
);
assert!(CommandKind::ContinueBatchCancelOrders.may_affect_batch_scheduler());
assert!(CommandKind::ContinueLapseOrders.may_affect_batch_scheduler());
assert!(CommandKind::ContinueVoidOrders.may_affect_batch_scheduler());
assert!(
CommandKind::VoidMarket {
reason: "x".to_string(),
}
.may_affect_batch_scheduler()
);
assert!(
CommandKind::SetMarketPhase {
phase: MarketPhase::Live,
}
.may_affect_batch_scheduler()
);
assert!(
CommandKind::SetMarketState {
state: MarketState::Closed,
}
.may_affect_batch_scheduler()
);
assert!(
CommandKind::SetMarketState {
state: MarketState::Suspended,
}
.may_affect_batch_scheduler()
);
assert!(
!CommandKind::SetMarketState {
state: MarketState::Open,
}
.may_affect_batch_scheduler()
);
assert!(
!CommandKind::PlaceOrder {
runner_id: RunnerId(1),
account_id: AccountId::from(1_u64),
client_order_id: None,
side: Side::Yes,
odds: OddsX10000(20_000),
stake: Money(100),
persistence: Persistence::Persist,
time_in_force: TimeInForce::Gtc,
}
.may_affect_batch_scheduler()
);
}
}