mod binary_yes;
pub mod common;
mod error;
mod multi_runner;
pub mod protocol;
mod two_runner;
use crate::types::*;
pub use binary_yes::BinaryYesBook;
pub use common::*;
pub use error::{BookError, BookErrorDetail, RequestedBookState};
pub use multi_runner::MultiRunnerBook;
use protocol::command::{Command, CommandKind};
pub use protocol::command::{
Persistence, ReduceBinaryOrderCondition, ReduceBinaryOrderTarget, ReduceOrderCondition,
ReduceOrderTarget, TimeInForce,
};
pub use two_runner::TwoRunnerBook;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Book {
TwoRunner(Box<TwoRunnerBook>),
MultiRunner(Box<MultiRunnerBook>),
BinaryYes(Box<BinaryYesBook>),
}
impl Book {
const DEFAULT_ORDER_STORE_CAPACITY: usize = 20_000;
fn default_phase_for_kind(market_kind: MarketKind) -> MarketPhase {
if market_kind == MarketKind::LiveOnly {
MarketPhase::Live
} else {
MarketPhase::Pre
}
}
pub fn new(market_id: MarketId, runner_ids: impl IntoIterator<Item = RunnerId>) -> Self {
Self::new_with_kind(market_id, MarketKind::InPlayCapable, runner_ids)
}
pub fn new_with_kind(
market_id: MarketId,
market_kind: MarketKind,
runner_ids: impl IntoIterator<Item = RunnerId>,
) -> Self {
Self::new_with_kind_and_phase(
market_id,
market_kind,
Self::default_phase_for_kind(market_kind),
runner_ids,
)
}
pub fn new_with_kind_and_phase(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
runner_ids: impl IntoIterator<Item = RunnerId>,
) -> Self {
let runners: Vec<RunnerId> = runner_ids.into_iter().collect();
match runners.len() {
0 | 1 => panic!("Book requires at least 2 runners"),
2 => Book::TwoRunner(Box::new(TwoRunnerBook::new_with_capacity(
market_id,
market_kind,
market_phase,
runners[0],
runners[1],
Self::DEFAULT_ORDER_STORE_CAPACITY,
))),
_ => Book::MultiRunner(Box::new(MultiRunnerBook::new_with_capacity(
market_id,
market_kind,
market_phase,
runners,
Self::DEFAULT_ORDER_STORE_CAPACITY,
))),
}
}
pub fn new_engine(market_id: MarketId) -> Self {
Self::new_engine_with_kind(market_id, MarketKind::InPlayCapable)
}
pub fn new_engine_with_kind(market_id: MarketId, market_kind: MarketKind) -> Self {
Self::new_engine_with_kind_and_phase(
market_id,
market_kind,
Self::default_phase_for_kind(market_kind),
)
}
pub fn new_engine_with_kind_and_phase(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
) -> Self {
Book::MultiRunner(Box::new(MultiRunnerBook::new_engine_with_capacity(
market_id,
market_kind,
market_phase,
Self::DEFAULT_ORDER_STORE_CAPACITY,
)))
}
pub fn new_engine_with_capacity(market_id: MarketId, order_store_capacity: usize) -> Self {
Self::new_engine_with_kind_and_capacity(
market_id,
MarketKind::InPlayCapable,
MarketPhase::Pre,
order_store_capacity,
)
}
pub fn new_engine_with_kind_and_capacity(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
order_store_capacity: usize,
) -> Self {
Book::MultiRunner(Box::new(MultiRunnerBook::new_engine_with_capacity(
market_id,
market_kind,
market_phase,
order_store_capacity,
)))
}
pub fn new_two_runner(market_id: MarketId, runner_a: RunnerId, runner_b: RunnerId) -> Self {
Self::new_two_runner_with_kind(market_id, MarketKind::InPlayCapable, runner_a, runner_b)
}
pub fn new_two_runner_with_kind(
market_id: MarketId,
market_kind: MarketKind,
runner_a: RunnerId,
runner_b: RunnerId,
) -> Self {
Self::new_two_runner_with_kind_and_phase(
market_id,
market_kind,
Self::default_phase_for_kind(market_kind),
runner_a,
runner_b,
)
}
pub fn new_two_runner_with_kind_and_phase(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
runner_a: RunnerId,
runner_b: RunnerId,
) -> Self {
Book::TwoRunner(Box::new(TwoRunnerBook::new_with_capacity(
market_id,
market_kind,
market_phase,
runner_a,
runner_b,
Self::DEFAULT_ORDER_STORE_CAPACITY,
)))
}
pub fn new_two_runner_with_capacity(
market_id: MarketId,
runner_a: RunnerId,
runner_b: RunnerId,
order_store_capacity: usize,
) -> Self {
Self::new_two_runner_with_kind_and_capacity(
market_id,
MarketKind::InPlayCapable,
MarketPhase::Pre,
runner_a,
runner_b,
order_store_capacity,
)
}
pub fn new_two_runner_with_kind_and_capacity(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
runner_a: RunnerId,
runner_b: RunnerId,
order_store_capacity: usize,
) -> Self {
Book::TwoRunner(Box::new(TwoRunnerBook::new_with_capacity(
market_id,
market_kind,
market_phase,
runner_a,
runner_b,
order_store_capacity,
)))
}
pub fn set_close_batch_max_events(&mut self, batch_max_events: u16) {
match self {
Book::TwoRunner(b) => b.set_close_batch_max_events(batch_max_events),
Book::MultiRunner(b) => b.set_close_batch_max_events(batch_max_events),
Book::BinaryYes(b) => b.set_close_batch_max_events(batch_max_events),
}
}
pub fn close_batch_max_events(&self) -> u16 {
match self {
Book::TwoRunner(b) => b.close_batch_max_events(),
Book::MultiRunner(b) => b.close_batch_max_events(),
Book::BinaryYes(b) => b.close_batch_max_events(),
}
}
pub fn new_multi_runner(
market_id: MarketId,
runner_ids: impl IntoIterator<Item = RunnerId>,
) -> Self {
Self::new_multi_runner_with_kind(market_id, MarketKind::InPlayCapable, runner_ids)
}
pub fn new_multi_runner_with_kind(
market_id: MarketId,
market_kind: MarketKind,
runner_ids: impl IntoIterator<Item = RunnerId>,
) -> Self {
Self::new_multi_runner_with_kind_and_phase(
market_id,
market_kind,
Self::default_phase_for_kind(market_kind),
runner_ids,
)
}
pub fn new_multi_runner_with_kind_and_phase(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
runner_ids: impl IntoIterator<Item = RunnerId>,
) -> Self {
Book::MultiRunner(Box::new(MultiRunnerBook::new_with_capacity(
market_id,
market_kind,
market_phase,
runner_ids,
Self::DEFAULT_ORDER_STORE_CAPACITY,
)))
}
pub fn new_multi_runner_with_capacity(
market_id: MarketId,
runner_ids: impl IntoIterator<Item = RunnerId>,
order_store_capacity: usize,
) -> Self {
Self::new_multi_runner_with_kind_and_capacity(
market_id,
MarketKind::InPlayCapable,
MarketPhase::Pre,
runner_ids,
order_store_capacity,
)
}
pub fn new_multi_runner_with_kind_and_capacity(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
runner_ids: impl IntoIterator<Item = RunnerId>,
order_store_capacity: usize,
) -> Self {
Book::MultiRunner(Box::new(MultiRunnerBook::new_with_capacity(
market_id,
market_kind,
market_phase,
runner_ids,
order_store_capacity,
)))
}
pub fn new_binary_yes_with_kind_and_capacity(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
yes_runner_id: RunnerId,
no_runner_id: RunnerId,
max_price_ticks: u16,
order_store_capacity: usize,
) -> Self {
Book::BinaryYes(Box::new(BinaryYesBook::new_with_capacity(
market_id,
market_kind,
market_phase,
yes_runner_id,
no_runner_id,
max_price_ticks,
order_store_capacity,
)))
}
pub fn new_binary_yes_with_kind(
market_id: MarketId,
market_kind: MarketKind,
yes_runner_id: RunnerId,
no_runner_id: RunnerId,
max_price_ticks: u16,
) -> Self {
Self::new_binary_yes_with_kind_and_phase(
market_id,
market_kind,
Self::default_phase_for_kind(market_kind),
yes_runner_id,
no_runner_id,
max_price_ticks,
)
}
pub fn new_binary_yes_with_kind_and_phase(
market_id: MarketId,
market_kind: MarketKind,
market_phase: MarketPhase,
yes_runner_id: RunnerId,
no_runner_id: RunnerId,
max_price_ticks: u16,
) -> Self {
Self::new_binary_yes_with_kind_and_capacity(
market_id,
market_kind,
market_phase,
yes_runner_id,
no_runner_id,
max_price_ticks,
Self::DEFAULT_ORDER_STORE_CAPACITY,
)
}
pub fn new_binary_yes(
market_id: MarketId,
yes_runner_id: RunnerId,
no_runner_id: RunnerId,
max_price_ticks: u16,
) -> Self {
Self::new_binary_yes_with_kind(
market_id,
MarketKind::InPlayCapable,
yes_runner_id,
no_runner_id,
max_price_ticks,
)
}
pub fn set_market_name(&mut self, name: &str) {
match self {
Book::TwoRunner(b) => b.market_name = name.to_owned(),
Book::MultiRunner(b) => b.market_name = name.to_owned(),
Book::BinaryYes(b) => b.market_name = name.to_owned(),
}
}
pub(crate) fn set_market_state(&mut self, state: BookMarketState) {
match self {
Book::TwoRunner(b) => b.set_market_state(state),
Book::MultiRunner(b) => b.set_market_state(state),
Book::BinaryYes(b) => b.set_market_state(state),
}
}
pub fn set_runner_labels(&mut self, runner_ids: &[RunnerId], runner_labels: &[String]) {
match self {
Book::TwoRunner(b) => b.set_runner_labels(runner_ids, runner_labels),
Book::MultiRunner(b) => b.set_runner_labels(runner_ids, runner_labels),
Book::BinaryYes(b) => b.set_runner_labels(runner_ids, runner_labels),
}
}
pub fn market_id(&self) -> MarketId {
match self {
Book::TwoRunner(b) => b.market_id(),
Book::MultiRunner(b) => b.market_id(),
Book::BinaryYes(b) => b.market_id(),
}
}
pub fn market_model(&self) -> MarketModel {
match self {
Book::TwoRunner(_) | Book::MultiRunner(_) => MarketModel::ExchangeOdds,
Book::BinaryYes(b) => MarketModel::BinaryYes {
max_price_ticks: b.max_price_ticks(),
},
}
}
pub fn binary_depth(&self, depth: usize) -> Option<BinaryDepth> {
match self {
Book::BinaryYes(b) => Some(b.depth(depth)),
_ => None,
}
}
pub fn market_state(&self) -> BookMarketState {
match self {
Book::TwoRunner(b) => b.market_state(),
Book::MultiRunner(b) => b.market_state(),
Book::BinaryYes(b) => b.market_state(),
}
}
pub fn market_phase(&self) -> MarketPhase {
match self {
Book::TwoRunner(b) => b.market_phase(),
Book::MultiRunner(b) => b.market_phase(),
Book::BinaryYes(b) => b.market_phase(),
}
}
pub fn is_halted(&self) -> bool {
match self {
Book::TwoRunner(b) => b.is_halted(),
Book::MultiRunner(b) => b.is_halted(),
Book::BinaryYes(b) => b.is_halted(),
}
}
pub fn get_order(&self, order_id: OrderId) -> Option<&BookOrder> {
match self {
Book::TwoRunner(b) => b.get_order(order_id),
Book::MultiRunner(b) => b.get_order(order_id),
Book::BinaryYes(_) => None,
}
}
pub fn is_resting(&self, order_id: OrderId) -> bool {
match self {
Book::TwoRunner(b) => b.is_resting(order_id),
Book::MultiRunner(b) => b.is_resting(order_id),
Book::BinaryYes(b) => b.is_resting(order_id),
}
}
pub fn active_order_count(&self) -> usize {
match self {
Book::TwoRunner(b) => b.active_order_count(),
Book::MultiRunner(b) => b.active_order_count(),
Book::BinaryYes(b) => b.active_order_count(),
}
}
pub fn batch_process_state(&self) -> Option<&BatchProcessState> {
match self {
Book::TwoRunner(b) => b.batch_process_state(),
Book::MultiRunner(b) => b.batch_process_state(),
Book::BinaryYes(b) => b.batch_process_state(),
}
}
pub fn runners(&self) -> Box<dyn Iterator<Item = RunnerId> + '_> {
match self {
Book::TwoRunner(b) => Box::new(b.runners()),
Book::MultiRunner(b) => Box::new(b.runners()),
Book::BinaryYes(b) => Box::new(b.runners()),
}
}
pub fn runner_prices(&self, runner_id: RunnerId, depth: usize) -> RunnerPrices {
match self {
Book::TwoRunner(b) => b.runner_prices(runner_id, depth),
Book::MultiRunner(b) => b.runner_prices(runner_id, depth),
Book::BinaryYes(b) => b.runner_prices(runner_id, depth),
}
}
pub fn best_back_price(&self, runner_id: RunnerId) -> Option<PriceSize> {
match self {
Book::TwoRunner(b) => b.best_back_price(runner_id),
Book::MultiRunner(b) => b.best_back_price(runner_id),
Book::BinaryYes(b) => b.best_back_price(runner_id),
}
}
pub fn best_lay_price(&self, runner_id: RunnerId) -> Option<PriceSize> {
match self {
Book::TwoRunner(b) => b.best_lay_price(runner_id),
Book::MultiRunner(b) => b.best_lay_price(runner_id),
Book::BinaryYes(b) => b.best_lay_price(runner_id),
}
}
pub fn runner_matched_volume(&self, runner_id: RunnerId) -> Money {
match self {
Book::TwoRunner(b) => b.runner_matched_volume(runner_id),
Book::MultiRunner(b) => b.runner_matched_volume(runner_id),
Book::BinaryYes(b) => b.runner_matched_volume(runner_id),
}
}
pub fn runner_label(&self, runner_id: RunnerId) -> &str {
match self {
Book::TwoRunner(b) => b.runner_label(runner_id),
Book::MultiRunner(b) => b.runner_label(runner_id),
Book::BinaryYes(b) => b.runner_label(runner_id),
}
}
pub fn total_matched(&self) -> Money {
match self {
Book::TwoRunner(b) => b.total_matched(),
Book::MultiRunner(b) => b.total_matched(),
Book::BinaryYes(b) => b.total_matched(),
}
}
pub fn handle(
&mut self,
cmd: &Command,
) -> Result<(Vec<BookEventEnvelope>, CommandResponse), BookError> {
let (mut events, resp) = match self {
Book::TwoRunner(b) => {
let (events, resp) = b.handle_command(cmd)?;
(events.into_vec(), resp)
}
Book::MultiRunner(b) => b.handle_command(cmd)?,
Book::BinaryYes(b) => b.handle_command(cmd)?,
};
Self::attach_metadata_to_events(cmd, &mut events, &cmd.metadata);
Ok((events, resp))
}
fn attach_metadata_to_events(
cmd: &Command,
events: &mut [BookEventEnvelope],
metadata: &Option<serde_json::Value>,
) {
if metadata.is_none() || matches!(&cmd.kind, CommandKind::ContinueBatchProcess) {
return;
}
if matches!(&cmd.kind, CommandKind::BatchCancelOrders { .. }) {
if let Some(first) = events.first_mut() {
first.metadata = metadata.clone();
}
return;
}
for event in events {
event.metadata = metadata.clone();
}
}
pub fn apply_event(&mut self, env: &BookEventEnvelope) {
debug_assert_eq!(
env.market_id,
self.market_id(),
"event market_id mismatch: expected {:?}, got {:?}",
self.market_id(),
env.market_id
);
match self {
Book::TwoRunner(b) => b.apply_event(env),
Book::MultiRunner(b) => b.apply_event(env),
Book::BinaryYes(b) => b.apply_event(env),
}
}
pub fn apply_all_events(&mut self, envs: &[BookEventEnvelope]) {
for env in envs {
self.apply_event(env);
}
}
}