Source code for orderbookmdp.order_book.market

# -*- coding: utf-8 -*-
"""A limit order book market with defined order types in :py:mod:`OrderBookRL.order_book.order_types`.

The main function is to send a message to the market, receiving a possible trade or the order placed in the book.
A trade and a order is simply a list contaning information about them, see :py:mod:`OrderBookRL.order_book.constants`
for indexes of a trade and a order.

The abstract class :py:class:`Market` must implement send_message. An implementation of an :py:class:`ExternalMarket`
is implemented which can be used with an external flow of messages such as level 3 orderbook data.
The :py:class:`ExternalMarket` is first filled with a snapshot of the external order book and then artificial or real
messages can be sent.

"""

import abc

import numpy as np
from custom_inherit import DocInheritMeta

from orderbookmdp._orderbookmdp import CyOrderBook
from orderbookmdp.order_book.constants import BUY
from orderbookmdp.order_book.constants import EXT_ID
from orderbookmdp.order_book.constants import OIB_ID
from orderbookmdp.order_book.constants import SELL
from orderbookmdp.order_book.constants import SO_EXT_ID
from orderbookmdp.order_book.constants import SO_PRICE
from orderbookmdp.order_book.constants import SO_SIZE
from orderbookmdp.order_book.order_books import OrderBook
from orderbookmdp.order_book.order_books import PyOrderBook
from orderbookmdp.order_book.utils import to_int


[docs]def get_ob(ob_type: str, price_levels_type: str, price_level_type: str) -> OrderBook: """ Returns an order book depending on the parameters Parameters ---------- ob_type: str Type of order book. price_levels_type: str Type or price levels for the order book. price_level_type: str Type of order level for the price levels Returns ------- orderbook: OrderBook """ if ob_type == 'py': return PyOrderBook(price_levels_type, price_level_type) if ob_type == 'cy': return CyOrderBook(price_levels_type, price_level_type) else: raise NotImplementedError(ob_type)
[docs]class Market(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)): """ An abstract market class where an implementation needs to implement send_messages. A market always has an order book which is set in :py:meth:`__init__`. Attributes ---------- ob: OrderBook The markets order book. tick_size: float The minimum multiple of price difference in a market. tick_dec: int Number of decimals in tick_size multipler: int 10**tick_dec. Used to multiply price to int """
[docs] def __init__(self, tick_size=0.01, ob_type='py', price_level_type='ordered_dict', price_levels_type='sorted_dict'): """ Parameters ---------- tick_size: float The minimum multiple of price difference in a market. ob_type: str Type of order book. price_levels_type: str Type or price levels for the order book. price_level_type: str Type of order level for the price levels """ self.ob = get_ob(ob_type, price_level_type, price_levels_type) self.tick_size = tick_size self.tick_dec = int(np.log10(1 / tick_size)) self.multiplier = 10**self.tick_dec
[docs] @abc.abstractmethod def send_message(self, message: dict) -> (list, tuple): """ The market receives a message and returns a possible trade or an order in the book. Parameters ---------- message: SimpleNamespace Can for example be a limit order, market order, cancelation message etc. See :py:mod:`OrderBookRL.order_book.order_types` for different types 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]class ExternalMarket(Market): """ An implementation of a :py:class:`Market` which can be used with an external flow of messages. When initiated, should be filled with a snapshot of the order book and then progressed with artificial or real messages. Attributes ---------- external_market_order_ids : dict Keeps track of the external order ids if for example a cancellation or update of an external order occurs. time : str The current time of the market """
[docs] def __init__(self, tick_size=0.01, ob_type='py', price_level_type='ordered_dict', price_levels_type='sorted_dict',): super(ExternalMarket, self).__init__(tick_size, ob_type, price_level_type, price_levels_type) self.external_market_order_ids = {} self.time = '2000-1-1 00:00'
[docs] def send_message(self, mess: dict, external=False) -> (list, tuple): """ Parameters ---------- mess external: bool If the message is an external message """ trades, order_in_book = [], None mess_type = mess.type if external: self.time = mess.time if mess_type == 'received': order_type = mess.order_type if order_type == 'limit': if external: trades, order_in_book = self.ob.limit(to_int(mess.price, self.multiplier), mess.side, mess.size, mess.trader_id, self.time) else: trades, order_in_book = self.ob.limit(mess.price, mess.side, mess.size, mess.trader_id, self.time) if external and order_in_book is not None: self.external_market_order_ids[mess.order_id] = order_in_book[OIB_ID] elif order_type == 'market': if mess.size != -1: trades = self.ob.market_order(mess.size, mess.side, mess.trader_id, self.time) else: trades = self.ob.market_order_funds(mess.funds*self.multiplier, mess.side, mess.trader_id, self.time) elif mess_type == 'done': if mess.reason == 'canceled': if external: try: order_id = self.external_market_order_ids.pop(mess.order_id) self.ob.cancel(order_id) except (ValueError, KeyError) as e: # TODO Fix # traceback.print_exc() # print(mess) pass else: try: self.ob.cancel(mess.order_id) except (ValueError, KeyError) as e: # TODO Fix pass elif mess_type == 'change': if external: order_id = self.external_market_order_ids[mess.order_id] self.ob.update(order_id, mess.size) else: self.ob.update(mess.order_id, mess.size) return trades, order_in_book
[docs] def fill_snap(self, snap: dict): """ Fills the market with orders from a snapshot. The snapshot contains all limit orders in a market at a given time. Parameters ---------- snap: dict Contains all the limit orders in the market. Format: {'asks':[order1, order2, ...], 'bids': [order1, order2, ...] """ for message in snap['bids']: _, oib = self.ob.limit(to_int(float(message[SO_PRICE]), self.multiplier), BUY, float(message[SO_SIZE]), EXT_ID, self.time) if oib is not None: self.external_market_order_ids[message[SO_EXT_ID]] = oib[OIB_ID] for message in snap['asks']: _, oib = self.ob.limit(to_int(float(message[SO_PRICE]), self.multiplier), SELL, float(message[SO_SIZE]), EXT_ID, self.time) if oib is not None: self.external_market_order_ids[message[SO_EXT_ID]] = oib[OIB_ID]