Esempio n. 1
0
    def __init__(self):
        super(OrderBook, self).__init__()

        self._logger = logging.getLogger(self.__class__.__name__)
        self._bids = Side()
        self._asks = Side()
        self.completed_orders = set()
Esempio n. 2
0
    def setUp(self):
        # Object creation

        self.tick = Tick(OrderId(TraderId(b'0' * 20), OrderNumber(1)),
                         AssetPair(AssetAmount(60, 'BTC'), AssetAmount(30, 'MB')),
                         Timeout(100), Timestamp.now(), True)
        self.tick2 = Tick(OrderId(TraderId(b'1' * 20), OrderNumber(2)),
                          AssetPair(AssetAmount(120, 'BTC'), AssetAmount(30, 'MB')),
                          Timeout(100), Timestamp.now(), True)
        self.side = Side()
Esempio n. 3
0
class SideTestSuite(unittest.TestCase):
    """Side test cases."""

    def setUp(self):
        # Object creation

        self.tick = Tick(OrderId(TraderId(b'0' * 20), OrderNumber(1)),
                         AssetPair(AssetAmount(60, 'BTC'), AssetAmount(30, 'MB')),
                         Timeout(100), Timestamp.now(), True)
        self.tick2 = Tick(OrderId(TraderId(b'1' * 20), OrderNumber(2)),
                          AssetPair(AssetAmount(120, 'BTC'), AssetAmount(30, 'MB')),
                          Timeout(100), Timestamp.now(), True)
        self.side = Side()

    def test_max_price(self):
        # Test max price (list)
        self.assertEqual(None, self.side.get_max_price('MB', 'BTC'))
        self.assertEqual(None, self.side.get_max_price_list('MB', 'BTC'))

        self.side.insert_tick(self.tick)
        self.side.insert_tick(self.tick2)

        self.assertEqual(Price(1, 2, 'MB', 'BTC'), self.side.get_max_price('MB', 'BTC'))

    def test_min_price(self):
        # Test min price (list)
        self.assertEqual(None, self.side.get_min_price_list('MB', 'BTC'))
        self.assertEqual(None, self.side.get_min_price('MB', 'BTC'))

        self.side.insert_tick(self.tick)
        self.side.insert_tick(self.tick2)

        self.assertEqual(Price(1, 4, 'MB', 'BTC'), self.side.get_min_price('MB', 'BTC'))

    def test_insert_tick(self):
        # Test insert tick
        self.assertEqual(0, len(self.side))
        self.assertFalse(self.side.tick_exists(OrderId(TraderId(b'0' * 20), OrderNumber(1))))

        self.side.insert_tick(self.tick)
        self.side.insert_tick(self.tick2)

        self.assertEqual(2, len(self.side))
        self.assertTrue(self.side.tick_exists(OrderId(TraderId(b'0' * 20), OrderNumber(1))))

    def test_remove_tick(self):
        # Test remove tick
        self.side.insert_tick(self.tick)
        self.side.insert_tick(self.tick2)

        self.side.remove_tick(OrderId(TraderId(b'0' * 20), OrderNumber(1)))
        self.assertEqual(1, len(self.side))
        self.side.remove_tick(OrderId(TraderId(b'1' * 20), OrderNumber(2)))
        self.assertEqual(0, len(self.side))

    def test_get_price_level_list_wallets(self):
        """
        Test the price level lists of wallets of a side
        """
        self.assertFalse(self.side.get_price_level_list_wallets())
        self.side.insert_tick(self.tick)
        self.assertTrue(self.side.get_price_level_list_wallets())

    def test_get_list_representation(self):
        """
        Testing the list representation of a side
        """
        self.assertFalse(self.side.get_list_representation())
        self.side.insert_tick(self.tick)

        list_rep = self.side.get_list_representation()
        self.assertTrue(list_rep)
Esempio n. 4
0
class OrderBook(TaskManager):
    """
    OrderBook is used for searching through all the orders and giving an indication to the user of what other offers
    are out there.
    """

    def __init__(self):
        super(OrderBook, self).__init__()

        self._logger = logging.getLogger(self.__class__.__name__)
        self._bids = Side()
        self._asks = Side()
        self.completed_orders = set()

    def timeout_ask(self, order_id):
        ask = self.get_ask(order_id).tick
        self.remove_tick(order_id)
        return ask

    def timeout_bid(self, order_id):
        bid = self.get_bid(order_id).tick
        self.remove_tick(order_id)
        return bid

    def on_invalid_tick_insert(self):
        self._logger.warning("Invalid tick inserted in order book.")

    def insert_ask(self, ask):
        """
        :type ask: Ask
        """
        if not self._asks.tick_exists(ask.order_id) and ask.order_id not in self.completed_orders and ask.is_valid():
            self._asks.insert_tick(ask)
            delay = int(ask.timestamp) + int(ask.timeout) * 1000 - int(time.time() * 1000)
            return self.register_task("ask_%s_timeout" % ask.order_id, self.timeout_ask, ask.order_id, delay=delay)
        self.on_invalid_tick_insert()
        return fail(RuntimeError("ask invalid"))

    def remove_ask(self, order_id):
        """
        :type order_id: OrderId
        """
        if self._asks.tick_exists(order_id):
            self.cancel_pending_task("ask_%s_timeout" % order_id)
            self._asks.remove_tick(order_id)

    def insert_bid(self, bid):
        """
        :type bid: Bid
        """
        if not self._bids.tick_exists(bid.order_id) and bid.order_id not in self.completed_orders and bid.is_valid():
            self._bids.insert_tick(bid)
            delay = int(bid.timestamp) + int(bid.timeout) * 1000 - int(time.time() * 1000)
            return self.register_task("bid_%s_timeout" % bid.order_id, self.timeout_bid, bid.order_id, delay=delay)
        self.on_invalid_tick_insert()
        return fail(RuntimeError("bid invalid"))

    def remove_bid(self, order_id):
        """
        :type order_id: OrderId
        """
        if self._bids.tick_exists(order_id):
            self.cancel_pending_task("bid_%s_timeout" % order_id)
            self._bids.remove_tick(order_id)

    def update_ticks(self, ask_order_dict, bid_order_dict, traded_quantity):
        """
        Update ticks according to a TrustChain block containing the status of the ask/bid orders.

        :type ask_order_dict: dict
        :type bid_order_dict: dict
        :type traded_quantity: int
        """
        ask_order_id = OrderId(TraderId(unhexlify(ask_order_dict["trader_id"])),
                               OrderNumber(ask_order_dict["order_number"]))
        bid_order_id = OrderId(TraderId(unhexlify(bid_order_dict["trader_id"])),
                               OrderNumber(bid_order_dict["order_number"]))

        self._logger.debug("Updating ticks in order book: %s and %s (traded quantity: %s)",
                           str(ask_order_id), str(bid_order_id), str(traded_quantity))

        # Update ask tick
        ask_exists = self.tick_exists(ask_order_id)
        if ask_exists and ask_order_dict["traded"] >= self.get_tick(ask_order_id).traded:
            tick = self.get_tick(ask_order_id)
            tick.traded = ask_order_dict["traded"]
            if tick.traded >= tick.assets.first.amount:
                self.remove_tick(tick.order_id)
                self.completed_orders.add(tick.order_id)
        elif not ask_exists and ask_order_dict["traded"] < ask_order_dict["assets"]["first"]["amount"] and \
                ask_order_id not in self.completed_orders:
            new_pair = AssetPair.from_dictionary(ask_order_dict["assets"])
            ask = Ask(ask_order_id, new_pair, Timeout(ask_order_dict["timeout"]),
                      Timestamp(ask_order_dict["timestamp"]), traded=ask_order_dict["traded"])
            self.insert_ask(ask)
        elif not ask_exists and ask_order_dict["traded"] >= ask_order_dict["assets"]["first"]["amount"]:
            self.completed_orders.add(ask_order_id)

        # Update bid tick
        bid_exists = self.tick_exists(bid_order_id)
        if bid_exists and bid_order_dict["traded"] >= self.get_tick(bid_order_id).traded:
            tick = self.get_tick(bid_order_id)
            tick.traded = bid_order_dict["traded"]
            if tick.traded >= tick.assets.first.amount:
                self.remove_tick(tick.order_id)
                self.completed_orders.add(tick.order_id)
        elif not bid_exists and bid_order_dict["traded"] < bid_order_dict["assets"]["first"]["amount"] and \
                bid_order_id not in self.completed_orders:
            new_pair = AssetPair.from_dictionary(bid_order_dict["assets"])
            bid = Bid(bid_order_id, new_pair, Timeout(bid_order_dict["timeout"]),
                      Timestamp(bid_order_dict["timestamp"]), traded=bid_order_dict["traded"])
            self.insert_bid(bid)
        elif not bid_exists and bid_order_dict["traded"] >= bid_order_dict["assets"]["first"]["amount"]:
            self.completed_orders.add(bid_order_id)

    def tick_exists(self, order_id):
        """
        :param order_id: The order id to search for
        :type order_id: OrderId
        :return: True if the tick exists, False otherwise
        :rtype: bool
        """
        is_ask = self._asks.tick_exists(order_id)
        is_bid = self._bids.tick_exists(order_id)

        return is_ask or is_bid

    def get_ask(self, order_id):
        """
        :param order_id: The order id to search for
        :type order_id: OrderId
        :rtype: TickEntry
        """
        return self._asks.get_tick(order_id)

    def get_bid(self, order_id):
        """
        :param order_id: The order id to search for
        :type order_id: OrderId
        :rtype: TickEntry
        """
        return self._bids.get_tick(order_id)

    def get_tick(self, order_id):
        """
        Return a tick with the specified order id.
        :param order_id: The order id to search for
        :type order_id: OrderId
        :rtype: TickEntry
        """
        return self._bids.get_tick(order_id) or self._asks.get_tick(order_id)

    def ask_exists(self, order_id):
        """
        :param order_id: The order id to search for
        :type order_id: OrderId
        :return: True if the ask exists, False otherwise
        :rtype: bool
        """
        return self._asks.tick_exists(order_id)

    def bid_exists(self, order_id):
        """
        :param order_id: The order id to search for
        :type order_id: OrderId
        :return: True if the bid exists, False otherwise
        :rtype: bool
        """
        return self._bids.tick_exists(order_id)

    def remove_tick(self, order_id):
        """
        :type order_id: OrderId
        """
        self._logger.debug("Removing tick %s from order book", order_id)

        self.remove_ask(order_id)
        self.remove_bid(order_id)

    @property
    def asks(self):
        """
        Return the asks side
        :rtype: Side
        """
        return self._asks

    @property
    def bids(self):
        """
        Return the bids side
        :rtype: Side
        """
        return self._bids

    def get_bid_price(self, price_wallet_id, quantity_wallet_id):
        """
        Return the price an ask needs to have to make a trade
        :rtype: Price
        """
        return self._bids.get_max_price(price_wallet_id, quantity_wallet_id)

    def get_ask_price(self, price_wallet_id, quantity_wallet_id):
        """
        Return the price a bid needs to have to make a trade
        :rtype: Price
        """
        return self._asks.get_min_price(price_wallet_id, quantity_wallet_id)

    def get_bid_ask_spread(self, price_wallet_id, quantity_wallet_id):
        """
        Return the spread between the bid and the ask price
        :rtype: Price
        """
        spread = self.get_ask_price(price_wallet_id, quantity_wallet_id).frac - \
                 self.get_bid_price(price_wallet_id, quantity_wallet_id).frac
        return Price(spread.numerator, spread.denominator, price_wallet_id, quantity_wallet_id)

    def bid_side_depth(self, price):
        """
        Return the depth of the price level with the given price on the bid side

        :param price: The price for the price level
        :type price: Price
        :return: The depth at that price level
        :rtype: Quantity
        """
        return self._bids.get_price_level(price).depth

    def ask_side_depth(self, price):
        """
        Return the depth of the price level with the given price on the ask side

        :param price: The price for the price level
        :type price: Price
        :return: The depth at that price level
        :rtype: Quantity
        """
        return self._asks.get_price_level(price).depth

    def get_bid_side_depth_profile(self, price_wallet_id, quantity_wallet_id):
        """
        format: [(<price>, <depth>), (<price>, <depth>), ...]

        :return: The depth profile
        :rtype: list
        """
        profile = []
        for price_level in self._bids.get_price_level_list(price_wallet_id, quantity_wallet_id).items():
            profile.append((price_level.price, price_level.depth))
        return profile

    def get_ask_side_depth_profile(self, price_wallet_id, quantity_wallet_id):
        """
        format: [(<price>, <depth>), (<price>, <depth>), ...]

        :return: The depth profile
        :rtype: list
        """
        profile = []
        for price_level in self._asks.get_price_level_list(price_wallet_id, quantity_wallet_id).items():
            profile.append((price_level.price, price_level.depth))
        return profile

    def get_bid_price_level(self, price_wallet_id, quantity_wallet_id):
        """
        Return the price level that an ask has to match to make a trade
        :rtype: PriceLevel
        """
        return self._bids.get_max_price_list(price_wallet_id, quantity_wallet_id)

    def get_ask_price_level(self, price_wallet_id, quantity_wallet_id):
        """
        Return the price level that a bid has to match to make a trade
        :rtype: PriceLevel
        """
        return self._asks.get_min_price_list(price_wallet_id, quantity_wallet_id)

    def get_order_ids(self):
        """
        Return all IDs of the orders in the orderbook, both asks and bids.

        :rtype: [OrderId]
        """
        return self.get_bid_ids() + self.get_ask_ids()

    def get_ask_ids(self):
        ids = []

        for price_wallet_id, quantity_wallet_id in self.asks.get_price_level_list_wallets():
            for price_level in self.asks.get_price_level_list(price_wallet_id, quantity_wallet_id).items():
                for ask in price_level:
                    ids.append(ask.tick.order_id)

        return ids

    def get_bid_ids(self):
        ids = []

        for price_wallet_id, quantity_wallet_id in self.bids.get_price_level_list_wallets():
            for price_level in self.bids.get_price_level_list(price_wallet_id, quantity_wallet_id).items():
                for bid in price_level:
                    ids.append(bid.tick.order_id)

        return ids

    def __str__(self):
        res_str = ''
        res_str += "------ Bids -------\n"
        for price_wallet_id, quantity_wallet_id in self.bids.get_price_level_list_wallets():
            for price_level in self._bids.get_price_level_list(price_wallet_id, quantity_wallet_id).items(reverse=True):
                res_str += '%s' % price_level
        res_str += "\n------ Asks -------\n"
        for price_wallet_id, quantity_wallet_id in self.asks.get_price_level_list_wallets():
            for price_level in self._asks.get_price_level_list(price_wallet_id, quantity_wallet_id).items():
                res_str += '%s' % price_level
        res_str += "\n"
        return res_str

    def cancel_all_pending_tasks(self):
        tasks = super(OrderBook, self).cancel_all_pending_tasks()
        for order_id in self.get_order_ids():
            tasks.extend(self.get_tick(order_id).cancel_all_pending_tasks())
        return tasks