Module 0x7::single_order_book
This module provides a core order book functionality for a trading system. On a high level, it has three major components
- ActiveOrderBook: This is the main order book that keeps track of active orders and their states. The active order book is backed by a BigOrderedMap, which is a data structure that allows for efficient insertion, deletion, and matching of the order The orders are matched based on price-time priority.
- PendingOrderBookIndex: This keeps track of pending orders. The pending orders are those that are not active yet. Three types of pending orders are supported.
- Price move up - Triggered when the price moves above a certain price level
- Price move down - Triggered when the price moves below a certain price level
- Time based - Triggered when a certain time has passed
- Orders: This is a BigOrderedMap of order id to order details.
- Enum
SingleOrderBook - Constants
- Function
new_single_order_book - Function
cancel_order - Function
try_cancel_order_with_client_order_id - Function
try_cancel_order - Function
client_order_id_exists - Function
place_maker_or_pending_order - Function
place_ready_maker_order_with_unique_idx - Function
reinsert_order - Function
place_pending_order_internal - Function
get_single_match_for_taker - Function
decrease_order_size - Function
get_order_id_by_client_id - Function
get_order_metadata - Function
set_order_metadata - Function
is_active_order - Function
get_order - Function
get_remaining_size - Function
take_ready_price_based_orders - Function
take_ready_time_based_orders - Function
__lambda__1__get_single_match_for_taker - Function
__lambda__1__decrease_order_size - Function
__lambda__1__reinsert_order - Function
__lambda__1__set_order_metadata
use 0x1::big_ordered_map;
use 0x1::error;
use 0x1::option;
use 0x1::string;
use 0x1::vector;
use 0x5::order_book_types;
use 0x5::order_match_types;
use 0x5::single_order_types;
use 0x7::order_book_utils;
use 0x7::pending_order_book_index;
use 0x7::price_time_index;
Enum SingleOrderBook
enum SingleOrderBook<M: copy, drop, store> has store
Variants
V1
Fields
Constants
const E_REINSERT_ORDER_MISMATCH: u64 = 8;
const EINVALID_ADD_SIZE_TO_ORDER: u64 = 6;
const EINVALID_INACTIVE_ORDER_STATE: u64 = 5;
const EORDER_ALREADY_EXISTS: u64 = 1;
const EORDER_CREATOR_MISMATCH: u64 = 9;
const EORDER_NOT_FOUND: u64 = 4;
const EPOST_ONLY_FILLED: u64 = 2;
const E_NOT_ACTIVE_ORDER: u64 = 7;
const ENOT_SINGLE_ORDER_BOOK: u64 = 10;
const ETRIGGER_COND_NOT_FOUND: u64 = 11;
Function new_single_order_book
public(friend) fun new_single_order_book<M: copy, drop, store>(): single_order_book::SingleOrderBook<M>
Implementation
public(friend) fun new_single_order_book<M: store + copy + drop>(): SingleOrderBook<M> {
SingleOrderBook::V1 {
orders: order_book_utils::new_default_big_ordered_map(),
client_order_ids: order_book_utils::new_default_big_ordered_map(),
pending_orders: new_pending_order_book_index()
}
}
Function cancel_order
Cancels an order from the order book. If the order is active, it is removed from the active order book else it is removed from the pending order book. If order doesn’t exist, it aborts with EORDER_NOT_FOUND.
order_creator is passed to only verify order cancellation is authorized correctly
public(friend) fun cancel_order<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, order_creator: address, order_id: order_book_types::OrderId): single_order_types::SingleOrder<M>
Implementation
public(friend) fun cancel_order<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
order_creator: address,
order_id: OrderId
): SingleOrder<M> {
let order_with_state_option = self.orders.remove_or_none(&order_id);
assert!(order_with_state_option.is_some(), EORDER_NOT_FOUND);
let order_with_state = order_with_state_option.destroy_some();
let (order, is_active) = order_with_state.destroy_order_from_state();
let order_request = order.get_order_request();
assert!(order_creator == order_request.get_account(), EORDER_CREATOR_MISMATCH);
if (is_active) {
price_time_idx.cancel_active_order(
order_request.get_price(),
order.get_unique_priority_idx(),
order_request.is_bid()
);
if (order_request.get_client_order_id().is_some()) {
self.client_order_ids.remove(
&new_account_client_order_id(
order_request.get_account(),
order_request.get_client_order_id().destroy_some()
)
);
};
} else {
self.pending_orders.cancel_pending_order(
order_request.get_trigger_condition().destroy_some(),
order.get_unique_priority_idx()
);
if (order_request.get_client_order_id().is_some()) {
self.client_order_ids.remove(
&new_account_client_order_id(
order_request.get_account(),
order_request.get_client_order_id().destroy_some()
)
);
};
};
return order
}
Function try_cancel_order_with_client_order_id
public(friend) fun try_cancel_order_with_client_order_id<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, order_creator: address, client_order_id: string::String): option::Option<single_order_types::SingleOrder<M>>
Implementation
public(friend) fun try_cancel_order_with_client_order_id<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
order_creator: address,
client_order_id: String
): Option<SingleOrder<M>> {
let account_client_order_id =
new_account_client_order_id(order_creator, client_order_id);
let order_id = self.client_order_ids.get(&account_client_order_id);
if (order_id.is_none()) {
return option::none();
};
option::some(
self.cancel_order(price_time_idx, order_creator, order_id.destroy_some())
)
}
Function try_cancel_order
public(friend) fun try_cancel_order<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, order_creator: address, order_id: order_book_types::OrderId): option::Option<single_order_types::SingleOrder<M>>
Implementation
public(friend) fun try_cancel_order<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
order_creator: address,
order_id: OrderId
): Option<SingleOrder<M>> {
let is_creator =
self.orders.get_and_map(
&order_id,
|order| order.get_order_from_state().get_order_request().get_account()
== order_creator
);
if (is_creator.is_none() || !is_creator.destroy_some()) {
return option::none();
};
option::some(self.cancel_order(price_time_idx, order_creator, order_id))
}
Function client_order_id_exists
public(friend) fun client_order_id_exists<M: copy, drop, store>(self: &single_order_book::SingleOrderBook<M>, order_creator: address, client_order_id: string::String): bool
Implementation
public(friend) fun client_order_id_exists<M: store + copy + drop>(
self: &SingleOrderBook<M>, order_creator: address, client_order_id: String
): bool {
let account_client_order_id =
new_account_client_order_id(order_creator, client_order_id);
self.client_order_ids.contains(&account_client_order_id)
}
Function place_maker_or_pending_order
Places a maker order to the order book. If the order is a pending order, it is added to the pending order book else it is added to the active order book. The API aborts if it’s not a maker order or if the order already exists
public(friend) fun place_maker_or_pending_order<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, order_req: single_order_types::SingleOrderRequest<M>)
Implementation
public(friend) fun place_maker_or_pending_order<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
order_req: SingleOrderRequest<M>
) {
let ascending_idx = next_increasing_idx_type();
if (order_req.get_trigger_condition().is_some()) {
return self.place_pending_order_internal(order_req, ascending_idx);
};
self.place_ready_maker_order_with_unique_idx(
price_time_idx, order_req, ascending_idx
);
}
Function place_ready_maker_order_with_unique_idx
fun place_ready_maker_order_with_unique_idx<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, order_req: single_order_types::SingleOrderRequest<M>, ascending_idx: order_book_types::IncreasingIdx)
Implementation
fun place_ready_maker_order_with_unique_idx<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
order_req: SingleOrderRequest<M>,
ascending_idx: IncreasingIdx
) {
let order = new_single_order(order_req, ascending_idx);
assert!(
self.orders.upsert(
order_req.get_order_id(), new_order_with_state(order, true)
).is_none(),
error::invalid_argument(EORDER_ALREADY_EXISTS)
);
if (order_req.get_client_order_id().is_some()) {
self.client_order_ids.add(
new_account_client_order_id(
order_req.get_account(),
order_req.get_client_order_id().destroy_some()
),
order_req.get_order_id()
);
};
price_time_idx.place_maker_order(
order_req.get_order_id(),
single_order_type(),
order_req.get_price(),
ascending_idx,
order_req.get_remaining_size(),
order_req.is_bid()
);
}
Function reinsert_order
Reinserts a maker order to the order book. This is used when the order is removed from the order book but the clearinghouse fails to settle all or part of the order. If the order doesn’t exist in the order book, it is added to the order book, if it exists, its size is updated.
public(friend) fun reinsert_order<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, reinsert_order: order_match_types::OrderMatchDetails<M>, original_order: &order_match_types::OrderMatchDetails<M>)
Implementation
public(friend) fun reinsert_order<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
reinsert_order: OrderMatchDetails<M>,
original_order: &OrderMatchDetails<M>
) {
assert!(
reinsert_order.validate_single_order_reinsertion_request(original_order),
E_REINSERT_ORDER_MISMATCH
);
let order_id = reinsert_order.get_order_id_from_match_details();
let unique_idx = reinsert_order.get_unique_priority_idx_from_match_details();
let reinsert_remaining_size =
reinsert_order.get_remaining_size_from_match_details();
let present =
self.orders.modify_if_present(
&order_id,
|order_with_state| {
order_with_state.increase_remaining_size_from_state(
reinsert_remaining_size
);
}
);
if (!present) {
return self.place_ready_maker_order_with_unique_idx(
price_time_idx,
new_order_request_from_match_details(reinsert_order),
unique_idx
);
};
price_time_idx.increase_order_size(
reinsert_order.get_price_from_match_details(),
unique_idx,
reinsert_order.get_remaining_size_from_match_details(),
reinsert_order.is_bid_from_match_details()
);
}
Function place_pending_order_internal
fun place_pending_order_internal<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, order_req: single_order_types::SingleOrderRequest<M>, ascending_idx: order_book_types::IncreasingIdx)
Implementation
fun place_pending_order_internal<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
order_req: SingleOrderRequest<M>,
ascending_idx: IncreasingIdx
) {
let order_id = order_req.get_order_id();
let order = new_single_order(order_req, ascending_idx);
self.orders.add(order_id, new_order_with_state(order, false));
if (order_req.get_client_order_id().is_some()) {
self.client_order_ids.add(
new_account_client_order_id(
order_req.get_account(),
order_req.get_client_order_id().destroy_some()
),
order_req.get_order_id()
);
};
self.pending_orders.place_pending_order(
order_id,
order_req.get_trigger_condition().destroy_some(),
ascending_idx
);
}
Function get_single_match_for_taker
Returns a single match for a taker order. It is responsibility of the caller to first call the is_taker_order
API to ensure that the order is a taker order before calling this API, otherwise it will abort.
public(friend) fun get_single_match_for_taker<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, active_matched_order: order_match_types::ActiveMatchedOrder): order_match_types::OrderMatch<M>
Implementation
public(friend) fun get_single_match_for_taker<M: store + copy + drop>(
self: &mut SingleOrderBook<M>, active_matched_order: ActiveMatchedOrder
): OrderMatch<M> {
let (order_id, matched_size, remaining_size, order_book_type) =
active_matched_order.destroy_active_matched_order();
assert!(order_book_type == single_order_type(), ENOT_SINGLE_ORDER_BOOK);
let order_with_state =
if (remaining_size == 0) {
let order = self.orders.remove(&order_id);
order.set_remaining_size_from_state(0);
order
} else {
self.orders.modify_and_return(
&order_id,
|order_with_state| {
aptos_trading::single_order_types::set_remaining_size_from_state(
order_with_state, remaining_size
);
// order_with_state.set_remaining_size_from_state(remaining_size);
*order_with_state
}
)
};
let (order, is_active) = order_with_state.destroy_order_from_state();
assert!(is_active, EINVALID_INACTIVE_ORDER_STATE);
let (order_request, unique_priority_idx) = order.destroy_single_order();
let (
account,
order_id,
client_order_id,
price,
orig_size,
size,
is_bid,
_trigger_condition,
time_in_force,
creation_time_micros,
metadata
) = order_request.destroy_single_order_request();
if (remaining_size == 0 && client_order_id.is_some()) {
self.client_order_ids.remove(
&new_account_client_order_id(
order.get_order_request().get_account(),
client_order_id.destroy_some()
)
);
};
new_order_match(
new_single_order_match_details(
order_id,
account,
client_order_id,
unique_priority_idx,
price,
orig_size,
size,
is_bid,
time_in_force,
creation_time_micros,
metadata
),
matched_size
)
}
Function decrease_order_size
Decrease the size of the order by the given size delta. The API aborts if the order is not found in the order book or
if the size delta is greater than or equal to the remaining size of the order. Please note that the API will abort and
not cancel the order if the size delta is equal to the remaining size of the order, to avoid unintended
cancellation of the order. Please use the cancel_order API to cancel the order.
order_creator is passed to only verify order cancellation is authorized correctly
public(friend) fun decrease_order_size<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, price_time_idx: &mut price_time_index::PriceTimeIndex, order_creator: address, order_id: order_book_types::OrderId, size_delta: u64)
Implementation
public(friend) fun decrease_order_size<M: store + copy + drop>(
self: &mut SingleOrderBook<M>,
price_time_idx: &mut PriceTimeIndex,
order_creator: address,
order_id: OrderId,
size_delta: u64
) {
let order_opt =
self.orders.modify_if_present_and_return(
&order_id,
|order_with_state| {
assert!(
order_creator
== order_with_state.get_order_from_state().get_order_request()
.get_account(),
EORDER_CREATOR_MISMATCH
);
// TODO should we be asserting that remaining size is greater than 0?
aptos_trading::single_order_types::decrease_remaining_size_from_state(
order_with_state, size_delta
);
// order_with_state.decrease_remaining_size(size_delta);
*order_with_state
}
);
assert!(order_opt.is_some(), EORDER_NOT_FOUND);
let order_with_state = order_opt.destroy_some();
if (order_with_state.is_active_order()) {
let order = order_with_state.get_order_from_state();
price_time_idx.decrease_order_size(
order.get_order_request().get_price(),
order_with_state.get_unique_priority_idx_from_state(),
size_delta,
order.get_order_request().is_bid()
);
};
}
Function get_order_id_by_client_id
public(friend) fun get_order_id_by_client_id<M: copy, drop, store>(self: &single_order_book::SingleOrderBook<M>, order_creator: address, client_order_id: string::String): option::Option<order_book_types::OrderId>
Implementation
public(friend) fun get_order_id_by_client_id<M: store + copy + drop>(
self: &SingleOrderBook<M>, order_creator: address, client_order_id: String
): Option<OrderId> {
let account_client_order_id =
new_account_client_order_id(order_creator, client_order_id);
self.client_order_ids.get(&account_client_order_id)
}
Function get_order_metadata
public(friend) fun get_order_metadata<M: copy, drop, store>(self: &single_order_book::SingleOrderBook<M>, order_id: order_book_types::OrderId): option::Option<M>
Implementation
public(friend) fun get_order_metadata<M: store + copy + drop>(
self: &SingleOrderBook<M>, order_id: OrderId
): Option<M> {
self.orders.get_and_map(&order_id, |order| order.get_metadata_from_state())
}
Function set_order_metadata
public(friend) fun set_order_metadata<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, order_id: order_book_types::OrderId, metadata: M)
Implementation
public(friend) fun set_order_metadata<M: store + copy + drop>(
self: &mut SingleOrderBook<M>, order_id: OrderId, metadata: M
) {
let present =
self.orders.modify_if_present(
&order_id,
|order_with_state| {
order_with_state.set_metadata_in_state(metadata);
}
);
assert!(present, EORDER_NOT_FOUND);
}
Function is_active_order
public(friend) fun is_active_order<M: copy, drop, store>(self: &single_order_book::SingleOrderBook<M>, order_id: order_book_types::OrderId): bool
Implementation
public(friend) fun is_active_order<M: store + copy + drop>(
self: &SingleOrderBook<M>, order_id: OrderId
): bool {
self.orders.get_and_map(&order_id, |order| order.is_active_order()).destroy_with_default(
false
)
}
Function get_order
public(friend) fun get_order<M: copy, drop, store>(self: &single_order_book::SingleOrderBook<M>, order_id: order_book_types::OrderId): option::Option<single_order_types::OrderWithState<M>>
Implementation
public(friend) fun get_order<M: store + copy + drop>(
self: &SingleOrderBook<M>, order_id: OrderId
): Option<OrderWithState<M>> {
self.orders.get(&order_id)
}
Function get_remaining_size
public(friend) fun get_remaining_size<M: copy, drop, store>(self: &single_order_book::SingleOrderBook<M>, order_id: order_book_types::OrderId): u64
Implementation
public(friend) fun get_remaining_size<M: store + copy + drop>(
self: &SingleOrderBook<M>, order_id: OrderId
): u64 {
self.orders.get_and_map(
&order_id, |order| order.get_remaining_size_from_state()
).destroy_with_default(0)
}
Function take_ready_price_based_orders
Removes and returns the orders that are ready to be executed based on the current price.
public(friend) fun take_ready_price_based_orders<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, current_price: u64, order_limit: u64): vector<single_order_types::SingleOrder<M>>
Implementation
public(friend) fun take_ready_price_based_orders<M: store + copy + drop>(
self: &mut SingleOrderBook<M>, current_price: u64, order_limit: u64
): vector<SingleOrder<M>> {
let self_orders = &mut self.orders;
let self_client_order_ids = &mut self.client_order_ids;
let order_ids =
self.pending_orders.take_ready_price_based_orders(
current_price, order_limit
);
let orders = vector::empty();
order_ids.for_each(
|order_id| {
let order_with_state = self_orders.remove(&order_id);
let (order, _) = order_with_state.destroy_order_from_state();
let client_order_id = order.get_order_request().get_client_order_id();
if (client_order_id.is_some()) {
self_client_order_ids.remove(
&new_account_client_order_id(
order.get_order_request().get_account(),
client_order_id.destroy_some()
)
);
};
orders.push_back(order);
}
);
orders
}
Function take_ready_time_based_orders
Removes and returns the orders that are ready to be executed based on the time condition.
public(friend) fun take_ready_time_based_orders<M: copy, drop, store>(self: &mut single_order_book::SingleOrderBook<M>, order_limit: u64): vector<single_order_types::SingleOrder<M>>
Implementation
public(friend) fun take_ready_time_based_orders<M: store + copy + drop>(
self: &mut SingleOrderBook<M>, order_limit: u64
): vector<SingleOrder<M>> {
let self_orders = &mut self.orders;
let self_client_order_ids = &mut self.client_order_ids;
let order_ids = self.pending_orders.take_ready_time_based_orders(order_limit);
let orders = vector::empty();
order_ids.for_each(
|order_id| {
let order_with_state = self_orders.remove(&order_id);
let (order, _) = order_with_state.destroy_order_from_state();
let client_order_id = order.get_order_request().get_client_order_id();
if (client_order_id.is_some()) {
self_client_order_ids.remove(
&new_account_client_order_id(
order.get_order_request().get_account(),
client_order_id.destroy_some()
)
);
};
orders.push_back(order);
}
);
orders
}
Function __lambda__1__get_single_match_for_taker
fun __lambda__1__get_single_match_for_taker<M: copy, drop, store>(remaining_size: u64, v: &mut single_order_types::OrderWithState<M>): single_order_types::OrderWithState<M>
Implementation
|v| f(v)
Function __lambda__1__decrease_order_size
fun __lambda__1__decrease_order_size<M: copy, drop, store>(order_creator: address, size_delta: u64, v: &mut single_order_types::OrderWithState<M>): single_order_types::OrderWithState<M>
Implementation
|v| modify_f(v)
Function __lambda__1__reinsert_order
fun __lambda__1__reinsert_order<M: copy, drop, store>(reinsert_remaining_size: u64, v: &mut single_order_types::OrderWithState<M>): bool
Implementation
|v| modify_f(v)
Function __lambda__1__set_order_metadata
fun __lambda__1__set_order_metadata<M: copy, drop, store>(metadata: M, v: &mut single_order_types::OrderWithState<M>): bool
Implementation
|v| modify_f(v)