Skip to content

Commit

Permalink
fixup! fill market order in OrderMatchingEngine
Browse files Browse the repository at this point in the history
  • Loading branch information
filipmacek committed Jan 12, 2025
1 parent 2305309 commit 5b64373
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 26 deletions.
57 changes: 50 additions & 7 deletions nautilus_core/backtest/src/matching_engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@ impl OrderMatchingEngine {
"Canceling REDUCE_ONLY {} as would increase position",
order.order_type()
);
self.cancel_order(order);
self.cancel_order(order, None);
return;
}
// set order side as taker
Expand All @@ -904,7 +904,12 @@ impl OrderMatchingEngine {
position: Option<Position>,
) {
if order.time_in_force() == TimeInForce::Fok {
todo!("Fill order with FOK")
let mut total_size = Quantity::zero(order.quantity().precision);
for (fill_px, fill_qty) in &fills {
total_size = total_size.add(*fill_qty);
}

if order.leaves_qty() > total_size {}
}

if fills.is_empty() {
Expand Down Expand Up @@ -984,7 +989,7 @@ impl OrderMatchingEngine {
}
}

if *fill_qty == Quantity::zero(fill_qty.precision) {
if fill_qty.is_zero() {
if fills.len() == 1 && order.status() == OrderStatus::Submitted {
self.generate_order_rejected(
order,
Expand All @@ -1011,7 +1016,7 @@ impl OrderMatchingEngine {

if order.time_in_force() == TimeInForce::Ioc && order.is_open() {
// IOC order has filled all available size
self.cancel_order(order);
self.cancel_order(order, None);
return;
}

Expand Down Expand Up @@ -1102,8 +1107,32 @@ impl OrderMatchingEngine {
todo!("expire_order")
}

fn cancel_order(&mut self, order: &OrderAny) {
todo!("cancel_order")
fn cancel_order(&mut self, order: &OrderAny, cancel_contingencies: Option<bool>) {
let cancel_contingencies = cancel_contingencies.unwrap_or(true);
if order.is_active_local() {
log::error!(
"Cannot cancel an order with {} from the matching engine",
order.status()
);
return;
}

// delete order from OrderMatchingCore
let _ = self
.core
.delete_order(&PassiveOrderAny::from(order.clone()));
self.cached_filled_qty.remove(&order.client_order_id());

let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
self.generate_order_canceled(order, venue_order_id);

if self.config.support_contingent_orders
&& order.contingency_type().is_some()
&& order.contingency_type().unwrap() != ContingencyType::NoContingency
&& cancel_contingencies
{
self.cancel_contingent_orders(order);
}
}

fn update_order(&mut self, order: &OrderAny) {
Expand All @@ -1119,7 +1148,21 @@ impl OrderMatchingEngine {
}

fn cancel_contingent_orders(&mut self, order: &OrderAny) {
todo!("cancel_contingent_orders")
if let Some(linked_order_ids) = order.linked_order_ids() {
for client_order_id in &linked_order_ids {
let contingent_order = match self.cache.borrow().order(client_order_id) {
Some(order) => order.clone(),
None => panic!("Cannot find contingent order for {client_order_id}"),
};
if contingent_order.is_active_local() {
// order is not on the exchange yet
continue;
}
if !contingent_order.is_closed() {
self.cancel_order(&contingent_order, Some(false));
}
}
}
}

// -- EVENT GENERATORS -----------------------------------------------------
Expand Down
62 changes: 43 additions & 19 deletions nautilus_core/backtest/src/matching_engine/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,6 @@ fn test_matching_engine_valid_market_buy(
order_event_handler: ShareableMessageHandler,
mut msgbus: MessageBus,
account_id: AccountId,
mut market_order_buy: OrderAny,
time: AtomicTime,
) {
// Register saving message handler to exec engine endpoint
Expand All @@ -774,38 +773,63 @@ fn test_matching_engine_valid_market_buy(
None,
);

let orderbook_delta_sell = OrderBookDelta::new(
// create 2 orderbook deltas and appropriate market order
let orderbook_delta_sell_1 = OrderBookDelta::new(
instrument_eth_usdt.id(),
BookAction::Add,
BookOrder::new(
OrderSide::Sell,
Price::from("1500.00"),
Quantity::from("1.00"),
Quantity::from("1.000"),
1,
),
0,
1,
UnixNanos::from(1),
UnixNanos::from(1),
);
let orderbook_delta_sell_2 = OrderBookDelta::new(
instrument_eth_usdt.id(),
BookAction::Add,
BookOrder::new(
OrderSide::Sell,
Price::from("1510.00"),
Quantity::from("1.000"),
1,
),
0,
2,
UnixNanos::from(1),
UnixNanos::from(1),
);
let mut market_order = OrderTestBuilder::new(OrderType::Market)
.instrument_id(instrument_eth_usdt.id())
.side(OrderSide::Buy)
.quantity(Quantity::from("2.000"))
.client_order_id(ClientOrderId::from("O-19700101-000000-001-001-1"))
.build();

// process orderbook delta to add liquidity then process market order
engine_l2.process_order_book_delta(&orderbook_delta_sell);
engine_l2.process_order(&mut market_order_buy, account_id);
// process orderbook deltas to add liquidity then process market order
engine_l2.process_order_book_delta(&orderbook_delta_sell_1);
engine_l2.process_order_book_delta(&orderbook_delta_sell_2);
engine_l2.process_order(&mut market_order, account_id);

// We need to test few things as we pushed sell orderbook delta and executed market buy order
// 1. that Order filled event was generated where with correct price and quantity
// 2. we cleared the orderbook and there are no orders left
// We need to test that two Order filled events were generated where with correct prices and quantities
let saved_messages = get_order_event_handler_messages(order_event_handler);
assert_eq!(saved_messages.len(), 1);
let order_event_any_message = saved_messages.first().unwrap();
let order_event = match order_event_any_message {
OrderEventAny::Filled(event) => Some(event),
_ => None,
assert_eq!(saved_messages.len(), 2);
let order_event_first = saved_messages.first().unwrap();
let order_filled_first = match order_event_first {
OrderEventAny::Filled(order_filled) => order_filled,
_ => panic!("Expected OrderFilled event in first message"),
};
let order_event_second = saved_messages.get(1).unwrap();
let order_filled_second = match order_event_second {
OrderEventAny::Filled(order_filled) => order_filled,
_ => panic!("Expected OrderFilled event in second message"),
};
assert!(order_event.is_some());
let order_filled = order_event.unwrap();
assert_eq!(order_filled.last_px, Price::from("1500.00"));
assert_eq!(order_filled.last_qty, Quantity::from("1.00"));
assert_eq!(order_filled.liquidity_side, LiquiditySide::Taker);
// check correct prices and quantities
assert_eq!(order_filled_first.last_px, Price::from("1500.00"));
assert_eq!(order_filled_first.last_qty, Quantity::from("1.000"));
assert_eq!(order_filled_second.last_px, Price::from("1510.00"));
assert_eq!(order_filled_second.last_qty, Quantity::from("1.000"));
}

0 comments on commit 5b64373

Please sign in to comment.