class OrderBook(object): """ Uses RBTrees to handle all types of orders and store them in their corresponding bucket """ def __init__(self, product_id: str): self._asks = RBTree() self._bids = RBTree() self._product_id = product_id @property def product_id(self): return self._product_id def process_snapshot(self, message: Dict): """ Process a snapshot message :param message: json """ # If a snapshot is sent reset trees self._asks = RBTree() self._bids = RBTree() # Parse all asks and add them to tree for ask in message['asks']: price, size = ask price = Decimal(price) size = Decimal(size) self._asks.insert(price, size) # Parse all bids and add them to tree for bid in message['bids']: price, size = bid price = Decimal(price) size = Decimal(size) self._bids.insert(price, size) def process_update(self, message: Dict): """ Process a update message :param message: json """ # Retrieve changes changes = message['changes'] for change in changes: side, price, size = change # parse numbers and keep precision price = Decimal(price) size = Decimal(size) if side == 'buy': # If it is equal to 0 (or less than) the order no longer exists if size <= 0: self._bids.remove(price) else: self._bids.insert(price, size) elif side == 'sell': # If it is equal to 0 (or less than) the order no longer exists if size <= 0: self._asks.remove(price) else: self._asks.insert(price, size) def process_message(self, message: Dict): """ Process all messages to identify next parser location :param message: json """ # Read type msg_type = message['type'] # dropped - not same product id if message.get('product_id', None) != self._product_id: return if msg_type == 'snapshot': self.process_snapshot(message) elif msg_type == 'l2update': self.process_update(message) def get_asks(self) -> List[Tuple[float, float]]: """ Provides a list of asks and sizes in order of best price for the buyer :return: a list of Tuple's corresponding to ask (price rate), and ask size """ asks = [] for ask in self._asks: try: size = self._asks[ask] except KeyError: continue asks.append([float(ask), float(size)]) return asks def get_bids(self) -> List[Tuple[float, float]]: """ Provides a list of bids and sizes in order of best price for the seller :return: a list of Tuple's corresponding to ask (price rate), and ask size """ bids = [] for bid in self._bids: try: size = self._bids[bid] except KeyError: continue # For bids the best value (for selling) is reversed so inserting at the beginning flips the order bids.insert(0, [float(bid), float(size)]) return bids def get_orders(self) -> Dict[str, List[Tuple[float, float]]]: """ Uses get_bids and get_asks to compile all orders :return: both bids and asks """ return {'asks': self.get_asks(), 'bids': self.get_bids()} def get_ask(self) -> Tuple[Decimal, Decimal]: """ Get the best asking price. If it does not exist it returns a size of 0 :return: the rate, and the size """ price = self._asks.min_key() try: size = self._asks[price] except KeyError: return price, Decimal(0) return price, size def get_bid(self) -> Tuple[Decimal, Decimal]: """ Get the best bid price. If it does not exist it returns a size of 0 :return: the rate, and the size """ price = self._bids.max_key() try: size = self._bids[price] except KeyError: return price, Decimal(0) return price, size
class LOBTree: def __init__(self): ''' Limit order book tree implementation using Red-Black tree for self-balancing Each limit price level is a OrderLinkedlist, and each order contains information including id, price, timestamp, volume self.limit_level: dict key: price level; value: OrderLinkedlist object self.order_ids: dict key: order id; value: Order object helps to locate order by id ''' # tree that store price as keys and number of orders on that level as values self.price_tree = FastRBTree() self.max_price = None self.min_price = None self.limit_levels = {} self.order_ids = {} @property def max(self): return self.max_price @property def min(self): return self.min_price def _get_price(self, price): ''' price: int :return: OrderLinkedlist instance ''' return self.limit_levels[price] def insert_order(self, order: Order): ''' order: Order Instance If order price doesn't exist in the self.limit_levels, insert it into the price level, else update accordingly; Will be used as limit order submission :return: None ''' if order.id in self.order_ids: raise ValueError('order already exists in the book') return if order.price not in self.limit_levels: new_price_level = OrderLinkedlist() self.price_tree[order.price] = 1 self.limit_levels[order.price] = new_price_level self.limit_levels[order.price].set_head(order) self.limit_levels[order.price].size += order.size self.order_ids[order.id] = order if self.max_price is None or order.price > self.max_price: self.max_price = order.price if self.min_price is None or order.price < self.min_price: self.min_price = order.price else: self.limit_levels[order.price].set_head(order) self.limit_levels[order.price].size += order.size self.order_ids[order.id] = order self.price_tree[order.price] += 1 def update_existing_order_size(self, order_id: int, updated_size: int): ''' order_id: int size: int Update an existing order's size in a price level and its price level's overall size :return: None ''' delta = self.order_ids[order_id].size - updated_size try: self.order_ids[order_id].size = updated_size order_price = self.order_ids[order_id].price # updated order will be put at the front of the list self.limit_levels[order_price].set_head(self.order_ids[order_id]) self.limit_levels[order_price].size -= delta except Exception as e: LOG.info('Order is not in the book') def remove_order(self, order_id: int): ''' order: Order Instance Remove the order from the self.order_ids first, then remove it from the self.limit_levels; if the limit_levels is empty after removal, adjust the max_price and min_price accordingly from the self.price_tree :return: Order Instance | order removed from the book ''' popped = self.order_ids.pop(order_id) self.limit_levels[popped.price].remove(popped, decrement=True) self.price_tree[popped.price] -= 1 if self.limit_levels[popped.price].size == 0: self._remove_price_level(popped.price) return popped def _remove_price_level(self, price: int): ''' order: Order Instance Given a price level, remove the price level in the price_tree and limit_levels reset the max and min prices ''' del self.limit_levels[price] self.price_tree.remove(price) if self.max_price == price: try: self.max_price = self.price_tree.max_key() except KeyError or ValueError: self.max_price = None if self.min_price == price: try: self.min_price = self.price_tree.min_key() except KeyError or ValueError: self.min_price = None def market_order(self, order: Order): ''' order: Order Instance ''' if len(self.limit_levels) == 0: raise ValueError('No orders in the book') return if order.is_bid: best_price = self.min_price while order.size > 0 and best_price != None: price_level = self._get_price(best_price) order.size, number_of_orders_deleted = price_level._consume_orders( order, self.order_ids) self.price_tree[best_price] -= number_of_orders_deleted if price_level._head == None: self._remove_price_level(best_price) best_price = self.min_price if order.size != 0: LOG.warning('no more limit orders in the bid book') else: best_price = self.max_price while order.size > 0 and best_price != None: price_level = self._get_price(best_price) order.size, number_of_orders_deleted = price_level._consume_orders( order, self.order_ids) self.price_tree[best_price] -= number_of_orders_deleted if price_level._head == None: self._remove_price_level(best_price) best_price = self.max_price if order.size != 0: LOG.warning('no more orders in the ask book') def level_with_most_orders(self, range: int): ''' range: int Gives the price level with the most orders on the top levels ''' pass def iceberg(self): ''' Iceberg order type ''' pass
class TradeTree(object): '''A red-black tree used to store TradeLists in price trade The exchange will be using the TradeTree to hold bid and ask data (one TradeTree for each side). Keeping the information in a red black tree makes it easier/faster to detect a match. ''' def __init__(self): self.price_tree = FastRBTree() self.trade_map = {} self.num_trades = 0 # Contains count of Orders in tree self.depth = 0 # Number of different prices in tree (http://en.wikipedia.org/wiki/trade_book_(trading)#Book_depth) def __len__(self): return len(self.trade_map) def get_price_list(self, price): return self.price_tree.get(price, []) def get_trade(self, trade_id): return self.trade_map[trade_id] if trade_id in self.trade_map else None def create_price(self, price): self.depth += 1 # Add a price depth level to the tree new_list = LinkedList() self.price_tree.insert(price, new_list) # Insert a new price into the tree def remove_price(self, price): self.depth -= 1 # Remove a price depth level self.price_tree.remove(price) def price_exists(self, price): return self.price_tree.__contains__(price) def trade_exists(self, trade_id): return trade_id in self.trade_map def insert_trade(self, xtrade): if self.trade_exists(xtrade.id): return self.num_trades += 1 if not self.price_exists(xtrade.limit_price): self.create_price( xtrade.limit_price ) # If price not in Price Map, create a node in RBtree self.trade_map[ trade.id] = self.price_tree[xtrade.limit_price].append_item( xtrade ) # Add the trade to the TradeList in Price Map return the reference def remove_trade(self, xtrade): self.num_trades -= 1 trade_node = self.trade_map[trade.id] self.price_tree[trade.limit_price].remove_item(trade_node) if len(self.price_tree[trade.limit_price]) == 0: self.remove_price(trade.limit_price) self.trade_map.pop(trade.id, None) def max_price(self): if self.depth > 0: return self.price_tree.max_key() else: return None def min_price(self): if self.depth > 0: return self.price_tree.min_key() else: return None def max_price_list(self): if self.depth > 0: return self.get_price_list(self.max_price()) else: return None def min_price_list(self): if self.depth > 0: return self.get_price_list(self.min_price()) else: return None
class BidBook(object): """ A BidBook is used to store the order book's rates and amounts on the bid side with a defined depth. To maintain a sorted order of rates, the BidBook uses a red-black tree to store rates and corresponding amounts. For O(1) query of volume at a predetermined rate, the BidBook also uses a dictionary to store rate and amount. """ def __init__(self, max_depth, data): # RBTree: maintains sorted order of rates # every value inserted to RBTree must be a tuple, so we hard code the second value to be 0 self.rate_tree = FastRBTree() # dict: Uses rate and amount for key value pairs self.rate_dict = {} # float: amounts summed across all rate levels in tree self.volume = 0 # int: total number of rate levels in tree self.depth = len(data) # int: maximum number of rate levels in tree self.max_depth = max_depth # populate rate_tree and rate_dict from public API call data # set volume for level in data: rate = float(level[0]) amount = float(level[1]) self.rate_tree.insert(rate, 0) self.rate_dict[rate] = amount self.volume += amount def __len__(self): return len(self.rate_dict) def rate_exists(self, rate): return rate in self.rate_dict def get_amount_at_rate(self, rate): return self.rate_dict.get(rate) def max_rate_level(self): if self.depth > 0: rate = self.rate_tree.max_key() amount = self.get_amount_at_rate(rate) return rate, amount else: return None def min_rate_level(self): if self.depth > 0: rate = self.rate_tree.min_key() amount = self.get_amount_at_rate(rate) return rate, amount else: return None def modify(self, event): # if the event's rate is already in the book, just modify the amount at the event's rate rate = float(event[u'data'][u'rate']) amount = float(event[u'data'][u'amount']) if self.rate_exists(rate): # print '~~~~~~~~~~~~~~~~~~~~~~ BID MODIFY ~~~~~~~~~~~~~~~~~~~~~~' self.rate_dict[rate] = amount # only rates not already in the book reach this logic # if the max depth hasn't been reached, just insert the event's rate and amount elif self.depth < self.max_depth: # print '~~~~~~~~~~~~~~~~~~~~~~ BID MODIFY ~~~~~~~~~~~~~~~~~~~~~~' self.rate_tree.insert(rate, 0) self.rate_dict[rate] = amount self.depth += 1 # only events being handled by a full order tree reach this logic # if the event is a bid and the rate is greater than min rate, effectively replace min rate level with event else: min_rate = self.min_rate_level()[0] if rate > min_rate: # print '~~~~~~~~~~~~~~~~~~~~~~ BID MODIFY ~~~~~~~~~~~~~~~~~~~~~~' self.rate_tree.remove(min_rate) del self.rate_dict[min_rate] self.rate_tree.insert(rate, 0) self.rate_dict[rate] = amount def remove(self, event): # if the event's rate is in the book, delete it rate = float(event[u'data'][u'rate']) if self.rate_exists(rate): # print '~~~~~~~~~~~~~~~~~~~~~~ BID REMOVE ~~~~~~~~~~~~~~~~~~~~~~' self.rate_tree.remove(rate) del self.rate_dict[rate] self.depth -= 1 def __str__(self): rate_tree_str = '[' + ','.join(rate[0] for rate in self.rate_tree) + ']' return 'BIDS: ' + rate_tree_str
cur[ 0 ] = 0 i = 0 for elem in newInput: v = elem[ 2 ] w = elem[ 1 ] #for line in fin: #info = line.split() #v = int( info[ 0 ] ) #w = int( info[ 1 ] ) #print newItems - i, (v,w), len( prev )#, len( testSet ) i += 1 for stepWeight in prev: step = [ stepWeight, prev[ stepWeight ] ] curv = cur.floor_item( step[ 0 ] )[ 1 ] maxv = max( step[ 1 ], curv ) # compare prev and cur on same weight if maxv == step[ 1 ]: cur[ step[ 0 ] ] = maxv nextw = step[ 0 ] + w # using step weight as base, compare value of # prev val( step weight ) + item val --> with current item # and prev val( step weight + item weight ) --> without current item if nextw < size and prev.floor_item( nextw )[ 1 ] < step[ 1 ] + v: cur[ nextw ] = step[ 1 ] + v prev = cur cur = FastRBTree() cur[ 0 ] = 0 print prev[ prev.max_key() ]
class PriceTree(object): def __init__(self, name): self.tree = FastRBTree() self.name = name self.price_map = {} # Map price -> OrderList self.order_map = {} # Map order_id -> Order self.min_price = None self.max_price = None def insert_price(self, price): """ Add a new price TreedNode and associate it with an orderList :param price: :return: """ new_list = OrderList() self.tree.insert(price, new_list) self.price_map[price] = new_list if self.max_price is None or price > self.max_price: self.max_price = price if self.min_price is None or price < self.min_price: self.min_price = price def remove_price(self, price): """ Remove price from the tree structure and the associated orderList Update min and max prices if needed :param price: :return: """ self.tree.remove(price) # Order-map will still contain all Orders emptied (with size 0) # as we delete them on the List match_order which is fine for now for to_del_order in self.price_map[price]: del self.order_map[to_del_order.id] # Delete the price from the price-map del self.price_map[price] if self.max_price == price: try: self.max_price = self.tree.max_key() except ValueError: self.max_price = None if self.min_price == price: try: self.min_price = self.tree.min_key() except ValueError: self.min_price = None def insert_price_order(self, order): if order.price not in self.price_map: self.insert_price(order.price) # Add order to orderList self.price_map[order.price].add(order) # Also keep it in the order mapping self.order_map[order.id] = order def match_price_order(self, curr_order): if len(self.price_map) == 0: return [] # if bid -> sell_tree min # if ask -> buy_tree max best_price = self.min if curr_order.is_bid else self.max complete_trades = [] while ((curr_order.is_bid and curr_order.price >= best_price) or (not curr_order.is_bid and curr_order.price <= best_price)) \ and curr_order.peak_size > 0: # Get price OrderList matching_orders_list = self.get_price(best_price) complete_trades.extend( matching_orders_list.match_order(curr_order, self.order_map)) # Remove exhausted price if matching_orders_list.size == 0: self.remove_price(best_price) if len(self.price_map) == 0: break # Try to find more price matches using the next price best_price = self.min if curr_order.is_bid else self.max return complete_trades def price_exists(self, price): return price in self.price_map def order_exists(self, id_num): return id_num in self.order_map def get_price(self, price): return self.price_map[price] def get_order(self, id_num): return self.order_map[id_num] @property def max(self): return self.max_price @property def min(self): return self.min_price