0x0::book
The book module contains the Book
struct which represents the order book.
All order book operations are defined in this module.
Book
bids
asks
tick_size
lot_size
min_size
empty
create_order
get_quantity_out
cancel_order
modify_order
mid_price
get_level2_range_and_ticks
get_order
book_side_mut
book_side
match_against_book
get_order_id
inject_limit_order
use 0x0::big_vector;
use 0x0::constants;
use 0x0::deep_price;
use 0x0::fill;
use 0x0::math;
use 0x0::order;
use 0x0::order_info;
use 0x0::utils;
use 0x1::u64;
use 0x2::tx_context;
Book
struct Book has store
tick_size: u64
lot_size: u64
min_size: u64
bids: big_vector::BigVector<order::Order>
asks: big_vector::BigVector<order::Order>
next_bid_order_id: u64
next_ask_order_id: u64
const EOrderBelowMinimumSize: u64 = 5;
const EOrderInvalidLotSize: u64 = 6;
const EEmptyOrderbook: u64 = 2;
const EInvalidAmountIn: u64 = 1;
const EInvalidPriceRange: u64 = 3;
const EInvalidTicks: u64 = 4;
const ENewQuantityMustBeLessThanOriginal: u64 = 7;
const START_ASK_ORDER_ID: u64 = 1;
const START_BID_ORDER_ID: u64 = 18446744073709551615;
bids
public(friend) fun bids(self: &book::Book): &big_vector::BigVector<order::Order>
asks
public(friend) fun asks(self: &book::Book): &big_vector::BigVector<order::Order>
tick_size
public(friend) fun tick_size(self: &book::Book): u64
lot_size
public(friend) fun lot_size(self: &book::Book): u64
min_size
public(friend) fun min_size(self: &book::Book): u64
empty
public(friend) fun empty(tick_size: u64, lot_size: u64, min_size: u64, ctx: &mut tx_context::TxContext): book::Book
public(package) fun empty(
tick_size: u64,
lot_size: u64,
min_size: u64,
ctx: &mut TxContext,
): Book {
Book {
tick_size,
lot_size,
min_size,
bids: big_vector::empty(
constants::max_slice_size(),
constants::max_fan_out(),
ctx,
),
asks: big_vector::empty(
constants::max_slice_size(),
constants::max_fan_out(),
ctx,
),
next_bid_order_id: START_BID_ORDER_ID,
next_ask_order_id: START_ASK_ORDER_ID,
}
}
create_order
Creates a new order. Order is matched against the book and injected into the book if necessary. If order is IOC or fully executed, it will not be injected.
public(friend) fun create_order(self: &mut book::Book, order_info: &mut order_info::OrderInfo, timestamp: u64)
public(package) fun create_order(
self: &mut Book,
order_info: &mut OrderInfo,
timestamp: u64,
) {
order_info.validate_inputs(
self.tick_size,
self.min_size,
self.lot_size,
timestamp,
);
let order_id = utils::encode_order_id(
order_info.is_bid(),
order_info.price(),
self.get_order_id(order_info.is_bid()),
);
order_info.set_order_id(order_id);
self.match_against_book(order_info, timestamp);
if (order_info.assert_execution()) return;
self.inject_limit_order(order_info);
order_info.set_order_inserted();
order_info.emit_order_placed();
}
get_quantity_out
Given base_quantity and quote_quantity, calculate the base_quantity_out and quote_quantity_out. Will return (base_quantity_out, quote_quantity_out, deep_quantity_required) if base_amount > 0 or quote_amount > 0.
public(friend) fun get_quantity_out(self: &book::Book, base_quantity: u64, quote_quantity: u64, taker_fee: u64, deep_price: deep_price::OrderDeepPrice, lot_size: u64, current_timestamp: u64): (u64, u64, u64)
public(package) fun get_quantity_out(
self: &Book,
base_quantity: u64,
quote_quantity: u64,
taker_fee: u64,
deep_price: OrderDeepPrice,
lot_size: u64,
current_timestamp: u64,
): (u64, u64, u64) {
assert!(
(base_quantity > 0 || quote_quantity > 0) &&
!(base_quantity > 0 && quote_quantity > 0),
EInvalidAmountIn,
);
let is_bid = quote_quantity > 0;
let mut quantity_out = 0;
let mut quantity_in_left = if (is_bid) quote_quantity else base_quantity;
let book_side = if (is_bid) &self.asks else &self.bids;
let (mut ref, mut offset) = if (is_bid) book_side.min_slice()
else book_side.max_slice();
while (!ref.is_null() && quantity_in_left > 0) {
let order = slice_borrow(book_side.borrow_slice(ref), offset);
let cur_price = order.price();
let cur_quantity = order.quantity() - order.filled_quantity();
if (current_timestamp <= order.expire_timestamp()) {
let mut matched_base_quantity;
if (is_bid) {
matched_base_quantity =
math::div(quantity_in_left, cur_price).min(cur_quantity);
matched_base_quantity =
matched_base_quantity -
matched_base_quantity % lot_size;
quantity_out = quantity_out + matched_base_quantity;
quantity_in_left =
quantity_in_left -
math::mul(matched_base_quantity, cur_price);
} else {
matched_base_quantity = quantity_in_left.min(cur_quantity);
matched_base_quantity =
matched_base_quantity -
matched_base_quantity % lot_size;
quantity_out =
quantity_out + math::mul(matched_base_quantity, cur_price);
quantity_in_left = quantity_in_left - matched_base_quantity;
};
if (matched_base_quantity == 0) break;
};
(ref, offset) =
if (is_bid) book_side.next_slice(ref, offset)
else book_side.prev_slice(ref, offset);
};
let quantity_in_deep = if (is_bid) {
deep_price.deep_quantity(
quantity_out,
quote_quantity - quantity_in_left,
)
} else {
deep_price.deep_quantity(base_quantity - quantity_in_left, quantity_out)
};
let deep_fee = math::mul(taker_fee, quantity_in_deep);
if (is_bid) {
(quantity_out, quantity_in_left, deep_fee)
} else {
(quantity_in_left, quantity_out, deep_fee)
}
}
cancel_order
Cancels an order given order_id
public(friend) fun cancel_order(self: &mut book::Book, order_id: u128): order::Order
public(package) fun cancel_order(self: &mut Book, order_id: u128): Order {
self.book_side_mut(order_id).remove(order_id)
}
modify_order
Modifies an order given order_id and new_quantity. New quantity must be less than the original quantity. Order must not have already expired.
public(friend) fun modify_order(self: &mut book::Book, order_id: u128, new_quantity: u64, timestamp: u64): (u64, &order::Order)
public(package) fun modify_order(
self: &mut Book,
order_id: u128,
new_quantity: u64,
timestamp: u64,
): (u64, &Order) {
assert!(new_quantity >= self.min_size, EOrderBelowMinimumSize);
assert!(new_quantity % self.lot_size == 0, EOrderInvalidLotSize);
let order = self.book_side_mut(order_id).borrow_mut(order_id);
assert!(
new_quantity < order.quantity(),
ENewQuantityMustBeLessThanOriginal,
);
let cancel_quantity = order.quantity() - new_quantity;
order.modify(new_quantity, timestamp);
(cancel_quantity, order)
}
mid_price
Returns the mid price of the order book.
public(friend) fun mid_price(self: &book::Book, current_timestamp: u64): u64
public(package) fun mid_price(self: &Book, current_timestamp: u64): u64 {
let (mut ask_ref, mut ask_offset) = self.asks.min_slice();
let (mut bid_ref, mut bid_offset) = self.bids.max_slice();
let mut best_ask_price = 0;
let mut best_bid_price = 0;
while (!ask_ref.is_null()) {
let best_ask_order = slice_borrow(
self.asks.borrow_slice(ask_ref),
ask_offset,
);
best_ask_price = best_ask_order.price();
if (current_timestamp <= best_ask_order.expire_timestamp()) break;
(ask_ref, ask_offset) = self.asks.next_slice(ask_ref, ask_offset);
};
while (!bid_ref.is_null()) {
let best_bid_order = slice_borrow(
self.bids.borrow_slice(bid_ref),
bid_offset,
);
best_bid_price = best_bid_order.price();
if (current_timestamp <= best_bid_order.expire_timestamp()) break;
(bid_ref, bid_offset) = self.bids.prev_slice(bid_ref, bid_offset);
};
assert!(!ask_ref.is_null() && !bid_ref.is_null(), EEmptyOrderbook);
math::mul(best_ask_price + best_bid_price, constants::half())
}
get_level2_range_and_ticks
Returns the best bids and asks. The number of ticks is the number of price levels to return. The price_low and price_high are the range of prices to return.
public(friend) fun get_level2_range_and_ticks(self: &book::Book, price_low: u64, price_high: u64, ticks: u64, is_bid: bool, current_timestamp: u64): (vector<u64>, vector<u64>)
public(package) fun get_level2_range_and_ticks(
self: &Book,
price_low: u64,
price_high: u64,
ticks: u64,
is_bid: bool,
current_timestamp: u64,
): (vector<u64>, vector<u64>) {
assert!(price_low <= price_high, EInvalidPriceRange);
assert!(
price_low >= constants::min_price() &&
price_low <= constants::max_price(),
EInvalidPriceRange,
);
assert!(
price_high >= constants::min_price() &&
price_high <= constants::max_price(),
EInvalidPriceRange,
);
assert!(ticks > 0, EInvalidTicks);
let mut price_vec = vector[];
let mut quantity_vec = vector[];
// convert price_low and price_high to keys for searching
let msb = if (is_bid) {
(0 as u128)
} else {
(1 as u128) << 127
};
let key_low = ((price_low as u128) << 64) + msb;
let key_high =
((price_high as u128) << 64) + (((1u128 << 64) - 1) as u128) + msb;
let book_side = if (is_bid) &self.bids else &self.asks;
let (mut ref, mut offset) = if (is_bid) {
book_side.slice_before(key_high)
} else {
book_side.slice_following(key_low)
};
let mut ticks_left = ticks;
let mut cur_price = 0;
let mut cur_quantity = 0;
while (!ref.is_null() && ticks_left > 0) {
let order = slice_borrow(book_side.borrow_slice(ref), offset);
if (current_timestamp <= order.expire_timestamp()) {
let (_, order_price, _) = utils::decode_order_id(order.order_id());
if (
(is_bid && order_price < price_low) || (
!is_bid && order_price > price_high,
)
) break;
if (
cur_price == 0 && (
(is_bid && order_price <= price_high) || (
!is_bid && order_price >= price_low,
),
)
) {
cur_price = order_price
};
if (cur_price != 0 && order_price != cur_price) {
price_vec.push_back(cur_price);
quantity_vec.push_back(cur_quantity);
cur_price = order_price;
cur_quantity = 0;
ticks_left = ticks_left - 1;
};
if (cur_price != 0) {
cur_quantity =
cur_quantity + order.quantity() - order.filled_quantity();
};
};
(ref, offset) =
if (is_bid) book_side.prev_slice(ref, offset)
else book_side.next_slice(ref, offset);
};
if (cur_price != 0) {
price_vec.push_back(cur_price);
quantity_vec.push_back(cur_quantity);
};
(price_vec, quantity_vec)
}
get_order
public(friend) fun get_order(self: &book::Book, order_id: u128): order::Order
public(package) fun get_order(self: &Book, order_id: u128): Order {
let order = self.book_side(order_id).borrow(order_id);
order.copy_order()
}
book_side_mut
fun book_side_mut(self: &mut book::Book, order_id: u128): &mut big_vector::BigVector<order::Order>
fun book_side_mut(self: &mut Book, order_id: u128): &mut BigVector<Order> {
let (is_bid, _, _) = utils::decode_order_id(order_id);
if (is_bid) {
&mut self.bids
} else {
&mut self.asks
}
}
book_side
fun book_side(self: &book::Book, order_id: u128): &big_vector::BigVector<order::Order>
fun book_side(self: &Book, order_id: u128): &BigVector<Order> {
let (is_bid, _, _) = utils::decode_order_id(order_id);
if (is_bid) {
&self.bids
} else {
&self.asks
}
}
match_against_book
Matches the given order and quantity against the order book. If is_bid, it will match against asks, otherwise against bids. Mutates the order and the maker order as necessary.
fun match_against_book(self: &mut book::Book, order_info: &mut order_info::OrderInfo, timestamp: u64)
fun match_against_book(
self: &mut Book,
order_info: &mut OrderInfo,
timestamp: u64,
) {
let is_bid = order_info.is_bid();
let book_side = if (is_bid) &mut self.asks else &mut self.bids;
let (mut ref, mut offset) = if (is_bid) book_side.min_slice()
else book_side.max_slice();
while (
!ref.is_null() &&
order_info.fills_ref().length() < constants::max_fills()
) {
let maker_order = slice_borrow_mut(
book_side.borrow_slice_mut(ref),
offset,
);
if (!order_info.match_maker(maker_order, timestamp)) break;
(ref, offset) =
if (is_bid) book_side.next_slice(ref, offset)
else book_side.prev_slice(ref, offset);
};
order_info.fills_ref().do_ref!(|fill| {
if (fill.expired() || fill.completed()) {
book_side.remove(fill.maker_order_id());
};
});
if (order_info.fills_ref().length() == constants::max_fills()) {
order_info.set_fill_limit_reached();
}
}
get_order_id
fun get_order_id(self: &mut book::Book, is_bid: bool): u64
fun get_order_id(self: &mut Book, is_bid: bool): u64 {
if (is_bid) {
self.next_bid_order_id = self.next_bid_order_id - 1;
self.next_bid_order_id
} else {
self.next_ask_order_id = self.next_ask_order_id + 1;
self.next_ask_order_id
}
}
inject_limit_order
Balance accounting happens before this function is called
fun inject_limit_order(self: &mut book::Book, order_info: &order_info::OrderInfo)
fun inject_limit_order(self: &mut Book, order_info: &OrderInfo) {
let order = order_info.to_order();
if (order_info.is_bid()) {
self.bids.insert(order_info.order_id(), order);
} else {
self.asks.insert(order_info.order_id(), order);
};
}