├── src ├── __init__.py └── order_book.py ├── tests ├── __init__.py └── order_book.py ├── requirements.txt ├── .gitignore ├── demo.py └── README.md /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bintrees==2.0.4 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | .venv/ 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | from src.order_book import OrderBook 2 | 3 | ''' 4 | Basic liquidity providing principles 5 | ''' 6 | 7 | book = OrderBook() 8 | 9 | # 0 0 0 10 | print(book.ask_size, book.bid_size, book.total_volume_traded) 11 | 12 | # LMT sell 10pcs @12.5 13 | book.submit_order('lmt', 'ask', 10, 12.5, 1) 14 | 15 | # LMT buy 10pcs @10.5 16 | book.submit_order('lmt', 'bid', 10, 10.5, 2) 17 | 18 | # 10 10 0 19 | print(book.ask_size, book.bid_size, book.total_volume_traded) 20 | 21 | # Satisfies market demand on both sides 22 | book.submit_order('lmt', 'bid', 10, 20, 3) 23 | book.submit_order('lmt', 'ask', 10, 5, 3) 24 | 25 | # 0 0 20 26 | print(book.ask_size, book.bid_size, book.total_volume_traded) 27 | 28 | ''' 29 | Market microstructure 30 | --- 31 | Implementation is not making concrete orders public 32 | only order sizes on given level 33 | ''' 34 | 35 | book = OrderBook() 36 | 37 | # Get 5 levels of order book 38 | # [[], []] 39 | print(book.get_mkt_depth(5)) 40 | 41 | book.submit_order('lmt', 'ask', 2, 10, 1) 42 | book.submit_order('lmt', 'ask', 4, 20, 1) 43 | book.submit_order('lmt', 'ask', 6, 30, 1) 44 | 45 | book.submit_order('lmt', 'bid', 1, 1, 2) 46 | book.submit_order('lmt', 'bid', 5, 2, 2) 47 | book.submit_order('lmt', 'bid', 7, 3, 2) 48 | 49 | # [[[Price, Size] * n], [[Price, Size] * n]] 50 | print(book.get_mkt_depth(3)) 51 | 52 | # ([IDs], [{ID: (size, side, priority_id)}]) 53 | # OrderBook respects time priority. It means that if 54 | # trader A submitted LMT buy @10 and trader B did same 55 | # then trader A is traded first 56 | print(book.get_participant_orders(1)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | My implementation of Exchange Limit Orderbook I did for Prop trading department of the leading CEE investment bank. (NDA free part of bigger solution) 3 | 4 | ## What is OrderBook 5 | The OrderBook is a place where orders get executed and where liquidity is made. 6 | 7 | It's a core of every capital markets exchange and its simulation and modeling is an elementary step in market microstructure discovery what is a base for not only Structural arbitrage trading algorithms. 8 | 9 | [Read more about OB at Investopedia](http://www.investopedia.com/terms/o/order-book.asp) 10 | 11 | ## Data structures used 12 | Order data are stored in AVL trees - fast C language implementation of AVL trees (bintrees package). Update and search both have complexity of O(log n) thanks to this. 13 | 14 | Multiple data redundancies are tolerated to achieve a maximal execution speed (for RT market structure simulation is speed crucial) 15 | 16 | ## Operations available 17 | 18 | Checkout demo.py for brief introduction or unit test in folder tests. 19 | 20 | book = OrderBook() 21 | 22 | * submit_order(order_type, side, size, price, participant_id) - lmt/mkt is supported, ask/bid sides 23 | * cancel(order_id) 24 | * ask_size - on best ask level 25 | * total_ask_size - over all levels 26 | * -- same for bid -- 27 | * total_volume_traded 28 | * total_volume_pending 29 | * ask - best ask price 30 | * bid - best bid price 31 | * get_participant_orders(participant_id) 32 | * get_mkt_depth(n) - returns n levels of order book 33 | 34 | Order matching algorithm is executed after every order submit and executed orders are returned by submit_order function. 35 | 36 | ## Persistance 37 | 38 | Persistance is done with pickle module (serializing processed data so no security risk at all). Whole OrderBook object is pickle serializable. -------------------------------------------------------------------------------- /tests/order_book.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import random 3 | import time 4 | import pickle 5 | 6 | import sys 7 | 8 | sys.path.append('./') 9 | 10 | from src.order_book import OrderBook 11 | 12 | class OrderBookTest(unittest.TestCase): 13 | def test_submit_order(self): 14 | order_id = None 15 | updates = None 16 | 17 | ob = OrderBook() 18 | owner_id = 1 19 | 20 | try: 21 | order_id, updates = ob.submit_order('lmt', 'ask', 10, 125.12, owner_id) 22 | except: 23 | pass 24 | 25 | self.assertIsNotNone(order_id) 26 | self.assertIsNotNone(updates) 27 | self.assertListEqual(updates, []) 28 | 29 | def test_side_size_calculation(self): 30 | ask_ob = OrderBook() 31 | bid_ob = OrderBook() 32 | size_sum = 0 33 | 34 | sz = 0 35 | for i in range(random.randint(1E3, 1E4)): 36 | sz = random.randint(1, 10) 37 | 38 | ask_ob.submit_order('lmt', 'ask', sz, 1E6 - i, 1) 39 | bid_ob.submit_order('lmt', 'bid', sz, i, 1) 40 | 41 | size_sum += sz 42 | 43 | self.assertEqual(ask_ob.ask_size, sz) 44 | self.assertEqual(bid_ob.bid_size, sz) 45 | 46 | self.assertEqual(ask_ob.total_ask_size, size_sum) 47 | self.assertEqual(bid_ob.total_bid_size, size_sum) 48 | 49 | 50 | def test_lmt_same_price(self): 51 | ob = OrderBook() 52 | granulated_size = random.choice(['ask', 'bid']) 53 | price = random.randint(1, 1E3) / 10 54 | size_sum = 0 55 | for i in range(int(1E3)): 56 | sz = 2 * random.randint(1, 1E2) 57 | size_sum += sz 58 | 59 | if 'ask' == granulated_size: 60 | ob.submit_order('lmt', 'ask', sz, price, 1) 61 | else: 62 | ob.submit_order('lmt', 'bid', sz, price, 1) 63 | 64 | if 'ask' != granulated_size: 65 | ob.submit_order('lmt', 'ask', size_sum / 2, price, 1) 66 | 67 | self.assertEqual(ob.ask_size, 0) 68 | self.assertEqual(ob.total_ask_size, 0) 69 | 70 | self.assertEqual(ob.bid_size, size_sum / 2) 71 | self.assertEqual(ob.total_bid_size, size_sum / 2) 72 | else: 73 | ob.submit_order('lmt', 'bid', size_sum / 2, price, 1) 74 | 75 | self.assertEqual(ob.bid_size, 0) 76 | self.assertEqual(ob.total_bid_size, 0) 77 | 78 | self.assertEqual(ob.ask_size, size_sum / 2) 79 | self.assertEqual(ob.total_ask_size, size_sum / 2) 80 | 81 | def test_owner_matching(self): 82 | ob = OrderBook() 83 | bid_id, bid_trades = ob.submit_order('lmt', 'bid', 2, 2, 1) 84 | ask_id, ask_trades = ob.submit_order('lmt', 'ask', 2, 2, 2) 85 | 86 | # 0 - public message 87 | ask_owner = ask_trades[1][4] # Order which matched with existing goes first 88 | bid_owner = ask_trades[2][4] 89 | 90 | self.assertEqual(bid_id, bid_owner) 91 | self.assertEqual(ask_id, ask_owner) 92 | 93 | def test_ask_goes_first(self): 94 | ob = OrderBook() 95 | bid_id, bid_trades = ob.submit_order('lmt', 'bid', 2, 2, 1) 96 | ask_id, ask_trades = ob.submit_order('lmt', 'ask', 2, 2, 2) 97 | 98 | self.assertEqual(ask_trades[1][5], 'ask') 99 | self.assertEqual(ask_trades[2][5], 'bid') 100 | 101 | def test_get_orders(self): 102 | ob = OrderBook() 103 | 104 | ask_ids = [] 105 | bid_id, bid_trades = ob.submit_order('lmt', 'bid', 2, 2, 1) 106 | 107 | ask_id, ask_trades = ob.submit_order('lmt', 'ask', 2, 3, 2) 108 | ask_ids.append(ask_id) 109 | 110 | ask_id, ask_trades = ob.submit_order('lmt', 'ask', 2, 3, 2) 111 | ask_ids.append(ask_id) 112 | 113 | ask_id, ask_trades = ob.submit_order('lmt', 'ask', 2, 3, 2) 114 | ask_ids.append(ask_id) 115 | 116 | self.assertListEqual(ask_ids, ob.get_participant_orders(2)[0]) 117 | self.assertListEqual([bid_id], ob.get_participant_orders(1)[0]) 118 | 119 | def test_persistence(self): 120 | ob_original = OrderBook() 121 | ob_recovered = OrderBook() 122 | 123 | price_levels = 0 124 | total_size = 0 125 | for price in range(random.randint(1E2, 1E3)): 126 | for size in range(random.randint(5, 15)): 127 | total_size += 2 * size 128 | 129 | ob_original.submit_order('lmt', 'bid', size, price, 1) 130 | ob_original.submit_order('lmt', 'ask', size, 1E6 - price, 1) 131 | 132 | price_levels += 1 133 | 134 | # started = time.time() 135 | ob_recovered = pickle.loads(pickle.dumps(ob_original)) 136 | # print("Time to recover", price_levels, "price levels order book. With total size", total_size, time.time() - started) 137 | 138 | self.assertListEqual(ob_original.get_mkt_depth(price_levels), ob_recovered.get_mkt_depth(price_levels)) 139 | self.assertEqual(ob_original.bid_size, ob_recovered.bid_size) 140 | self.assertEqual(ob_original.total_bid_size, ob_recovered.total_bid_size) 141 | 142 | def test_total_volume(self): 143 | ob = OrderBook() 144 | total_size = 0 145 | for price in range(random.randint(1E2, 1E3)): 146 | for size in range(random.randint(5, 15)): 147 | total_size += 2 * size 148 | 149 | ob.submit_order('lmt', 'bid', size, price, 1) 150 | ob.submit_order('lmt', 'ask', size, 1E6 - price, 1) 151 | 152 | self.assertEqual(ob.total_volume_pending, total_size) 153 | self.assertEqual(ob.total_volume_traded, 0) 154 | 155 | ob = OrderBook() 156 | ob.submit_order('lmt', 'bid', 1, 1, 1) 157 | ob.submit_order('lmt', 'ask', 1, 1, 1) 158 | 159 | self.assertEqual(ob.total_volume_traded, 1) 160 | self.assertEqual(ob.total_volume_pending, 0) 161 | 162 | def test_spread(self): 163 | ob = OrderBook() 164 | 165 | price = random.randint(1, 1E3) 166 | offset = random.randint(1, 1E3) 167 | 168 | # order_type, side, size, price, owner_id 169 | ob.submit_order('lmt', 'bid', 2, price, 5) 170 | ob.submit_order('lmt', 'ask', 2, price + offset, 5) 171 | 172 | self.assertEqual(ob.spread, offset) 173 | 174 | def test_performance(self): 175 | def test_struct(struct, method, test_rounds): 176 | times = [] 177 | for i in range(int(test_rounds)): 178 | started = time.time() 179 | price = random.randint(1E2, 1E3) / 10 180 | size = random.randint(1E3, 1E4) 181 | side = random.choice(['ask', 'bid']) 182 | order_type = random.choice(['lmt', 'mkt']) 183 | 184 | if method == 'append': 185 | struct.append((price, side)) 186 | 187 | if method == 'submit_order': 188 | struct.submit_order(order_type, side, size, price, 1) 189 | 190 | times.append(time.time() - started) 191 | 192 | return sum(times) / test_rounds 193 | 194 | for test_rounds in range(1, 5): 195 | ob_avg_t = test_struct(OrderBook(), 'submit_order', 10 ** test_rounds) 196 | list_avg_t = test_struct([], 'append', 10 ** test_rounds) 197 | 198 | print('\n', 10 ** test_rounds, ob_avg_t, list_avg_t) 199 | 200 | self.assertLess(list_avg_t / ob_avg_t, 5) 201 | 202 | 203 | if __name__ == '__main__': 204 | unittest.main() 205 | -------------------------------------------------------------------------------- /src/order_book.py: -------------------------------------------------------------------------------- 1 | from bintrees import FastAVLTree 2 | import pickle 3 | 4 | class OrderBook: 5 | """Limit order book able to process LMT and MKT orders 6 | MKT orders are disassembled to LMT orders up to current liquidity situation 7 | """ 8 | 9 | def __init__(self): 10 | # AVL trees are used as a main structure due its optimal performance features for this purpose 11 | 12 | self._participants = FastAVLTree() 13 | self._order_owners = FastAVLTree() # Assigning ID -> Owner 14 | 15 | self._asks = FastAVLTree() # MIN Heap 16 | self._bids = FastAVLTree() # MAX Heap 17 | self._price_ids = FastAVLTree() # Assigning ID -> Price 18 | 19 | self._total_ask_size = 0 # For monitoring purpose 20 | self._total_bid_size = 0 # For monitoring purpose 21 | 22 | self._last_order_id = 0 # Increases with each order processed 23 | self._cleared_orders_count = 0 # For monitoring purpose 24 | self._total_volume_traded = 0 # For monitoring purpose 25 | self._total_volume_pending = 0 # For monitoring purpose 26 | 27 | 28 | def __getstate__(self): 29 | """ Whole book could be repopulated from dict containing class attributes """ 30 | 31 | return self.__dict__ 32 | 33 | def __setstate__(self, state): 34 | """ Book repopulation (recovery) """ 35 | 36 | for attr_name, attr_val in state.items(): 37 | setattr(self, attr_name, attr_val) 38 | 39 | def _get_order_id(self): 40 | """ Orders id managment """ 41 | 42 | self._last_order_id += 1 43 | return self._last_order_id 44 | 45 | def _balance(self, trades_stack): 46 | """ Executes trades if it finds liquidity for them """ 47 | 48 | # No liquidity at all 49 | if self._asks.is_empty() or self._bids.is_empty(): 50 | return trades_stack 51 | 52 | min_ask = self._asks.min_key() 53 | max_bid = self._bids.max_key() 54 | 55 | # Check liquidity situation 56 | if max_bid >= min_ask: 57 | ask_orders = self._asks.get(min_ask) 58 | bid_orders = self._bids.get(max_bid) 59 | 60 | for ask_order in ask_orders: 61 | for bid_order in bid_orders: 62 | if not ask_order in ask_orders or not bid_order in bid_orders: 63 | continue 64 | 65 | traded = min(ask_orders[ask_order], bid_orders[bid_order]) 66 | 67 | ask_orders[ask_order] -= traded 68 | bid_orders[bid_order] -= traded 69 | 70 | self._total_ask_size -= traded 71 | self._total_bid_size -= traded 72 | 73 | self._total_volume_traded += traded 74 | self._total_volume_pending -= 2 * traded 75 | 76 | ask_owner = self._order_owners[ask_order] 77 | bid_owner = self._order_owners[bid_order] 78 | 79 | # Buy side order fully liquidated 80 | if bid_orders[bid_order] == 0: 81 | # print("BID ORDER LIQUIDATED") 82 | self._cleared_orders_count += 1 83 | del bid_orders[bid_order] 84 | del self._price_ids[bid_order] 85 | 86 | del self._order_owners[bid_order] 87 | owner_ids = self._participants[bid_owner] 88 | owner_ids.remove(bid_order) 89 | 90 | del self._participants[bid_owner] 91 | self._participants.insert(bid_owner, owner_ids) 92 | 93 | # Sell side order fully liquidated 94 | if ask_orders[ask_order] == 0: 95 | # print("ASK ORDER LIQUIDATED") 96 | self._cleared_orders_count += 1 97 | del ask_orders[ask_order] 98 | del self._price_ids[ask_order] 99 | 100 | del self._order_owners[ask_order] 101 | owner_ids = self._participants[ask_owner] 102 | owner_ids.remove(ask_order) 103 | 104 | del self._participants[ask_owner] 105 | self._participants.insert(ask_owner, owner_ids) 106 | 107 | # Inform sides about state of their orders 108 | trades_stack.append((0, traded, max_bid)) 109 | trades_stack.append((1, ask_order, traded, max_bid, ask_owner, 'ask')) 110 | trades_stack.append((1, bid_order, traded, max_bid, bid_owner, 'bid')) 111 | 112 | # Whole ASK price level were liquidated, remove it from three and let it rebalance 113 | if self._asks[min_ask].is_empty(): 114 | # print("ASK level liquidated") 115 | del self._asks[min_ask] 116 | 117 | # Whole BID price level were liquidated, remove it from three and let it rebalance 118 | if self._bids[max_bid].is_empty(): 119 | # print("BID level liquidated") 120 | del self._bids[max_bid] 121 | else: 122 | return trades_stack 123 | 124 | return self._balance(trades_stack) 125 | 126 | def _submit_mkt(self, side, size, participant_id): 127 | """ Find liquidity for mkt order - put multiple lmt orders to extract liquidity for order execution """ 128 | 129 | orders_list = [] 130 | trades_stack = [] 131 | 132 | while size > 0: 133 | if side == 'ask': 134 | second_side_size = self.bid_size 135 | second_side_price = self.bid 136 | else: 137 | second_side_size = self.ask_size 138 | second_side_price = self.ask 139 | 140 | # We could only taky liquidity which exists 141 | trade_size = min([second_side_size, size]) 142 | orders_list.append(self._submit_lmt(side, trade_size, second_side_price, participant_id)) 143 | trades_stack = self._balance(trades_stack) 144 | 145 | size -= trade_size 146 | 147 | return 0, trades_stack 148 | 149 | 150 | def _submit_lmt(self, side, size, price, participant_id): 151 | """ Submits LMT order to book """ 152 | 153 | # Assign order ID 154 | order_id = self._get_order_id() 155 | 156 | # Pending volume monitoring 157 | self._total_volume_pending += size 158 | self._price_ids.insert(order_id, (price, side)) 159 | 160 | # Keep track of participant orders, book will be asked for sure 161 | if participant_id not in self._participants: 162 | self._participants.insert(participant_id, [order_id]) 163 | else: 164 | owner_trades = self._participants.get(participant_id, []) 165 | owner_trades.append(order_id) 166 | 167 | self._order_owners.insert(order_id, participant_id) 168 | 169 | # Assign to right (correct) side 170 | if side == 'ask': 171 | self._total_ask_size += size 172 | ask_level = self._asks.get(price, FastAVLTree()) 173 | ask_level.insert(order_id, size) 174 | 175 | if price not in self._asks: 176 | self._asks.insert(price, ask_level) 177 | else: # bid 178 | self._total_bid_size += size 179 | bid_level = self._bids.get(price, FastAVLTree()) 180 | bid_level.insert(order_id, size) 181 | 182 | if price not in self._bids: 183 | self._bids.insert(price, bid_level) 184 | 185 | return order_id 186 | 187 | def cancel(self, order_id): 188 | """ Cancel order """ 189 | 190 | # Finds and cancels order 191 | 192 | order = self._price_ids[order_id] 193 | 194 | if order[1] == 'ask': 195 | del self._asks[order[0]][order_id] 196 | 197 | if self._asks[order[0]].is_empty(): 198 | del self._asks[order[0]] 199 | else: 200 | del self._bids[order[0]][order_id] 201 | 202 | if self._bids[order[0]].is_empty(): 203 | del self._bids[order[0]] 204 | 205 | @property 206 | def ask_size(self): 207 | """ Volume waiting on ask side bottom level - liquidity level size for ask price """ 208 | 209 | best_ask = self.get_mkt_depth(1)[0] 210 | 211 | if len(best_ask) == 0: 212 | return 0 213 | else: 214 | return best_ask[0][1] 215 | 216 | @property 217 | def total_ask_size(self): 218 | return self._total_ask_size 219 | 220 | @property 221 | def bid_size(self): 222 | """ Volume waiting on bid side top level - liquidity level size for bid price """ 223 | 224 | best_bid = self.get_mkt_depth(1)[1] 225 | if len(best_bid) == 0: 226 | return 0 227 | else: 228 | return best_bid[0][1] 229 | 230 | @property 231 | def total_volume_traded(self): 232 | """ Total traded volume """ 233 | 234 | return self._total_volume_traded 235 | 236 | @property 237 | def total_volume_pending(self): 238 | """ Total size of orders in whole book """ 239 | 240 | return self._total_volume_pending 241 | 242 | @property 243 | def total_bid_size(self): 244 | return self._total_bid_size 245 | 246 | @property 247 | def ask(self): 248 | """ Best ask """ 249 | 250 | try: 251 | return self.get_mkt_depth(1)[0][0][0] 252 | except: 253 | return -1 254 | 255 | @property 256 | def bid(self): 257 | """ Best bid """ 258 | 259 | try: 260 | return self.get_mkt_depth(1)[1][0][0] 261 | except: 262 | return -1 263 | 264 | @property 265 | def spread(self): 266 | """ Difference between ask and bid """ 267 | 268 | return self.ask - self.bid 269 | 270 | def get_participant_orders(self, participant_id): 271 | """ Orders of given participant """ 272 | 273 | orders_list = self._participants.get_value(participant_id) 274 | 275 | order_prices = {} 276 | for order_id in orders_list: 277 | order = self._price_ids.get_value(order_id) 278 | 279 | if order[1] == 'ask': 280 | order_size = self._asks.get_value(order[0]).get_value(order_id) 281 | else: 282 | order_size = self._bids.get_value(order[0]).get_value(order_id) 283 | 284 | # price, side, size 285 | order_prices[order_id] = (order[0], order[1], order_size) 286 | 287 | return orders_list, order_prices 288 | 289 | def submit_order(self, order_type, side, size, price, participant_id): 290 | """ Abstraction on order placement - boht LMT and MKT """ 291 | 292 | if order_type == 'lmt': 293 | order_id = self._submit_lmt(side, size, price, participant_id) 294 | trades = self._balance([]) 295 | return order_id, trades 296 | 297 | if order_type == 'mkt': 298 | second_side_ask = 0 299 | if side != 'ask': 300 | second_side_ask = self._total_ask_size 301 | else: 302 | second_side_ask = self._total_bid_size 303 | 304 | if second_side_ask >= size: 305 | return self._submit_mkt(side, size, participant_id) 306 | else: 307 | # Insufficient liquidity 308 | return -1, [] 309 | 310 | def get_mkt_depth(self, depth): 311 | """ Liquidity levels size for both bid and ask """ 312 | 313 | ask_side = [] 314 | if not self._asks.is_empty(): 315 | for price in self._asks.keys(): 316 | ask_level = self._asks.get(price) 317 | ask_size = 0 318 | for order_id in ask_level.keys(): 319 | # print(ask_size, order_id, ask_level.get(order_id)) 320 | ask_size += ask_level.get(order_id) 321 | 322 | ask_side.append([price, ask_size]) 323 | 324 | if len(ask_side) >= depth: 325 | break 326 | 327 | bid_side = [] 328 | if not self._bids.is_empty(): 329 | for price in self._bids.keys(reverse=True): 330 | bid_level = self._bids.get(price) 331 | bid_size = 0 332 | for order_id in bid_level.keys(): 333 | bid_size += bid_level.get(order_id) 334 | 335 | bid_side.append([price, bid_size]) 336 | 337 | if len(bid_side) >= depth: 338 | break 339 | 340 | return [ask_side, bid_side] 341 | --------------------------------------------------------------------------------