use super::OrderBook;
use crate::{QueueEntry, command::*, orders::*, outcome::*, types::*};
impl OrderBook {
pub(super) fn execute_submit(&mut self, meta: CommandMeta, cmd: &SubmitCmd) -> CommandOutcome {
let (was_bid_empty, was_ask_empty) = (
self.is_side_empty(Side::Buy),
self.is_side_empty(Side::Sell),
);
let result = match &cmd.order {
NewOrder::Market(order) => self.submit_market_order(meta.sequence_number, order),
NewOrder::Limit(order) => self.submit_limit_order(meta, order),
NewOrder::Pegged(order) => self.submit_pegged_order(meta, order),
NewOrder::PriceConditional(order) => self.submit_price_conditional_order(meta, order),
};
let target = match result {
Ok(outcome) => outcome,
Err(failure) => return CommandOutcome::Rejected(failure),
};
let triggered = self.process_order_cascade(meta, was_bid_empty, was_ask_empty);
CommandOutcome::Applied(CommandReport::Submit(CommandEffects::new(
target, triggered,
)))
}
fn submit_market_order(
&mut self,
sequence_number: SequenceNumber,
order: &MarketOrder,
) -> Result<OrderOutcome, CommandFailure> {
order.validate().map_err(CommandFailure::InvalidCommand)?;
Ok(self.submit_validated_market_order(
sequence_number,
OrderId::from(sequence_number),
order,
))
}
pub(super) fn submit_validated_market_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &MarketOrder,
) -> OrderOutcome {
let mut outcome = OrderOutcome::new(id);
if self.is_side_empty(order.side().opposite()) {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.quantity(),
available: Quantity(0),
});
return outcome;
}
let result = self.match_order(sequence_number, order.side(), None, order.quantity());
let executed_quantity = result.executed_quantity();
outcome.set_match_result(result);
let remaining_quantity = order.quantity() - executed_quantity;
if remaining_quantity.is_zero() {
return outcome;
}
if order.market_to_limit() {
let price = self.last_trade_price.unwrap();
self.add_limit_order(
sequence_number,
id,
LimitOrder::new(
price,
QuantityPolicy::Standard {
quantity: remaining_quantity,
},
OrderFlags::new(order.side(), false, TimeInForce::Gtc),
),
);
return outcome;
}
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.quantity(),
available: executed_quantity,
});
outcome
}
fn submit_limit_order(
&mut self,
meta: CommandMeta,
order: &LimitOrder,
) -> Result<OrderOutcome, CommandFailure> {
order.validate().map_err(CommandFailure::InvalidCommand)?;
if order.is_expired(meta.timestamp) {
return Err(CommandFailure::InvalidCommand(CommandError::Expired));
}
Ok(self.submit_validated_limit_order(
meta.sequence_number,
OrderId::from(meta.sequence_number),
order,
))
}
pub(super) fn submit_validated_limit_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &LimitOrder,
) -> OrderOutcome {
if self.has_crossable_order(order.side(), order.price()) {
self.submit_crossable_order(sequence_number, id, order)
} else {
self.submit_non_crossable_order(sequence_number, id, order)
}
}
fn submit_crossable_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &LimitOrder,
) -> OrderOutcome {
let mut outcome = OrderOutcome::new(id);
if order.post_only() {
outcome.set_cancel_reason(CancelReason::PostOnlyWouldTake);
return outcome;
}
if order.time_in_force() == TimeInForce::Fok {
let executable_quantity = self.max_executable_quantity_with_limit_price_unchecked(
order.side(),
order.price(),
order.total_quantity(),
);
if executable_quantity < order.total_quantity() {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.total_quantity(),
available: executable_quantity,
});
return outcome;
}
}
let result = self.match_order(sequence_number, order.side(), None, order.total_quantity());
let executed_quantity = result.executed_quantity();
outcome.set_match_result(result);
let remaining_quantity = order.total_quantity() - executed_quantity;
if remaining_quantity.is_zero() {
return outcome;
}
if order.time_in_force() == TimeInForce::Ioc {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.total_quantity(),
available: executed_quantity,
});
return outcome;
}
let quantity_policy = order
.quantity_policy()
.with_remaining_quantity(remaining_quantity);
self.add_limit_order(
sequence_number,
id,
LimitOrder::new(order.price(), quantity_policy, order.flags().clone()),
);
outcome
}
fn submit_non_crossable_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &LimitOrder,
) -> OrderOutcome {
let mut outcome = OrderOutcome::new(id);
if order.is_immediate() {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.total_quantity(),
available: Quantity(0),
});
return outcome;
}
self.add_limit_order(sequence_number, id, order.clone());
outcome
}
fn submit_pegged_order(
&mut self,
meta: CommandMeta,
order: &PeggedOrder,
) -> Result<OrderOutcome, CommandFailure> {
order.validate().map_err(CommandFailure::InvalidCommand)?;
if order.is_expired(meta.timestamp) {
return Err(CommandFailure::InvalidCommand(CommandError::Expired));
}
Ok(self.submit_validated_pegged_order(
meta.sequence_number,
OrderId::from(meta.sequence_number),
order,
))
}
pub(super) fn submit_validated_pegged_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &PeggedOrder,
) -> OrderOutcome {
match order.peg_reference() {
PegReference::Primary => self.submit_primary_pegged_order(sequence_number, id, order),
PegReference::Market => self.submit_market_pegged_order(sequence_number, id, order),
PegReference::MidPrice => {
self.submit_mid_price_pegged_order(sequence_number, id, order)
}
}
}
fn submit_primary_pegged_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &PeggedOrder,
) -> OrderOutcome {
self.submit_unmarketable_pegged_order(sequence_number, id, order)
}
fn submit_market_pegged_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &PeggedOrder,
) -> OrderOutcome {
let mut outcome = OrderOutcome::new(id);
if self.is_side_empty(order.side().opposite()) {
if order.is_immediate() {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.quantity(),
available: Quantity(0),
});
return outcome;
}
self.add_pegged_order(sequence_number, id, order.clone());
return outcome;
}
if order.time_in_force() == TimeInForce::Fok {
let executable_quantity =
self.max_executable_quantity_unchecked(order.side(), order.quantity());
if executable_quantity < order.quantity() {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.quantity(),
available: executable_quantity,
});
return outcome;
}
}
let result = self.match_order(sequence_number, order.side(), None, order.quantity());
let executed_quantity = result.executed_quantity();
outcome.set_match_result(result);
let remaining_quantity = order.quantity() - executed_quantity;
if remaining_quantity.is_zero() {
return outcome;
}
if order.time_in_force() == TimeInForce::Ioc {
outcome.set_cancel_reason(CancelReason::InsufficientLiquidity {
requested: order.quantity(),
available: executed_quantity,
});
return outcome;
}
self.add_pegged_order(
sequence_number,
id,
PeggedOrder::new(
order.peg_reference(),
remaining_quantity,
order.flags().clone(),
),
);
outcome
}
fn submit_mid_price_pegged_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &PeggedOrder,
) -> OrderOutcome {
self.submit_unmarketable_pegged_order(sequence_number, id, order)
}
fn submit_unmarketable_pegged_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &PeggedOrder,
) -> OrderOutcome {
self.add_pegged_order(sequence_number, id, order.clone());
OrderOutcome::new(id)
}
fn submit_price_conditional_order(
&mut self,
meta: CommandMeta,
order: &PriceConditionalOrder,
) -> Result<OrderOutcome, CommandFailure> {
order.validate().map_err(CommandFailure::InvalidCommand)?;
if order.is_expired(meta.timestamp) {
return Err(CommandFailure::InvalidCommand(CommandError::Expired));
}
Ok(self.submit_validated_price_conditional_order(
meta.sequence_number,
OrderId::from(meta.sequence_number),
order,
))
}
pub(super) fn submit_validated_price_conditional_order(
&mut self,
sequence_number: SequenceNumber,
id: OrderId,
order: &PriceConditionalOrder,
) -> OrderOutcome {
let outcome = OrderOutcome::new(id);
match self.last_trade_price {
None => {
self.price_conditional
.pre_trade_level
.add_order_entry(QueueEntry::new(sequence_number, id));
}
Some(last_trade_price) => {
if order.is_ready(last_trade_price) {
self.price_conditional
.ready_orders
.push_back((id, order.clone()));
return outcome;
}
}
}
self.add_price_conditional_order(sequence_number, id, order.clone());
outcome
}
}
#[cfg(test)]
mod tests_submit_market_order {
use super::*;
fn submit(book: &mut OrderBook, seq: u64, ts: u64, order: MarketOrder) -> CommandOutcome {
book.execute_submit(
CommandMeta {
sequence_number: SequenceNumber(seq),
timestamp: Timestamp(ts),
},
&SubmitCmd {
order: NewOrder::Market(order),
},
)
}
fn unwrap_submit_effects(outcome: CommandOutcome) -> CommandEffects {
match outcome {
CommandOutcome::Applied(CommandReport::Submit(effects)) => effects,
other => panic!("expected applied submit, got: {other:?}"),
}
}
#[test]
fn cancel_order_on_empty_opposite_side() {
let mut book = OrderBook::new("TEST");
let effects = unwrap_submit_effects(submit(
&mut book,
0,
0,
MarketOrder::new(Quantity(10), Side::Buy, false),
));
assert_eq!(
effects.target_order().cancel_reason(),
Some(&CancelReason::InsufficientLiquidity {
requested: Quantity(10),
available: Quantity(0)
})
);
assert!(effects.target_order().match_result().is_none());
}
#[test]
fn market_to_limit_converts_remaining_to_limit_at_last_trade() {
let mut book = OrderBook::new("TEST");
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(5),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let effects = unwrap_submit_effects(submit(
&mut book,
1,
1,
MarketOrder::new(Quantity(10), Side::Buy, true),
));
let submitted = effects.target_order();
assert_eq!(submitted.order_id(), OrderId(1));
assert_eq!(
submitted.match_result().unwrap().executed_quantity(),
Quantity(5)
);
assert!(submitted.cancel_reason().is_none());
assert_eq!(book.last_trade_price(), Some(Price(100)));
assert!(book.limit.orders.contains_key(&submitted.order_id()));
let resting = book.limit.orders.get(&submitted.order_id()).unwrap();
assert_eq!(resting.side(), Side::Buy);
assert_eq!(resting.price(), Price(100));
assert_eq!(resting.total_quantity(), Quantity(5));
}
}
#[cfg(test)]
mod tests_submit_limit_order {
use super::*;
fn submit(book: &mut OrderBook, seq: u64, ts: u64, order: LimitOrder) -> CommandOutcome {
book.execute_submit(
CommandMeta {
sequence_number: SequenceNumber(seq),
timestamp: Timestamp(ts),
},
&SubmitCmd {
order: NewOrder::Limit(order),
},
)
}
fn unwrap_submit_effects(outcome: CommandOutcome) -> CommandEffects {
match outcome {
CommandOutcome::Applied(CommandReport::Submit(effects)) => effects,
other => panic!("expected applied submit, got: {other:?}"),
}
}
#[test]
fn reject_expired_order() {
let mut book = OrderBook::new("TEST");
let outcome = submit(
&mut book,
0,
1000,
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Gtd(Timestamp(1000))),
),
);
match outcome {
CommandOutcome::Rejected(CommandFailure::InvalidCommand(CommandError::Expired)) => {}
other => panic!("expected expired rejection, got: {other:?}"),
}
}
#[test]
fn cancel_immediate_order_on_non_crossable() {
let mut book = OrderBook::new("TEST");
let effects = unwrap_submit_effects(submit(
&mut book,
0,
0,
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Ioc),
),
));
assert_eq!(
effects.target_order().cancel_reason(),
Some(&CancelReason::InsufficientLiquidity {
requested: Quantity(10),
available: Quantity(0)
})
);
assert!(effects.target_order().match_result().is_none());
assert!(book.limit.orders.is_empty());
}
#[test]
fn cancel_post_only_order_on_crossable() {
let mut book = OrderBook::new("TEST");
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(5),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let effects = unwrap_submit_effects(submit(
&mut book,
1,
0,
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, true, TimeInForce::Gtc),
),
));
assert_eq!(
effects.target_order().cancel_reason(),
Some(&CancelReason::PostOnlyWouldTake)
);
assert!(effects.target_order().match_result().is_none());
}
#[test]
fn cancel_fok_order_on_crossable_insufficient_liquidity() {
let mut book = OrderBook::new("TEST");
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(5),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let effects = unwrap_submit_effects(submit(
&mut book,
1,
0,
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Fok),
),
));
assert_eq!(
effects.target_order().cancel_reason(),
Some(&CancelReason::InsufficientLiquidity {
requested: Quantity(10),
available: Quantity(5)
})
);
assert!(effects.target_order().match_result().is_none());
assert_eq!(book.last_trade_price(), None);
}
#[test]
fn cancel_ioc_order_on_crossable_after_partial_match() {
let mut book = OrderBook::new("TEST");
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(5),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let effects = unwrap_submit_effects(submit(
&mut book,
1,
0,
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Ioc),
),
));
let submitted = effects.target_order();
assert_eq!(
submitted.match_result().unwrap().executed_quantity(),
Quantity(5)
);
assert_eq!(
submitted.cancel_reason(),
Some(&CancelReason::InsufficientLiquidity {
requested: Quantity(10),
available: Quantity(5)
})
);
assert!(!book.limit.orders.contains_key(&submitted.order_id()));
}
#[test]
fn rest_remaining_order_on_crossable_after_partial_match() {
let mut book = OrderBook::new("TEST");
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(5),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let effects = unwrap_submit_effects(submit(
&mut book,
1,
0,
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Gtc),
),
));
let submitted = effects.target_order();
assert_eq!(
submitted.match_result().unwrap().executed_quantity(),
Quantity(5)
);
assert!(submitted.cancel_reason().is_none());
assert_eq!(book.last_trade_price(), Some(Price(100)));
let resting = book.limit.orders.get(&submitted.order_id()).unwrap();
assert_eq!(resting.side(), Side::Buy);
assert_eq!(resting.price(), Price(100));
assert_eq!(resting.total_quantity(), Quantity(5));
}
}
#[cfg(test)]
mod tests_submit_pegged_order {
use super::*;
fn submit(book: &mut OrderBook, seq: u64, ts: u64, order: PeggedOrder) -> CommandOutcome {
book.execute_submit(
CommandMeta {
sequence_number: SequenceNumber(seq),
timestamp: Timestamp(ts),
},
&SubmitCmd {
order: NewOrder::Pegged(order),
},
)
}
fn unwrap_submit_effects(outcome: CommandOutcome) -> CommandEffects {
match outcome {
CommandOutcome::Applied(CommandReport::Submit(effects)) => effects,
other => panic!("expected applied submit, got: {other:?}"),
}
}
#[test]
fn reject_expired_order() {
let mut book = OrderBook::new("TEST");
let outcome = submit(
&mut book,
0,
1000,
PeggedOrder::new(
PegReference::Primary,
Quantity(10),
OrderFlags::new(Side::Buy, false, TimeInForce::Gtd(Timestamp(1000))),
),
);
match outcome {
CommandOutcome::Rejected(CommandFailure::InvalidCommand(CommandError::Expired)) => {}
other => panic!("expected expired rejection, got: {other:?}"),
}
}
#[test]
fn add_primary_pegged_order_to_book() {
let mut book = OrderBook::new("TEST");
let effects = unwrap_submit_effects(submit(
&mut book,
0,
0,
PeggedOrder::new(
PegReference::Primary,
Quantity(10),
OrderFlags::new(Side::Buy, false, TimeInForce::Gtc),
),
));
let id = effects.target_order().order_id();
assert!(book.pegged.orders.contains_key(&id));
assert!(effects.target_order().match_result().is_none());
assert!(effects.target_order().cancel_reason().is_none());
}
#[test]
fn cancel_immediate_order_on_empty_opposite_side() {
let mut book = OrderBook::new("TEST");
let effects = unwrap_submit_effects(submit(
&mut book,
0,
0,
PeggedOrder::new(
PegReference::Market,
Quantity(10),
OrderFlags::new(Side::Buy, false, TimeInForce::Ioc),
),
));
assert_eq!(
effects.target_order().cancel_reason(),
Some(&CancelReason::InsufficientLiquidity {
requested: Quantity(10),
available: Quantity(0)
})
);
assert!(book.pegged.orders.is_empty());
}
#[test]
fn rest_remaining_market_pegged_order_after_partial_match() {
let mut book = OrderBook::new("TEST");
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(5),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let effects = unwrap_submit_effects(submit(
&mut book,
1,
0,
PeggedOrder::new(
PegReference::Market,
Quantity(10),
OrderFlags::new(Side::Buy, false, TimeInForce::Gtc),
),
));
let submitted = effects.target_order();
assert_eq!(
submitted.match_result().unwrap().executed_quantity(),
Quantity(5)
);
assert!(submitted.cancel_reason().is_none());
let resting = book.pegged.orders.get(&submitted.order_id()).unwrap();
assert_eq!(resting.peg_reference(), PegReference::Market);
assert_eq!(resting.side(), Side::Buy);
assert_eq!(resting.quantity(), Quantity(5));
}
}
#[cfg(test)]
mod tests_submit_price_conditional_order {
use super::*;
fn submit(
book: &mut OrderBook,
seq: u64,
ts: u64,
order: PriceConditionalOrder,
) -> CommandOutcome {
book.execute_submit(
CommandMeta {
sequence_number: SequenceNumber(seq),
timestamp: Timestamp(ts),
},
&SubmitCmd {
order: NewOrder::PriceConditional(order),
},
)
}
fn submit_market(
book: &mut OrderBook,
seq: u64,
ts: u64,
order: MarketOrder,
) -> CommandOutcome {
book.execute_submit(
CommandMeta {
sequence_number: SequenceNumber(seq),
timestamp: Timestamp(ts),
},
&SubmitCmd {
order: NewOrder::Market(order),
},
)
}
fn unwrap_submit_effects(outcome: CommandOutcome) -> CommandEffects {
match outcome {
CommandOutcome::Applied(CommandReport::Submit(effects)) => effects,
other => panic!("expected applied submit, got: {other:?}"),
}
}
fn seed_last_trade_price(book: &mut OrderBook, trade_price: Price) {
book.add_limit_order(
SequenceNumber(0),
OrderId(0),
LimitOrder::new(
trade_price,
QuantityPolicy::Standard {
quantity: Quantity(1),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
),
);
let _ = submit_market(book, 1, 0, MarketOrder::new(Quantity(1), Side::Buy, false));
assert_eq!(book.last_trade_price(), Some(trade_price));
}
#[test]
fn reject_expired_order() {
let mut book = OrderBook::new("TEST");
let outcome = submit(
&mut book,
0,
1000,
PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
TriggerOrder::Limit(LimitOrder::new(
Price(99),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Gtd(Timestamp(1000))),
)),
),
);
match outcome {
CommandOutcome::Rejected(CommandFailure::InvalidCommand(CommandError::Expired)) => {}
other => panic!("expected expired rejection, got: {other:?}"),
}
}
#[test]
fn activate_immediately_at_or_above_submits_target_limit_order() {
let mut book = OrderBook::new("TEST");
seed_last_trade_price(&mut book, Price(100));
let effects = unwrap_submit_effects(submit(
&mut book,
2,
0,
PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
TriggerOrder::Limit(LimitOrder::new(
Price(99),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Gtc),
)),
),
));
let target = effects.target_order();
assert_eq!(target.order_id(), OrderId(2));
assert!(target.match_result().is_none());
assert!(target.cancel_reason().is_none());
let triggered = effects.triggered_orders();
assert_eq!(triggered.len(), 1);
assert_eq!(triggered[0].order_id(), OrderId(2));
assert!(triggered[0].match_result().is_none());
assert!(triggered[0].cancel_reason().is_none());
assert!(book.limit.orders.contains_key(&OrderId(2)));
assert!(!book.price_conditional.orders.contains_key(&OrderId(2)));
}
#[test]
fn activate_immediately_at_or_below_submits_target_limit_order() {
let mut book = OrderBook::new("TEST");
seed_last_trade_price(&mut book, Price(100));
let effects = unwrap_submit_effects(submit(
&mut book,
2,
0,
PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrBelow),
TriggerOrder::Limit(LimitOrder::new(
Price(101),
QuantityPolicy::Standard {
quantity: Quantity(10),
},
OrderFlags::new(Side::Sell, false, TimeInForce::Gtc),
)),
),
));
let target = effects.target_order();
assert_eq!(target.order_id(), OrderId(2));
assert!(target.match_result().is_none());
assert!(target.cancel_reason().is_none());
let triggered = effects.triggered_orders();
assert_eq!(triggered.len(), 1);
assert_eq!(triggered[0].order_id(), OrderId(2));
assert!(triggered[0].match_result().is_none());
assert!(triggered[0].cancel_reason().is_none());
assert!(book.limit.orders.contains_key(&OrderId(2)));
assert!(!book.price_conditional.orders.contains_key(&OrderId(2)));
}
#[test]
fn rest_in_price_conditional_book_when_not_triggered() {
let mut book = OrderBook::new("TEST");
seed_last_trade_price(&mut book, Price(100));
let effects = unwrap_submit_effects(submit(
&mut book,
2,
0,
PriceConditionalOrder::new(
PriceCondition::new(Price(101), TriggerDirection::AtOrAbove),
TriggerOrder::Market(MarketOrder::new(Quantity(10), Side::Buy, false)),
),
));
let target = effects.target_order();
assert_eq!(target.order_id(), OrderId(2));
assert!(target.match_result().is_none());
assert!(target.cancel_reason().is_none());
let triggered = effects.triggered_orders();
assert!(triggered.is_empty());
assert!(book.price_conditional.orders.contains_key(&OrderId(2)));
assert!(!book.limit.orders.contains_key(&OrderId(2)));
}
}