Exemplo n.º 1
0
class RangeList(object):
    """List of ranges.
    
    Ranges are automatically merged and split when a new range is appended
    or removed.
    """
    
    def __init__(self, sequence=None):
        self.tree = RBTree(sequence)
        
    def __iter__(self):
        return iter(self.tree.items())
    
    def __repr__(self):
        return `list(self.tree.items())`
    
    def append(self, start, stop):
        """Append range (start, stop) to the list."""
        
        if start >= stop:
            raise ValueError('stop has to be greater than start')
        
        delete = []
        for nextstart, nextstop in self.tree[start:].items():
            if nextstart <= stop:
                stop = max(nextstop, stop)
                delete.append(nextstart)
            else: break
        for key in delete: del self.tree[key]
        
        left = self.tree[:start]
        try:
            prevstart = max(left)
            prevstop = self.tree[prevstart]
            if prevstop >= start:
                start = prevstart
                stop = max(prevstop, stop)
                del self.tree[prevstart]
        except ValueError: pass
        
        self.tree[start] = stop
        
    def remove(self, start, stop):
        """Remove range (start, stop) from the list."""
        
        delete = []
        for nextstart, nextstop in self.tree[start:].items():
            if nextstart < stop:
                if nextstop > stop: self.tree[stop] = nextstop
                delete.append(nextstart)
            else: break
        for key in delete: del self.tree[key]
        
        left = self.tree[:start]
        try:
            prevstart = max(left)
            prevstop = self.tree[prevstart]
            if prevstop > stop: self.tree[stop] = prevstop
            if prevstop >= start: self.tree[prevstart] = start
        except ValueError: pass
Exemplo n.º 2
0
class Scope(object):
    # Static constants uses for indicating the result of the inserting
    # a symbol into the Scope.
    INSERT_SUCCESS = 0
    INSERT_SHADOWED = 1
    INSERT_REDECL = 2

    # Initializes the Scope with a balanced Red-Black tree.
    def __init__(self):
        self.map = RBTree()

    # Inserts a symbol into the Scope.
    #
    # @return INSERT_SUCCESS or INSERT_REDECL.
    def insert(self, symbol):
        if not isinstance(symbol, Symbol):
            raise TypeError("'symbol' is not an instance of Symbol.")

        if symbol.identifier not in self.map:
            self.map[symbol.identifier] = symbol
            return Scope.INSERT_SUCCESS
        else:
            return Scope.INSERT_REDECL

    # Finds a symbol in the Scope.
    #
    # @param name String identifier for the Symbol to find.
    #
    # @return Symbol if found or None if not found.
    def find(self, name):
        return self.map.get(name, None)

    # Size of the scope.
    #
    # @return Number of Symbols in the Scope.
    def size(self):
        return len(self.map)

    # Clones the current Scope and returns a deep copy.
    #
    # @return Copy of the Scope.
    def clone(self):
        result = Scope()
        for identifier, value in self.map.items():
            result.map[identifier] = value.clone()
        return result

    # @return String representation of the current Scope.
    def __repr__(self):
        symbols = []
        for key, value in self.map.items():
            symbols.append('  ' + key + ' : ' + repr(value))
        return '\n'.join(symbols)
Exemplo n.º 3
0
        return total_traded

    def get_last_price(last_price, trades):
        return trades[len(trades) -
                      1]['price'] if len(trades) > 0 else last_price

    def get_traded_volume(trades):
        return sum([trade['qty'] for trade in trades] + [0])

    # Loop
    for t in range(max_iterations):
        traded_volume = 0
        next_orders = []

        # Limit order cancellations
        for idNum, order in limit_orders.items():
            if t >= order['time_limit']:
                lob.cancelOrder(order['side'], idNum)
                limit_orders.remove(idNum)

        # Limit order arrivals
        df['orders'] = df['lambda'].apply(lambda x: np.random.poisson(x))
        for idx, row in df.iterrows():
            for i in range(int(row['orders'])):

                if row['price'] < ask:
                    side = 'bid'
                    order_id = order_id + 1
                    order = {
                        'type': 'limit',
                        'side': side,
Exemplo n.º 4
0
    tree[16.47] = 2694
    tree[16.5] = 2079
    tree[16.48] = 2599
    tree[16.46] = 2564
    tree[16.44] = 2709
#    tree[16.45] = 2644
#    tree[16.43] = 2621
#    tree[16.49] = 3959
#    tree[16.47] = 3494
#    tree[16.48] = 3399
#    tree[16.46] = 3364
#    tree[16.44] = 3509
#    tree[16.45] = 3444
#    tree[16.43] = 3421
#    tree[16.46] = 3735
#    del tree[15.84]
#    tree[16.43] = 4921
#    tree[16.48] = 4099
#    tree[16.5] = 1279
#    tree[16.49] = 1959
#    tree[16.39] = tree.get(16.39,0) + 2000

rbt = RBTree()
frbt = FastRBTree()
populate(rbt)
populate(frbt)

print('RBT len: {0} FRBT len: {1}'.format(len(rbt), len(frbt)))
for key, value in rbt.items():
    print("RBTree[{key}] = {value} <-> FastRBTree[{key}] = {value2}".format(key=key, value=value, value2=frbt[key]))
Exemplo n.º 5
0
    tree[16.46] = 2564
    tree[16.44] = 2709


#    tree[16.45] = 2644
#    tree[16.43] = 2621
#    tree[16.49] = 3959
#    tree[16.47] = 3494
#    tree[16.48] = 3399
#    tree[16.46] = 3364
#    tree[16.44] = 3509
#    tree[16.45] = 3444
#    tree[16.43] = 3421
#    tree[16.46] = 3735
#    del tree[15.84]
#    tree[16.43] = 4921
#    tree[16.48] = 4099
#    tree[16.5] = 1279
#    tree[16.49] = 1959
#    tree[16.39] = tree.get(16.39,0) + 2000

rbt = RBTree()
frbt = FastRBTree()
populate(rbt)
populate(frbt)

print('RBT len: {0} FRBT len: {1}'.format(len(rbt), len(frbt)))
for key, value in rbt.items():
    print("RBTree[{key}] = {value} <-> FastRBTree[{key}] = {value2}".format(
        key=key, value=value, value2=frbt[key]))
Exemplo n.º 6
0
class OrderBook(object):
    def __init__(self, product_id='BTC-USD', feed=None, log_to=None):
        self._product_id = product_id
        self._asks = RBTree()
        self._bids = RBTree()
        self._sequence = -1
        self._current_ticker = None
        self._feed = feed

    @property
    def product_id(self):
        """ Currently OrderBook only supports a single product even though it is stored as a list of products. """
        return self.product_id

    def reset_book(self, snapshot):
        self._asks = RBTree()
        self._bids = RBTree()
        for bid in snapshot['bids']:
            self._add({
                'id': bid[2],
                'side': 'buy',
                'price': Decimal(bid[0]),
                'size': Decimal(bid[1])
            })
        for ask in snapshot['asks']:
            self._add({
                'id': ask[2],
                'side': 'sell',
                'price': Decimal(ask[0]),
                'size': Decimal(ask[1])
            })
        self._sequence = snapshot['sequence']

    def on_message(self, message):
        sequence = message['sequence']
        if self._sequence == -1:
            logger.error("Expected snapshot before any message")
            sys.exit()
            # self.reset_book()
            return
        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            self.on_sequence_gap(self._sequence, sequence)
            # return

        msg_type = message['type']
        if msg_type == 'open':
            self._add(message)
        elif msg_type == 'done' and 'price' in message:
            self._remove(message)
        elif msg_type == 'match':
            self._match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self._change(message)

        self._sequence = sequence

    def on_sequence_gap(self, gap_start, gap_end):
        # self.reset_book()
        logger.error(
            'Error: messages missing ({} - {}). ignoring the gap.'.format(
                gap_start, gap_end, self._sequence))

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append(
                    [order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)

    @staticmethod
    def meet_min_diff_price(low_price, high_price, min_diff_price):
        if low_price is None or high_price is None or min_diff_price is None:
            return False
        return high_price - low_price > min_diff_price

    @staticmethod
    def meet_max_size(total_size, max_size):
        if total_size is None or max_size is None:
            return False
        return total_size > max_size

    def get_aggr_bids(self, min_diff_price=None, max_size=None):
        max_bid = self.get_bid()
        total_size = 0
        aggr_bids = list()
        for price, bid_orders in reversed(list(self._bids.items())):
            if (OrderBook.meet_min_diff_price(price, max_bid, min_diff_price)
                    and OrderBook.meet_max_size(total_size, max_size)):
                break
            size = sum(bid_order['size'] for bid_order in bid_orders)
            num_orders = len(bid_orders)
            aggr_bids.append({
                'price': price,
                'size': size,
                'num_orders': num_orders
            })
            total_size += size
        return aggr_bids

    def get_aggr_asks(self, min_diff_price=None, max_size=None):
        min_ask = self.get_ask()
        total_size = 0
        aggr_asks = list()
        for price, ask_orders in self._asks.items():
            if (OrderBook.meet_min_diff_price(min_ask, price, min_diff_price)
                    and OrderBook.meet_max_size(total_size, max_size)):
                break
            size = sum(ask_order['size'] for ask_order in ask_orders)
            num_orders = len(ask_orders)
            aggr_asks.append({
                'price': price,
                'size': size,
                'num_orders': num_orders
            })
            total_size += size
        return aggr_asks

    # Internal operations
    def _add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def _remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def _match(self, order):
        if self._feed is not None:
            self._feed._match(order)

        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def _change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError:
            return

        try:
            price = Decimal(order['price'])
        except KeyError:
            return

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return
class MarinerOrderBook(gdax.OrderBook):

    current_milli_time = lambda self: int(time.time() * 1000)

    def __init__(self, ticker, threshold):
        gdax.OrderBook.__init__(self, product_id=ticker)
        self._threshold = threshold
        self.bookChanged = None
        self.whaleEnteredMarket = None
        self.whaleExitedMarket = None

    def registerHandlers(self, bookChangedHandler, whaleEnteredMarketHandler,
                         whaleExitedMarketHandler, whaleChangedHandler):
        Logging.logger.info("registering callbacks...")
        self.bookChanged = bookChangedHandler
        self.whaleEnteredMarket = whaleEnteredMarketHandler
        self.whaleExitedMarket = whaleExitedMarketHandler
        self.whaleChanged = whaleChangedHandler

    def onMessage(self, message):
        #Logging.logger.info(message)
        sequence = message['sequence']
        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.getProductOrderBook(level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': Decimal(bid[0]),
                    'size': Decimal(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': Decimal(ask[0]),
                    'size': Decimal(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            Logging.logger.error(
                'Error: messages missing ({} - {}). Re-initializing websocket.'
                .format(sequence, self._sequence))
            self.close()
            self.start()
            return

        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

        if not self.bookChanged is None:
            self.bookChanged()

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }

        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

        if not self.whaleEnteredMarket == None and self.isWhale(order['size']):
            self.whaleEnteredMarket(order)

    def remove(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

        if not self.whaleExitedMarket == None and self.isWhale(order['size']):
            self.whaleExitedMarket(order)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        price = Decimal(order['price'])
        new_volume = Decimal(order['new_size'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = map(itemgetter('id'), bids).index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = map(itemgetter('id'), asks).index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

        if not self.whaleChanged == None and self.isWhale(order['size']):
            self.whaleChanged(order)

    def get_current_book(self,
                         num_levels=10
                         ):  #fetch only 100 levels off book either way
        book = []

        for index, bid_entry in enumerate(self._bids.items([True])):
            if index == num_levels:
                break
            else:
                try:
                    # There can be a race condition here, where a price point is removed
                    # between these two ops
                    thisBid = bid_entry[1]
                except KeyError:
                    continue

                for order in thisBid:
                    order['price'] = Decimal128(order['price'])
                    order['size'] = Decimal128(order['size'])
                    book.append(order)

        for index, ask_entry in enumerate(self._asks.items()):
            if index == num_levels:
                break
            else:
                try:
                    # There can be a race condition here, where a price point is removed
                    # between these two ops
                    thisAsk = ask_entry[1]
                except KeyError:
                    continue

                for order in thisAsk:
                    order['price'] = Decimal128(order['price'])
                    order['size'] = Decimal128(order['size'])
                    book.append(order)

        book_frame = pd.DataFrame(book)
        return book_frame

    def isWhale(self, aVolume):
        return aVolume >= self._threshold

    #For general book management purposes
    def get_bid(self):
        if not self._bids.is_empty():
            return self._bids.max_key()
        else:
            return None

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)

    def get_ask(self):
        if not self._asks.is_empty():
            return self._asks.min_key()
        else:
            return None

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)
class WhaleTracker():

    current_milli_time = lambda self: int(time.time() * 1000)

    def __init__(self, ticker):
        self._ticker = ticker
        self._bid_whales = RBTree()
        self._ask_whales = RBTree()

    def whaleEnteredMarketHandler(self, order):
        if order['side'] == 'buy':
            self.addBidWhale(order)
        else:
            self.addAskWhale(order)

    def whaleExitedMarketHandler(self, order):
        if order['side'] == 'buy':
            self.removeBidWhale(order)
        else:
            self.removeAskWhale(order)

    def whaleChangedHandler(self, order):
        if order['side'] == 'buy':
            self.changeBidWhale(order)
        else:
            self.changeAskWhale(order)

    def addBidWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        bid_whale_order = self.get_bid_whale(price)
        if bid_whale_order is None or volume > bid_whale_order.get_volume():
            #Logging.logger.info("NEW WHALE ENTERED (BID): price=" + str(price) + " volume=" + str(volume))
            bid_whale_order = WhaleOrder(ID, price, volume)
            self.set_bid_whale(price, bid_whale_order)

    def addAskWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        ask_whale_order = self.get_ask_whale(price)
        if ask_whale_order is None or volume > ask_whale_order.get_volume():
            #Logging.logger.info("NEW WHALE ENTERED (ASK): price=" + str(price) + " volume=" + str(volume))
            ask_whale_order = WhaleOrder(ID, price, volume)
            self.set_ask_whale(price, ask_whale_order)

    def removeBidWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        bid_whale_order = self.get_bid_whale(price)
        if bid_whale_order is not None and bid_whale_order.get_id() == ID:
            #Logging.logger.info("WHALE LEFT (BID): price=" + str(price) + " volume=" + str(volume))
            self.remove_bid_whale(price)

    def removeAskWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        ask_whale_order = self.get_ask_whale(price)
        if ask_whale_order is not None and ask_whale_order.get_id() == ID:
            #Logging.logger.info("WHALE LEFT (ASK): price=" + str(price) + " volume=" + str(volume))
            self.remove_ask_whale(price)

    def changeBidWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        new_volume = order['new_size']
        bid_whale_order = self.get_bid_whale(price)
        if not bid_whale_order == None:
            #Logger.logging.info("WHALE CHANGED (BID): price=" + price + " new_volume=" + new_volume)
            if self.isWhale(new_volume):
                bid_whale_order.setVolume(new_volume)
                self.set_bid_whale(price, bid_whale_order)
            else:
                self.removeBidWhale(order)

    def changeAskWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        new_volume = order['new_size']
        ask_whale_order = self.get_ask_whale(price)
        if not ask_whale_order == None:
            #Logger.logging.info("WHALE CHANGED (ASK): price=" + price + " new_volume=" + new_volume)
            if self.isWhale(new_volume):
                ask_whale_order.setVolume(new_volume)
                self.set_ask_whale(price, ask_whale_order)
            else:
                self.removeAskWhale(order)

    def get_current_whales(
        self,
        num_whales=30
    ):  #by default, fetch only 30 whales off the book either way
        whales = []

        for index, bid_whale in self._bid_whales.items(True):
            if index == num_whales:
                break
            else:
                whales.append({
                    'price': Decimal128(bid_whale.get_price()),
                    'volume': Decimal128(bid_whale.get_volume()),
                    'id': bid_whale.get_id(),
                })

        for index, ask_whale in self._ask_whales.items():
            if index == num_whales:
                break
            else:
                whales.append({
                    'price': Decimal128(ask_whale.get_price()),
                    'volume': Decimal128(ask_whale.get_volume()),
                    'id': ask_whale.get_id(),
                })

        whales_frame = pd.DataFrame(whales)
        return whales_frame

    def get_top_bid_whale(self):
        if not self._bid_whales.is_empty():
            top_bid = self._bid_whales.max_key()
            return self.get_bid_whale(top_bid)
        else:
            return None

    def get_bid_whale(self, price):
        return self._bid_whales.get(price)

    def remove_bid_whale(self, price):
        self._bid_whales.remove(price)

    def set_bid_whale(self, price, whale):
        self._bid_whales.insert(price, whale)

    def get_top_ask_whale(self):
        if not self._ask_whales.is_empty():
            top_ask = self._ask_whales.min_key()
            return self.get_ask_whale(top_ask)
        else:
            return None

    def get_ask_whale(self, price):
        return self._ask_whales.get(price)

    def remove_ask_whale(self, price):
        self._ask_whales.remove(price)

    def set_ask_whale(self, price, whale):
        self._ask_whales.insert(price, whale)