Source code for orderbookmdp.order_book.order_books

import abc

from custom_inherit import DocInheritMeta

import orderbookmdp._orderbookmdp
from orderbookmdp.order_book.constants import BUY
from orderbookmdp.order_book.constants import O_ID
from orderbookmdp.order_book.constants import O_PRICE
from orderbookmdp.order_book.constants import O_SIDE
from orderbookmdp.order_book.constants import O_SIZE
from orderbookmdp.order_book.constants import O_TRADER_ID
from orderbookmdp.order_book.constants import SELL
from orderbookmdp.order_book.price_levels import AVLTreePriceLevels
from orderbookmdp.order_book.price_levels import ListPriceLevels
from orderbookmdp.order_book.price_levels import PriceLevels
from orderbookmdp.order_book.price_levels import RBTreePriceLevels
from orderbookmdp.order_book.price_levels import SortedDictPriceLevels


[docs]def get_price_levels(price_levels_type: str, price_level_type: str, **kwargs) -> PriceLevels: """ Returns a type or price levels based on the parameters Parameters ---------- price_levels_type: str Type or price levels for the order book. price_level_type: str Type of order level for the price levels kwargs Returns ------- price_levels: PriceLevels """ if price_levels_type == 'sorted_dict': return SortedDictPriceLevels(price_level_type, **kwargs) elif price_levels_type == 'fast_rb': return RBTreePriceLevels(price_level_type, **kwargs) elif price_levels_type == 'fast_avl': return AVLTreePriceLevels(price_level_type, **kwargs) elif price_levels_type == 'list': return ListPriceLevels(price_level_type, **kwargs) elif price_levels_type == 'cylist': return orderbookmdp._orderbookmdp.CyListPriceLevels(price_level_type, **kwargs)
[docs]class OrderBook(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)): """ A abstract class defining a order book interface. The main functions are the functions for the messages. Sending a limit order, market order etc. The order book handles the matching and storing of limit orders. Important notes is the speed of adding a limit order, cancelling a limit order or updating a limit order. Attributes ---------- orders : dict All current orders in the order book, key is the order id order_id : int The internal order id set by the order book. Is incremented for each order sent to the order book. """
[docs] def __init__(self, price_level_type='cydeque', price_levels_type='cylist'): self.price_levels = get_price_levels(price_levels_type, price_level_type) self.orders = {} self.order_id = 0
[docs] @abc.abstractmethod def limit(self, price: int, side: int, size: float, trader_id: int, time: str) -> (list, tuple): """ Handles a limit order sent to the order book. Matches the limit order if possible, otherwise puts it in the order book. Parameters ---------- price: float Price of the order. side: int BUY or SELL, see :py:mod:´OrderBookRL.order_book.constants´ size: float Size of the order. trader_id: int Id of the trader sending the order time: str Returns ------- trades, order_in_book trades: list If trades have occurred due the the message, all the trades are returned. Otherwise empty list order_in_book: tuple If a limit order has been placed which size has not fully been matched, the remaining order in book is returned. Otherwise -1. """
[docs] @abc.abstractmethod def market_order(self, size: float, side: int, trader_id: int, time: str) -> list: """ Handles a market order sent to the order book. Matches the market order if possible. Parameters ---------- side: int BUY or SELL, see :py:mod:´OrderBookRL.order_book.constants´ size: float Size of the order. trader_id: int Id of the trader sending the order time: str Returns ------- trades: list If trades have occurred due the the message, all the trades are returned. Otherwise empty list """
[docs] @abc.abstractmethod def market_order_funds(self, funds: float, side: int, trader_id: int, time: str) -> list: """ Handles a market order sent to the order book. Matches the market order if possible. Parameters ---------- funds: float Size of the order. side: int BUY or SELL, see :py:mod:´OrderBookRL.order_book.constants´ trader_id: int Id of the trader sending the order time: str Returns ------- trades: list If trades have occurred due the the message, all the trades are returned. Otherwise empty list """
[docs] @abc.abstractmethod def cancel(self, order_id: int): """ Attempts to cancel a order by its order id Parameters ---------- order_id: int The order id of the order to be cancelled """
[docs] @abc.abstractmethod def update(self, order_id: int, size: float): """ Attempts to update a order by its order id Parameters ---------- order_id: int The order id of the order to be updated size: float The new size of the order """
[docs]class PyOrderBook(OrderBook): """An implementation of the abstract class :py:class:`OrderBook`. """
[docs] def limit(self, price: int, side: int, size: float, trader_id: int, time: str) -> (list, tuple): trades = [] if side == BUY: if self.price_levels.exist_sell_orders(): ask = self.price_levels.get_ask() while price >= ask: price_level = self.price_levels.get_level(SELL, ask) while price_level.is_not_empty(): level_entry = price_level.get_first() level_entry_size = level_entry[O_SIZE] if size < level_entry_size: price_level.update(level_entry, -size) # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], ask, size, level_entry[O_ID], side, time)) return trades, None else: price_level.delete_first(level_entry) self.orders.pop(level_entry[O_ID]) if price_level.is_empty(): self.price_levels.remove_level(SELL, ask) size -= level_entry_size # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], ask, level_entry_size, level_entry[O_ID], side, time)) if size == 0: return trades, None if self.price_levels.exist_sell_orders(): ask = self.price_levels.get_ask() else: break self.order_id += 1 # Limit Order: [side, price, size, trader_id, order_id] order = self.price_levels.add_order(side, price, size, trader_id, self.order_id) if order != -1: self.orders[self.order_id] = order # Order in Book : (order_id, size, side, price) order_in_book = (self.order_id, size, side, price) return trades, order_in_book else: return trades, None else: if self.price_levels.exist_buy_orders(): bid = self.price_levels.get_bid() while price <= bid: price_level = self.price_levels.get_level(BUY, bid) while price_level.is_not_empty(): level_entry = price_level.get_first() level_entry_size = level_entry[O_SIZE] if size < level_entry_size: price_level.update(level_entry, -size) # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], bid, size, level_entry[O_ID], side, time)) return trades, None else: price_level.delete_first(level_entry) self.orders.pop(level_entry[O_ID]) if price_level.is_empty(): self.price_levels.remove_level(BUY, bid) size -= level_entry_size # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], bid, level_entry_size, level_entry[O_ID], side, time)) if size == 0: return trades, None if self.price_levels.exist_buy_orders(): bid = self.price_levels.get_bid() else: break self.order_id += 1 # Limit Order: [side, price, size, trader_id, order_id] order = self.price_levels.add_order(side, price, size, trader_id, self.order_id) if order != -1: self.orders[self.order_id] = order # Order in Book : (order_id, size, side, price) order_in_book = (self.order_id, size, side, price) return trades, order_in_book else: return trades, None
[docs] def cancel(self, order_id: int): if order_id in self.orders: order = self.orders.pop(order_id) level = self.price_levels.get_level(order[O_SIDE], order[O_PRICE]) level.delete(order) if level.is_empty(): self.price_levels.remove_level(order[O_SIDE], order[O_PRICE])
[docs] def update(self, order_id: int, size: float): if order_id in self.orders: order = self.orders[order_id] price_level = self.price_levels.get_level(order[O_SIDE], order[O_PRICE]) price_level.update(order, size - order[O_SIZE])
[docs] def market_order(self, size: float, side: int, trader_id: int, time: str) -> list: trades = [] if side == BUY: while (size > 0) and self.price_levels.exist_sell_orders(): ask = self.price_levels.get_ask() price_level = self.price_levels.get_level(SELL, ask) while price_level.is_not_empty(): level_entry = price_level.get_first() level_entry_size = level_entry[O_SIZE] if size < level_entry_size: price_level.update(level_entry, -size) # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], ask, size, level_entry[O_ID], side, time)) return trades else: price_level.delete_first(level_entry) self.orders.pop(level_entry[O_ID]) size -= level_entry_size # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], ask, level_entry_size, level_entry[O_ID], side, time)) if size == 0: return trades self.price_levels.remove_level(SELL, ask) else: while (size > 0) and self.price_levels.exist_buy_orders(): bid = self.price_levels.get_bid() price_level = self.price_levels.get_level(BUY, bid) while price_level.is_not_empty(): level_entry = price_level.get_first() level_entry_size = level_entry[O_SIZE] if size < level_entry_size: price_level.update(level_entry, -size) # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], bid, size, level_entry[O_ID], side, time)) return trades else: price_level.delete_first(level_entry) self.orders.pop(level_entry[O_ID]) size -= level_entry_size # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], bid, level_entry_size, level_entry[O_ID], side, time)) if size == 0: return trades self.price_levels.remove_level(BUY, bid) return trades
[docs] def market_order_funds(self, funds: float, side: int, trader_id: int, time: str) -> list: trades = [] if side == BUY: while (funds > 0) and self.price_levels.exist_sell_orders(): ask = self.price_levels.get_ask() size = funds / ask price_level = self.price_levels.get_level(SELL, ask) while price_level.is_not_empty(): level_entry = price_level.get_first() level_entry_size = level_entry[O_SIZE] if size < level_entry_size: price_level.update(level_entry, -size) # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], ask, size, level_entry[O_ID], side, time)) return trades else: price_level.delete_first(level_entry) self.orders.pop(level_entry[O_ID]) size -= level_entry_size # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], ask, level_entry_size, level_entry[O_ID], side, time)) if size == 0: return trades else: funds -= level_entry_size * ask self.price_levels.remove_level(SELL, ask) else: while (funds > 0) and self.price_levels.exist_buy_orders(): bid = self.price_levels.get_bid() size = funds / bid price_level = self.price_levels.get_level(BUY, bid) while price_level.is_not_empty(): level_entry = price_level.get_first() level_entry_size = level_entry[O_SIZE] if size < level_entry_size: price_level.update(level_entry, -size) # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], bid, size, level_entry[O_ID], side, time)) return trades else: price_level.delete_first(level_entry) self.orders.pop(level_entry[O_ID]) size -= level_entry_size # Trade : (trader_id, counter_part_id, price, size, order_id) trades.append((trader_id, level_entry[O_TRADER_ID], bid, level_entry_size, level_entry[O_ID], side, time)) if size == 0: return trades else: funds -= level_entry_size * bid self.price_levels.remove_level(BUY, bid) return trades