Module 0x7::price_time_index
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.
This is internal module, which cannot be used directly, use OrderBook instead.
- Struct
PriceAscTime - Struct
PriceDescTime - Struct
OrderData - Enum
PriceTimeIndex - Constants
- Function
get_slippage_pct_precision - Function
new_price_time_idx - Function
best_bid_price - Function
best_ask_price - Function
get_mid_price - Function
get_slippage_price - Function
cancel_active_order - Function
is_taker_order - Function
single_match_with_current_active_order - Function
get_single_match_for_buy_order - Function
get_single_match_for_sell_order - Function
modify_order_data - Function
get_single_match_result - Function
increase_order_size - Function
decrease_order_size - Function
place_maker_order
use 0x1::big_ordered_map;
use 0x1::error;
use 0x1::option;
use 0x5::order_book_types;
use 0x5::order_match_types;
use 0x7::order_book_utils;
Struct PriceAscTime
struct PriceAscTime has copy, drop, store
Fields
-
price: u64 -
tie_breaker: order_book_types::IncreasingIdx
Struct PriceDescTime
struct PriceDescTime has copy, drop, store
Fields
-
price: u64 -
tie_breaker: order_book_types::DecreasingIdx
Struct OrderData
struct OrderData has copy, drop, store
Fields
-
order_id: order_book_types::OrderId -
order_book_type: order_book_types::OrderType -
size: u64
Enum PriceTimeIndex
OrderBook tracking active (i.e. unconditional, immediately executable) limit orders.
- invariant - all buys are smaller than sells, at all times.
- tie_breaker in sells is U128_MAX-value, to make sure largest value in the book that is taken first, is the one inserted first, amongst those with same bid price.
enum PriceTimeIndex has store
Variants
Constants
There is a code bug that breaks internal invariant
const EINTERNAL_INVARIANT_BROKEN: u64 = 2;
const EINVALID_MAKER_ORDER: u64 = 1;
const EINVALID_SLIPPAGE_BPS: u64 = 3;
const SLIPPAGE_PCT_PRECISION: u64 = 100;
========= Active OrderBook ===========
const U64_MAX: u64 = 18446744073709551615;
Function get_slippage_pct_precision
public fun get_slippage_pct_precision(): u64
Implementation
public fun get_slippage_pct_precision(): u64 {
SLIPPAGE_PCT_PRECISION
}
Function new_price_time_idx
public(friend) fun new_price_time_idx(): price_time_index::PriceTimeIndex
Implementation
public(friend) fun new_price_time_idx(): PriceTimeIndex {
// potentially add max value to both sides (that will be skipped),
// so that max_key never changes, and doesn't create conflict.
PriceTimeIndex::V1 {
buys: order_book_utils::new_default_big_ordered_map(),
sells: order_book_utils::new_default_big_ordered_map()
}
}
Function best_bid_price
Picks the best (i.e. highest) bid (i.e. buy) price from the active order book. Returns None if there are no buys
public(friend) fun best_bid_price(self: &price_time_index::PriceTimeIndex): option::Option<u64>
Implementation
public(friend) fun best_bid_price(self: &PriceTimeIndex): Option<u64> {
if (self.buys.is_empty()) {
option::none()
} else {
let (back_key, _) = self.buys.borrow_back();
option::some(back_key.price)
}
}
Function best_ask_price
Picks the best (i.e. lowest) ask (i.e. sell) price from the active order book. Returns None if there are no sells
public(friend) fun best_ask_price(self: &price_time_index::PriceTimeIndex): option::Option<u64>
Implementation
public(friend) fun best_ask_price(self: &PriceTimeIndex): Option<u64> {
if (self.sells.is_empty()) {
option::none()
} else {
let (front_key, _) = self.sells.borrow_front();
option::some(front_key.price)
}
}
Function get_mid_price
Returns the mid price (i.e. the average of the highest bid (buy) price and the lowest ask (sell) price. If there are o buys / sells, returns None.
public(friend) fun get_mid_price(self: &price_time_index::PriceTimeIndex): option::Option<u64>
Implementation
public(friend) fun get_mid_price(self: &PriceTimeIndex): Option<u64> {
if (self.sells.is_empty() || self.buys.is_empty()) {
return option::none();
};
let (front_key, _) = self.sells.borrow_front();
let best_ask = front_key.price;
let (back_key, _) = self.buys.borrow_back();
let best_bid = back_key.price;
option::some((best_bid + best_ask) / 2)
}
Function get_slippage_price
public(friend) fun get_slippage_price(self: &price_time_index::PriceTimeIndex, is_bid: bool, slippage_bps: u64): option::Option<u64>
Implementation
public(friend) fun get_slippage_price(
self: &PriceTimeIndex, is_bid: bool, slippage_bps: u64
): Option<u64> {
if (!is_bid) {
assert!(
slippage_bps <= get_slippage_pct_precision() * 100,
EINVALID_SLIPPAGE_BPS
);
};
let mid_price = self.get_mid_price();
if (mid_price.is_none()) {
return option::none();
};
let mid_price = mid_price.destroy_some();
let slippage = mul_div(
mid_price, slippage_bps, get_slippage_pct_precision() * 100
);
if (is_bid) {
option::some(mid_price + slippage)
} else {
option::some(mid_price - slippage)
}
}
Function cancel_active_order
public(friend) fun cancel_active_order(self: &mut price_time_index::PriceTimeIndex, price: u64, unique_priority_idx: order_book_types::IncreasingIdx, is_bid: bool): u64
Implementation
public(friend) fun cancel_active_order(
self: &mut PriceTimeIndex,
price: u64,
unique_priority_idx: IncreasingIdx,
is_bid: bool
): u64 {
if (is_bid) {
let key = PriceDescTime {
price,
tie_breaker: unique_priority_idx.into_decreasing_idx_type()
};
self.buys.remove(&key).size
} else {
let key = PriceAscTime { price, tie_breaker: unique_priority_idx };
self.sells.remove(&key).size
}
}
Function is_taker_order
Check if the order is a taker order - i.e. if it can be immediately matched with the order book fully or partially.
public(friend) fun is_taker_order(self: &price_time_index::PriceTimeIndex, price: u64, is_bid: bool): bool
Implementation
public(friend) fun is_taker_order(
self: &PriceTimeIndex, price: u64, is_bid: bool
): bool {
if (is_bid) {
let best_ask_price = self.best_ask_price();
best_ask_price.is_some() && price >= best_ask_price.destroy_some()
} else {
let best_bid_price = self.best_bid_price();
best_bid_price.is_some() && price <= best_bid_price.destroy_some()
}
}
Function single_match_with_current_active_order
fun single_match_with_current_active_order<K: copy, drop, store>(remaining_size: u64, cur_key: K, cur_value: price_time_index::OrderData, orders: &mut big_ordered_map::BigOrderedMap<K, price_time_index::OrderData>): order_match_types::ActiveMatchedOrder
Implementation
inline fun single_match_with_current_active_order<K: drop + copy + store>(
remaining_size: u64,
cur_key: K,
cur_value: OrderData,
orders: &mut BigOrderedMap<K, OrderData>
): ActiveMatchedOrder {
let is_cur_match_fully_consumed = cur_value.size <= remaining_size;
let matched_size_for_this_order =
if (is_cur_match_fully_consumed) {
orders.remove(&cur_key);
cur_value.size
} else {
modify_order_data(
orders,
&cur_key,
|order_data| {
order_data.size -= remaining_size;
}
);
remaining_size
};
new_active_matched_order(
cur_value.order_id,
matched_size_for_this_order, // Matched size on the maker order
cur_value.size - matched_size_for_this_order, // Remaining size on the maker order
cur_value.order_book_type
)
}
Function get_single_match_for_buy_order
fun get_single_match_for_buy_order(self: &mut price_time_index::PriceTimeIndex, price: u64, size: u64): order_match_types::ActiveMatchedOrder
Implementation
fun get_single_match_for_buy_order(
self: &mut PriceTimeIndex, price: u64, size: u64
): ActiveMatchedOrder {
let (smallest_key, smallest_value) = self.sells.borrow_front();
assert!(price >= smallest_key.price, EINTERNAL_INVARIANT_BROKEN);
single_match_with_current_active_order(
size,
smallest_key,
*smallest_value,
&mut self.sells
)
}
Function get_single_match_for_sell_order
fun get_single_match_for_sell_order(self: &mut price_time_index::PriceTimeIndex, price: u64, size: u64): order_match_types::ActiveMatchedOrder
Implementation
fun get_single_match_for_sell_order(
self: &mut PriceTimeIndex, price: u64, size: u64
): ActiveMatchedOrder {
let (largest_key, largest_value) = self.buys.borrow_back();
assert!(price <= largest_key.price, EINTERNAL_INVARIANT_BROKEN);
single_match_with_current_active_order(
size,
largest_key,
*largest_value,
&mut self.buys
)
}
Function modify_order_data
fun modify_order_data<K: copy, drop, store>(orders: &mut big_ordered_map::BigOrderedMap<K, price_time_index::OrderData>, key: &K, modify_fn: |&mut price_time_index::OrderData|)
Implementation
inline fun modify_order_data<K: drop + copy + store>(
orders: &mut BigOrderedMap<K, OrderData>,
key: &K,
modify_fn: |&mut OrderData|
) {
let order = orders.borrow_mut(key);
modify_fn(order);
}
Function get_single_match_result
public(friend) fun get_single_match_result(self: &mut price_time_index::PriceTimeIndex, price: u64, size: u64, is_bid: bool): order_match_types::ActiveMatchedOrder
Implementation
public(friend) fun get_single_match_result(
self: &mut PriceTimeIndex,
price: u64,
size: u64,
is_bid: bool
): ActiveMatchedOrder {
if (is_bid) {
self.get_single_match_for_buy_order(price, size)
} else {
self.get_single_match_for_sell_order(price, size)
}
}
Function increase_order_size
Increase the size of the order in the orderbook without altering its position in the price-time priority.
public(friend) fun increase_order_size(self: &mut price_time_index::PriceTimeIndex, price: u64, unique_priority_idx: order_book_types::IncreasingIdx, size_delta: u64, is_bid: bool)
Implementation
public(friend) fun increase_order_size(
self: &mut PriceTimeIndex,
price: u64,
unique_priority_idx: IncreasingIdx,
size_delta: u64,
is_bid: bool
) {
if (is_bid) {
let key = PriceDescTime {
price,
tie_breaker: unique_priority_idx.into_decreasing_idx_type()
};
modify_order_data(
&mut self.buys,
&key,
|order_data| {
order_data.size += size_delta;
}
);
} else {
let key = PriceAscTime { price, tie_breaker: unique_priority_idx };
modify_order_data(
&mut self.sells,
&key,
|order_data| {
order_data.size += size_delta;
}
);
};
}
Function decrease_order_size
Decrease the size of the order in the order book without altering its position in the price-time priority.
public(friend) fun decrease_order_size(self: &mut price_time_index::PriceTimeIndex, price: u64, unique_priority_idx: order_book_types::IncreasingIdx, size_delta: u64, is_bid: bool)
Implementation
public(friend) fun decrease_order_size(
self: &mut PriceTimeIndex,
price: u64,
unique_priority_idx: IncreasingIdx,
size_delta: u64,
is_bid: bool
) {
if (is_bid) {
let key = PriceDescTime {
price,
tie_breaker: unique_priority_idx.into_decreasing_idx_type()
};
modify_order_data(
&mut self.buys,
&key,
|order_data| {
order_data.size -= size_delta;
}
);
} else {
let key = PriceAscTime { price, tie_breaker: unique_priority_idx };
modify_order_data(
&mut self.sells,
&key,
|order_data| {
order_data.size -= size_delta;
}
);
};
}
Function place_maker_order
public(friend) fun place_maker_order(self: &mut price_time_index::PriceTimeIndex, order_id: order_book_types::OrderId, order_book_type: order_book_types::OrderType, price: u64, unique_priority_idx: order_book_types::IncreasingIdx, size: u64, is_bid: bool)
Implementation
public(friend) fun place_maker_order(
self: &mut PriceTimeIndex,
order_id: OrderId,
order_book_type: OrderType,
price: u64,
unique_priority_idx: IncreasingIdx,
size: u64,
is_bid: bool
) {
let value = OrderData { order_id, order_book_type, size };
// Assert that this is not a taker order
assert!(!self.is_taker_order(price, is_bid), EINVALID_MAKER_ORDER);
if (is_bid) {
let key = PriceDescTime {
price,
tie_breaker: unique_priority_idx.into_decreasing_idx_type()
};
self.buys.add(key, value);
} else {
let key = PriceAscTime { price, tie_breaker: unique_priority_idx };
self.sells.add(key, value);
};
}