def test_order_book_trades(self):
        start: int = int(self.snapshot_time)
        end: int = int(self.snapshot_time) + 86400
        self.clock.backtest_til(start)

        event_recorder: EventLogger = EventLogger()
        self.order_book.add_listener(OrderBookEvent.TradeEvent, event_recorder)
        self.clock.backtest_til(end)

        trade_messages: List[OrderBookMessage] = [
            message
            for message in self.order_book_loader.fetch_order_book_messages(start, end)
            if message.type is OrderBookMessageType.TRADE
        ]

        events: List[OrderBookTradeEvent] = event_recorder.event_log
        self.assertGreater(len(trade_messages), 0)
        self.assertEqual(len(trade_messages), len(events))
        for trade_message, trade_event in zip(trade_messages, events):
            self.assertAlmostEqual(trade_message.timestamp, trade_event.timestamp)
            self.assertAlmostEqual(float(trade_message.content["price"]), trade_event.price)
            self.assertAlmostEqual(float(trade_message.content["amount"]), trade_event.amount)
class RadarRelayMarketUnitTest(unittest.TestCase):
    market_events: List[MarketEvent] = [
        MarketEvent.ReceivedAsset,
        MarketEvent.BuyOrderCompleted,
        MarketEvent.SellOrderCompleted,
        MarketEvent.BuyOrderCreated,
        MarketEvent.SellOrderCreated,
        MarketEvent.OrderCancelled,
        MarketEvent.OrderExpired,
        MarketEvent.OrderFilled,
        MarketEvent.WithdrawAsset
    ]

    wallet_events: List[WalletEvent] = [
        WalletEvent.WrappedEth,
        WalletEvent.UnwrappedEth
    ]

    wallet: Web3Wallet
    market: RadarRelayMarket
    market_logger: EventLogger
    wallet_logger: EventLogger

    @classmethod
    def setUpClass(cls):
        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.wallet = Web3Wallet(private_key=conf.web3_private_key_radar,
                                backend_urls=conf.test_web3_provider_list,
                                erc20_token_addresses=[conf.mn_zerox_token_address, conf.mn_weth_token_address],
                                chain=EthereumChain.MAIN_NET)
        cls.market: RadarRelayMarket = RadarRelayMarket(wallet=cls.wallet,
                                                        web3_url=conf.test_web3_provider_list[0],
                                                        order_book_tracker_data_source_type=
                                                            OrderBookTrackerDataSourceType.EXCHANGE_API,
                                                        symbols=["ZRX-WETH"])
        print("Initializing Radar Relay market... ")
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        cls.clock.add_iterator(cls.wallet)
        cls.clock.add_iterator(cls.market)
        cls.ev_loop.run_until_complete(cls.clock.run_til(time.time() + 1))
        cls.ev_loop.run_until_complete(cls.wait_til_ready())
        print("Ready.")

    @classmethod
    async def wait_til_ready(cls):
        while True:
            if cls.market.ready:
                break
            await asyncio.sleep(1.0)

    def setUp(self):
        self.market_logger = EventLogger()
        self.wallet_logger = EventLogger()
        for event_tag in self.market_events:
            self.market.add_listener(event_tag, self.market_logger)
        for event_tag in self.wallet_events:
            self.wallet.add_listener(event_tag, self.wallet_logger)

    def tearDown(self):
        for event_tag in self.market_events:
            self.market.remove_listener(event_tag, self.market_logger)
        self.market_logger = None
        for event_tag in self.wallet_events:
            self.wallet.remove_listener(event_tag, self.wallet_logger)
        self.wallet_logger = None

    async def run_parallel_async(self, *tasks):
        future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks))
        while not future.done():
            now = time.time()
            next_iteration = now // 1.0 + 1
            await self.clock.run_til(next_iteration)
        return future.result()

    def run_parallel(self, *tasks):
        return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks))

    def test_get_fee(self):
        maker_buy_trade_fee: TradeFee = self.market.get_fee("ZRX", "WETH", OrderType.LIMIT, TradeType.BUY, 20, 0.01)
        self.assertEqual(maker_buy_trade_fee.percent, 0)
        self.assertEqual(len(maker_buy_trade_fee.flat_fees), 0)
        taker_buy_trade_fee: TradeFee = self.market.get_fee("ZRX", "WETH", OrderType.MARKET, TradeType.BUY, 20)
        self.assertEqual(taker_buy_trade_fee.percent, 0)
        self.assertEqual(len(taker_buy_trade_fee.flat_fees), 1)
        self.assertEqual(taker_buy_trade_fee.flat_fees[0][0], "ETH")

    def test_get_wallet_balances(self):
        balances = self.market.get_all_balances()
        self.assertGreaterEqual((balances["ETH"]), 0)
        self.assertGreaterEqual((balances["WETH"]), 0)

    def test_single_limit_order_cancel(self):
        symbol: str = "ZRX-WETH"
        current_price: float = self.market.get_price(symbol, True)
        amount: float = 10
        expires = int(time.time() + 60 * 5)
        quantized_amount: Decimal = self.market.quantize_order_amount(symbol, amount)
        buy_order_id = self.market.buy(symbol=symbol,
                                       amount=amount,
                                       order_type=OrderType.LIMIT,
                                       price=current_price - 0.2 * current_price,
                                       expiration_ts=expires)
        [buy_order_opened_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent))
        self.assertEqual("ZRX-WETH", buy_order_opened_event.symbol)
        self.assertEqual(OrderType.LIMIT, buy_order_opened_event.type)
        self.assertEqual(quantized_amount, Decimal(buy_order_opened_event.amount))

        self.run_parallel(self.market.cancel_order(buy_order_id))
        [buy_order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent))
        self.assertEqual(buy_order_opened_event.order_id, buy_order_cancelled_event.order_id)

        # Reset the logs
        self.market_logger.clear()

    def test_limit_buy_and_sell_and_cancel_all(self):
        symbol: str = "ZRX-WETH"
        current_price: float = self.market.get_price(symbol, True)
        amount: float = 10
        expires = int(time.time() + 60 * 5)
        quantized_amount: Decimal = self.market.quantize_order_amount(symbol, amount)
        buy_order_id = self.market.buy(symbol=symbol,
                                       amount=amount,
                                       order_type=OrderType.LIMIT,
                                       price=current_price - 0.2 * current_price,
                                       expiration_ts=expires)
        [buy_order_opened_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent))
        self.assertEqual(buy_order_id, buy_order_opened_event.order_id)
        self.assertEqual(quantized_amount, Decimal(buy_order_opened_event.amount))
        self.assertEqual("ZRX-WETH", buy_order_opened_event.symbol)
        self.assertEqual(OrderType.LIMIT, buy_order_opened_event.type)

        # Reset the logs
        self.market_logger.clear()

        sell_order_id = self.market.sell(symbol=symbol,
                                         amount=amount,
                                         order_type=OrderType.LIMIT,
                                         price=current_price + 0.2 * current_price,
                                         expiration_ts=expires)
        [sell_order_opened_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent))
        self.assertEqual(sell_order_id, sell_order_opened_event.order_id)
        self.assertEqual(quantized_amount, Decimal(sell_order_opened_event.amount))
        self.assertEqual("ZRX-WETH", sell_order_opened_event.symbol)
        self.assertEqual(OrderType.LIMIT, sell_order_opened_event.type)

        [cancellation_results] = self.run_parallel(self.market.cancel_all(60 * 5))
        self.assertEqual(cancellation_results[0], CancellationResult(buy_order_id, True))
        self.assertEqual(cancellation_results[1], CancellationResult(sell_order_id, True))
        # Reset the logs
        self.market_logger.clear()

    def test_order_expire(self):
        symbol: str = "ZRX-WETH"
        current_price: float = self.market.get_price(symbol, True)
        amount: float = 10
        expires = int(time.time() + 60 * 2) # expires in 2 min
        quantized_amount: Decimal = self.market.quantize_order_amount(symbol, amount)
        buy_order_id = self.market.buy(symbol=symbol,
                                       amount=amount,
                                       order_type=OrderType.LIMIT,
                                       price=current_price - 0.2 * current_price,
                                       expiration_ts=expires)
        [buy_order_opened_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent))

        self.assertEqual("ZRX-WETH", buy_order_opened_event.symbol)
        self.assertEqual(OrderType.LIMIT, buy_order_opened_event.type)
        [buy_order_expired_event] = self.run_parallel(self.market_logger.wait_for(OrderExpiredEvent, 60 * 3))
        self.assertEqual(buy_order_opened_event.order_id, buy_order_expired_event.order_id)

        # Reset the logs
        self.market_logger.clear()

    def test_market_buy(self):
        amount: float = 5
        quantized_amount: Decimal = self.market.quantize_order_amount("ZRX-WETH", amount)
        order_id = self.market.buy("ZRX-WETH", amount, OrderType.MARKET)

        [order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        order_filled_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log
                                                       if isinstance(t, OrderFilledEvent)]

        self.assertTrue([evt.order_type == OrderType.MARKET for evt in order_filled_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(float(quantized_amount), float(order_completed_event.base_asset_amount))
        self.assertEqual("ZRX", order_completed_event.base_asset)
        self.assertEqual("WETH", order_completed_event.quote_asset)
        self.market_logger.clear()

    def test_market_sell(self):
        amount: float = 5
        quantized_amount: Decimal = self.market.quantize_order_amount("ZRX-WETH", amount)
        order_id = self.market.sell("ZRX-WETH", amount, OrderType.MARKET)

        [order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        order_filled_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log
                                                       if isinstance(t, OrderFilledEvent)]

        self.assertTrue([evt.order_type == OrderType.MARKET for evt in order_filled_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(float(quantized_amount), float(order_completed_event.base_asset_amount))
        self.assertEqual("ZRX", order_completed_event.base_asset)
        self.assertEqual("WETH", order_completed_event.quote_asset)
        self.market_logger.clear()

    def test_wrap_eth(self):
        amount_to_wrap = 0.01
        tx_hash = self.wallet.wrap_eth(amount_to_wrap)
        [tx_completed_event] = self.run_parallel(self.wallet_logger.wait_for(WalletWrappedEthEvent))
        tx_completed_event: WalletWrappedEthEvent = tx_completed_event

        self.assertEqual(tx_hash, tx_completed_event.tx_hash)
        self.assertEqual(amount_to_wrap, tx_completed_event.amount)
        self.assertEqual(self.wallet.address, tx_completed_event.address)

    def test_unwrap_eth(self):
        amount_to_unwrap = 0.01
        tx_hash = self.wallet.unwrap_eth(amount_to_unwrap)
        [tx_completed_event] = self.run_parallel(self.wallet_logger.wait_for(WalletUnwrappedEthEvent))
        tx_completed_event: WalletUnwrappedEthEvent = tx_completed_event

        self.assertEqual(tx_hash, tx_completed_event.tx_hash)
        self.assertEqual(amount_to_unwrap, tx_completed_event.amount)
        self.assertEqual(self.wallet.address, tx_completed_event.address)
Exemplo n.º 3
0
 def setUp(self):
     self.logger_a = EventLogger()
     self.logger_b = EventLogger()
     for event_tag in self.events:
         self.wallet_a.add_listener(event_tag, self.logger_a)
         self.wallet_b.add_listener(event_tag, self.logger_b)
Exemplo n.º 4
0
class Web3WalletUnitTest(unittest.TestCase):
    wallet_a: Optional[Web3Wallet] = None
    wallet_b: Optional[Web3Wallet] = None
    erc20_token: Optional[ERC20Token] = None
    events: List[WalletEvent] = [
        WalletEvent.ReceivedAsset, WalletEvent.GasUsed,
        WalletEvent.TokenApproved, WalletEvent.TransactionFailure
    ]

    logger_a: EventLogger
    logger_b: EventLogger

    @classmethod
    def setUpClass(cls):
        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.erc20_token_address = conf.test_erc20_token_address
        cls.w3 = Web3(Web3.HTTPProvider(conf.test_web3_provider_list[0]))

        cls.wallet_a = Web3Wallet(conf.web3_test_private_key_a,
                                  conf.test_web3_provider_list,
                                  [cls.erc20_token_address])
        cls.wallet_b = Web3Wallet(conf.web3_test_private_key_b,
                                  conf.test_web3_provider_list,
                                  [cls.erc20_token_address])

        cls.erc20_token: ERC20Token = list(
            cls.wallet_a.current_backend.erc20_tokens.values())[0]

        cls.clock.add_iterator(cls.wallet_a)
        cls.clock.add_iterator(cls.wallet_b)
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()

        next_iteration = (time.time() // 5.0 + 1) * 5
        cls.ev_loop.run_until_complete(cls.clock.run_til(next_iteration))

    def setUp(self):
        self.logger_a = EventLogger()
        self.logger_b = EventLogger()
        for event_tag in self.events:
            self.wallet_a.add_listener(event_tag, self.logger_a)
            self.wallet_b.add_listener(event_tag, self.logger_b)

    def tearDown(self):
        for event_tag in self.events:
            self.wallet_a.remove_listener(event_tag, self.logger_a)
            self.wallet_b.remove_listener(event_tag, self.logger_b)
        self.logger_a = None
        self.logger_b = None

    async def run_parallel_async(self, *tasks):
        future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks))
        while not future.done():
            now = time.time()
            next_iteration = now // 1.0 + 1
            await self.clock.run_til(next_iteration)
        return future.result()

    def run_parallel(self, *tasks):
        return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks))

    def test_send_balances(self):
        # Check the initial conditions. There should be a certain number of initial tokens before the test can be
        # carried out.
        self.assertGreater(self.wallet_a.get_balance("ETH"), 1.0)
        self.assertGreater(self.wallet_b.get_balance("ETH"), 1.0)
        self.assertGreater(self.wallet_a.get_balance("BNB"), 0.1)
        self.assertGreater(self.wallet_b.get_balance("BNB"), 0.1)

        # Send some Ether between wallets.
        eth_tx_hash: str = self.wallet_a.send(self.wallet_b.address, "ETH",
                                              0.1)
        bnb_tx_hash: str = self.wallet_b.send(self.wallet_a.address, "BNB",
                                              0.01)
        bnb_asset_received, eth_asset_received, eth_gas_used, bnb_gas_used = self.run_parallel(
            self.logger_a.wait_for(WalletReceivedAssetEvent),
            self.logger_b.wait_for(WalletReceivedAssetEvent),
            self.logger_a.wait_for(EthereumGasUsedEvent),
            self.logger_b.wait_for(EthereumGasUsedEvent))
        eth_asset_received: WalletReceivedAssetEvent = eth_asset_received
        eth_gas_used: EthereumGasUsedEvent = eth_gas_used
        self.assertEqual(eth_tx_hash, eth_asset_received.tx_hash)
        self.assertEqual(self.wallet_a.address,
                         eth_asset_received.from_address)
        self.assertEqual(self.wallet_b.address, eth_asset_received.to_address)
        self.assertEqual("ETH", eth_asset_received.asset_name)
        self.assertEqual(0.1, eth_asset_received.amount_received)
        self.assertEqual(int(1e17), eth_asset_received.raw_amount_received)
        self.assertEqual(eth_tx_hash, eth_gas_used.tx_hash)
        self.assertEqual(21000, eth_gas_used.gas_used)

        bnb_asset_received: WalletReceivedAssetEvent = bnb_asset_received
        bnb_gas_used: EthereumGasUsedEvent = bnb_gas_used
        self.assertEqual(bnb_tx_hash, bnb_asset_received.tx_hash)
        self.assertEqual(self.wallet_b.address,
                         bnb_asset_received.from_address)
        self.assertEqual(self.wallet_a.address, bnb_asset_received.to_address)
        self.assertEqual("BNB", bnb_asset_received.asset_name)
        self.assertEqual(0.01, bnb_asset_received.amount_received)
        self.assertEqual(int(1e16), bnb_asset_received.raw_amount_received)
        self.assertEqual(bnb_tx_hash, bnb_gas_used.tx_hash)
        self.assertTrue(bnb_gas_used.gas_used > 21000)

        # Send out the reverse transactions.
        self.wallet_b.send(self.wallet_a.address, "ETH", 0.1)
        self.wallet_a.send(self.wallet_b.address, "BNB", 0.01)

    def test_transaction_failure(self):
        # Produce a transfer failure, by not transferring more than the account has.
        erc20_token_contract: Contract = self.erc20_token.contract
        failure_hash: str = self.wallet_a.execute_transaction(
            erc20_token_contract.functions.transfer(self.wallet_b.address,
                                                    int(1e30)),
            gas=500000)
        failure_tx, gas_used_event = self.run_parallel(
            self.logger_a.wait_for(str),
            self.logger_a.wait_for(EthereumGasUsedEvent))
        failure_tx: str = failure_tx
        gas_used_event: EthereumGasUsedEvent = gas_used_event
        self.assertEqual(failure_hash, failure_tx)
        self.assertGreater(gas_used_event.gas_used, 21000)

    def test_token_approval(self):
        approval_hash: str = self.wallet_a.approve_token_transfer(
            self.erc20_token.symbol, self.wallet_b.address, 1.0)
        approval_event, gas_used_event = self.run_parallel(
            self.logger_a.wait_for(TokenApprovedEvent),
            self.logger_a.wait_for(EthereumGasUsedEvent))
        approval_event: TokenApprovedEvent = approval_event
        gas_used_event: EthereumGasUsedEvent = gas_used_event
        self.assertEqual(approval_hash, approval_event.tx_hash)
        self.assertEqual(approval_hash, gas_used_event.tx_hash)
        self.assertEqual(self.wallet_a.address, approval_event.owner_address)
        self.assertEqual(self.wallet_b.address, approval_event.spender_address)
        self.assertEqual(self.erc20_token.symbol, approval_event.asset_name)
        self.assertEqual(1.0, approval_event.amount)
        self.assertEqual(int(1e18), approval_event.raw_amount)

        self.wallet_a.approve_token_transfer(self.erc20_token.symbol,
                                             self.wallet_b.address, 0.0)
        self.run_parallel(self.logger_a.wait_for(TokenApprovedEvent),
                          self.logger_a.wait_for(EthereumGasUsedEvent))
Exemplo n.º 5
0
class DDEXMarketUnitTest(unittest.TestCase):
    market_events: List[MarketEvent] = [
        MarketEvent.ReceivedAsset, MarketEvent.BuyOrderCompleted,
        MarketEvent.SellOrderCompleted, MarketEvent.WithdrawAsset,
        MarketEvent.OrderFilled, MarketEvent.BuyOrderCreated,
        MarketEvent.SellOrderCreated
    ]

    wallet_events: List[WalletEvent] = [
        WalletEvent.WrappedEth, WalletEvent.UnwrappedEth
    ]

    wallet: Web3Wallet
    market: DDEXMarket
    market_logger: EventLogger
    wallet_logger: EventLogger

    @classmethod
    def setUpClass(cls):
        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.wallet = Web3Wallet(private_key=conf.web3_test_private_key_ddex,
                                backend_urls=conf.test_ddex_web3_provider_list,
                                erc20_token_addresses=[
                                    conf.test_ddex_erc20_token_address_1,
                                    conf.test_ddex_erc20_token_address_2
                                ],
                                chain=EthereumChain.MAIN_NET)
        cls.market: DDEXMarket = DDEXMarket(
            wallet=cls.wallet,
            web3_url=conf.test_ddex_web3_provider_list[0],
            order_book_tracker_data_source_type=OrderBookTrackerDataSourceType.
            EXCHANGE_API,
            symbols=["HOT-WETH"])
        print("Initializing DDEX market... ")
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        cls.clock.add_iterator(cls.wallet)
        cls.clock.add_iterator(cls.market)
        cls.ev_loop.run_until_complete(cls.clock.run_til(time.time() + 1))
        cls.ev_loop.run_until_complete(cls.wait_til_ready())
        print("Ready.")

    @classmethod
    async def wait_til_ready(cls):
        while True:
            if cls.market.ready:
                break
            await asyncio.sleep(1.0)

    def setUp(self):
        self.market_logger = EventLogger()
        self.wallet_logger = EventLogger()
        for event_tag in self.market_events:
            self.market.add_listener(event_tag, self.market_logger)
        for event_tag in self.wallet_events:
            self.wallet.add_listener(event_tag, self.wallet_logger)

    def tearDown(self):
        for event_tag in self.market_events:
            self.market.remove_listener(event_tag, self.market_logger)
        self.market_logger = None
        for event_tag in self.wallet_events:
            self.wallet.remove_listener(event_tag, self.wallet_logger)
        self.wallet_logger = None

    async def run_parallel_async(self, *tasks):
        future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks))
        while not future.done():
            now = time.time()
            next_iteration = now // 1.0 + 1
            await self.clock.run_til(next_iteration)
        return future.result()

    def run_parallel(self, *tasks):
        return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks))

    def test_get_fee(self):
        weth_trade_fee: TradeFee = self.market.get_fee("ZRX-WETH",
                                                       OrderType.LIMIT,
                                                       TradeType.BUY, 10000, 1)
        self.assertGreater(weth_trade_fee.percent, 0)
        self.assertEqual(len(weth_trade_fee.flat_fees), 1)
        self.assertEqual(weth_trade_fee.flat_fees[0][0], "WETH")
        dai_trade_fee: TradeFee = self.market.get_fee("WETH-DAI",
                                                      OrderType.MARKET,
                                                      TradeType.BUY, 10000)
        self.assertGreater(dai_trade_fee.percent, 0)
        self.assertEqual(len(dai_trade_fee.flat_fees), 1)
        self.assertEqual(dai_trade_fee.flat_fees[0][0], "DAI")

    def test_get_wallet_balances(self):
        balances = self.market.get_all_balances()
        self.assertGreaterEqual((balances["ETH"]), 0)
        self.assertGreaterEqual((balances["WETH"]), 0)

    def test_list_orders(self):
        [orders] = self.run_parallel(self.market.list_orders())
        self.assertGreaterEqual(len(orders), 0)

    def test_list_locked_balances(self):
        [locked_balances
         ] = self.run_parallel(self.market.list_locked_balances())
        self.assertGreaterEqual(len(locked_balances), 0)

    @unittest.skipUnless(
        any("test_bad_orders_are_not_tracked" in arg for arg in sys.argv),
        "bad_orders_are_not_tracked test requires manual action.")
    def test_bad_orders_are_not_tracked(self):
        # Should fail due to insufficient balance
        order_id = self.market.buy("WETH-DAI", 10000, OrderType.LIMIT, 1)
        self.assertEqual(self.market.in_flight_orders.get(order_id), None)

    def test_cancel_order(self):
        symbol = "HOT-WETH"
        bid_price: float = self.market.get_price(symbol, True)
        amount = 0.02 / bid_price

        # Intentionally setting invalid price to prevent getting filled
        client_order_id = self.market.buy(symbol, amount, OrderType.LIMIT,
                                          bid_price * 0.7)
        self.market.cancel(symbol, client_order_id)
        self.run_parallel(asyncio.sleep(5))
        self.assertEqual(self.market.in_flight_orders.get(client_order_id),
                         None)

    def test_place_limit_buy_and_sell(self):
        self.assertGreater(self.market.get_balance("WETH"), 0.01)

        # Try to buy 0.01 WETH worth of HOT from the exchange, and watch for completion event.
        symbol = "HOT-WETH"
        bid_price: float = self.market.get_price(symbol, True)
        amount: float = 0.01 / bid_price
        buy_order_id: str = self.market.buy(symbol, amount, OrderType.LIMIT,
                                            bid_price * 0.7)
        self.run_parallel(asyncio.sleep(3))
        exchange_order_id: str = self.market.in_flight_orders.get(
            buy_order_id).exchange_order_id
        buy_order = self.run_parallel(self.market.get_order(exchange_order_id))
        self.assertEqual(buy_order[0].get('id'), exchange_order_id)
        self.market.cancel(symbol, buy_order_id)

        # Try to sell back the same amount of HOT to the exchange, and watch for completion event.
        ask_price: float = self.market.get_price(symbol, False)
        sell_order_id: str = self.market.sell(symbol, amount, OrderType.LIMIT,
                                              ask_price * 1.5)
        self.run_parallel(asyncio.sleep(3))
        exchange_order_id: str = self.market.in_flight_orders.get(
            sell_order_id).exchange_order_id
        sell_order = self.run_parallel(
            self.market.get_order(exchange_order_id))
        self.assertEqual(sell_order[0].get('id'), exchange_order_id)
        self.market.cancel(symbol, sell_order_id)

    @unittest.skipUnless(
        any("test_limit_buy_and_sell_get_matched" in arg for arg in sys.argv),
        "test_limit_buy_and_sell_get_matched test requires manual action.")
    def test_limit_buy_and_sell_get_matched(self):
        self.assertGreater(self.market.get_balance("WETH"), 0.01)

        # Try to buy 0.01 WETH worth of HOT from the exchange, and watch for completion event.
        current_price: float = self.market.get_price("HOT-WETH", True)
        amount: float = 0.01 / current_price
        quantized_amount: Decimal = self.market.quantize_order_amount(
            "HOT-WETH", amount)
        order_id = self.market.buy("HOT-WETH", amount, OrderType.LIMIT,
                                   current_price)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        order_filled_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in order_filled_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(float(quantized_amount),
                         order_completed_event.base_asset_amount)
        self.assertEqual("HOT", order_completed_event.base_asset)
        self.assertEqual("WETH", order_completed_event.quote_asset)
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, BuyOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))
        # Reset the logs
        self.market_logger.clear()

        # Try to sell back the same amount of HOT to the exchange, and watch for completion event.
        current_price: float = self.market.get_price("HOT-WETH", False)
        amount = float(order_completed_event.base_asset_amount)
        quantized_amount = order_completed_event.base_asset_amount
        order_id = self.market.sell("HOT-WETH", amount, OrderType.LIMIT,
                                    current_price)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        order_filled_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in order_filled_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(float(quantized_amount),
                         order_completed_event.base_asset_amount)
        self.assertEqual("HOT", order_completed_event.base_asset)
        self.assertEqual("WETH", order_completed_event.quote_asset)
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, SellOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))

    def test_market_buy_and_sell(self):
        self.assertGreater(self.market.get_balance("WETH"), 0.01)

        amount: float = 1200.0  # Min order size is 1000 HOT
        quantized_amount: Decimal = self.market.quantize_order_amount(
            "HOT-WETH", amount)

        order_id = self.market.buy("HOT-WETH", amount, OrderType.MARKET)

        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        order_filled_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]

        self.assertTrue([
            evt.order_type == OrderType.MARKET for evt in order_filled_events
        ])
        self.assertEqual(order_id, order_completed_event.order_id)

        # This is because some of the tokens are deducted in the trading fees.
        self.assertTrue(
            float(quantized_amount) > order_completed_event.base_asset_amount >
            float(quantized_amount) * 0.9)
        self.assertEqual("HOT", order_completed_event.base_asset)
        self.assertEqual("WETH", order_completed_event.quote_asset)
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, BuyOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))
        # Reset the logs
        self.market_logger.clear()

        # Try to sell back the same amount of HOT to the exchange, and watch for completion event.
        amount = float(order_completed_event.base_asset_amount)
        quantized_amount = order_completed_event.base_asset_amount
        order_id = self.market.sell("HOT-WETH", amount, OrderType.MARKET)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        order_filled_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]

        self.assertTrue([
            evt.order_type == OrderType.MARKET for evt in order_filled_events
        ])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(float(quantized_amount),
                         order_completed_event.base_asset_amount)
        self.assertEqual("HOT", order_completed_event.base_asset)
        self.assertEqual("WETH", order_completed_event.quote_asset)
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, SellOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))

    @unittest.skipUnless(any("test_wrap_eth" in arg for arg in sys.argv),
                         "Wrap Eth test requires manual action.")
    def test_wrap_eth(self):
        amount_to_wrap = 0.01
        tx_hash = self.wallet.wrap_eth(amount_to_wrap)
        [tx_completed_event] = self.run_parallel(
            self.wallet_logger.wait_for(WalletWrappedEthEvent))
        tx_completed_event: WalletWrappedEthEvent = tx_completed_event

        self.assertEqual(tx_hash, tx_completed_event.tx_hash)
        self.assertEqual(amount_to_wrap, tx_completed_event.amount)
        self.assertEqual(self.wallet.address, tx_completed_event.address)

    @unittest.skipUnless(any("test_unwrap_eth" in arg for arg in sys.argv),
                         "Unwrap Eth test requires manual action.")
    def test_unwrap_eth(self):
        amount_to_unwrap = 0.01
        tx_hash = self.wallet.unwrap_eth(amount_to_unwrap)
        [tx_completed_event] = self.run_parallel(
            self.wallet_logger.wait_for(WalletUnwrappedEthEvent))
        tx_completed_event: WalletUnwrappedEthEvent = tx_completed_event

        self.assertEqual(tx_hash, tx_completed_event.tx_hash)
        self.assertEqual(amount_to_unwrap, tx_completed_event.amount)
        self.assertEqual(self.wallet.address, tx_completed_event.address)

    def test_cancel_all_happy_case(self):
        symbol = "HOT-WETH"
        bid_price: float = self.market.get_price(symbol, True)
        ask_price: float = self.market.get_price(symbol, False)
        amount = 0.02 / bid_price

        self.assertGreater(self.market.get_balance("WETH"), 0.02)
        self.assertGreater(self.market.get_balance("HOT"), amount)

        # Intentionally setting invalid price to prevent getting filled
        self.market.buy(symbol, amount, OrderType.LIMIT, bid_price * 0.7)
        self.market.sell(symbol, amount, OrderType.LIMIT, ask_price * 1.5)

        [cancellation_results] = self.run_parallel(self.market.cancel_all(10))
        print(cancellation_results)
        self.assertGreater(len(cancellation_results), 0)
        for cr in cancellation_results:
            self.assertEqual(cr.success, True)

    def test_cancel_all_failure_case(self):
        symbol = "HOT-WETH"
        bid_price: float = self.market.get_price(symbol, True)
        ask_price: float = self.market.get_price(symbol, False)
        # order submission should fail due to insufficient balance
        amount = 100 / bid_price

        self.assertLess(self.market.get_balance("WETH"), 100)
        self.assertLess(self.market.get_balance("HOT"), amount)

        self.market.buy(symbol, amount, OrderType.LIMIT, bid_price * 0.7)
        self.market.sell(symbol, amount, OrderType.LIMIT, ask_price * 1.5)

        [cancellation_results] = self.run_parallel(self.market.cancel_all(10))
        print(cancellation_results)
        self.assertGreater(len(cancellation_results), 0)
        for cr in cancellation_results:
            self.assertEqual(cr.success, False)
 def setUp(self):
     self.market_logger = EventLogger()
     for event_tag in self.events:
         self.market.add_listener(event_tag, self.market_logger)
class BinanceMarketUnitTest(unittest.TestCase):
    events: List[MarketEvent] = [
        MarketEvent.ReceivedAsset, MarketEvent.BuyOrderCompleted,
        MarketEvent.SellOrderCompleted, MarketEvent.WithdrawAsset,
        MarketEvent.OrderFilled, MarketEvent.TransactionFailure,
        MarketEvent.BuyOrderCreated, MarketEvent.SellOrderCreated
    ]

    market: BinanceMarket
    market_logger: EventLogger

    @classmethod
    def setUpClass(cls):
        global MAINNET_RPC_URL

        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.market: BinanceMarket = BinanceMarket(
            MAINNET_RPC_URL,
            conf.binance_api_key,
            conf.binance_api_secret,
            order_book_tracker_data_source_type=OrderBookTrackerDataSourceType.
            EXCHANGE_API,
            user_stream_tracker_data_source_type=UserStreamTrackerDataSourceType
            .EXCHANGE_API,
            symbols=["ZRXETH", "LOOMETH"])
        print("Initializing Binance market... this will take about a minute.")
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        cls.clock.add_iterator(cls.market)
        cls.ev_loop.run_until_complete(cls.clock.run_til(time.time() + 1))
        cls.ev_loop.run_until_complete(cls.wait_til_ready())
        print("Ready.")

    @classmethod
    async def wait_til_ready(cls):
        while True:
            if cls.market.ready:
                break
            await asyncio.sleep(1.0)

    def setUp(self):
        self.market_logger = EventLogger()
        for event_tag in self.events:
            self.market.add_listener(event_tag, self.market_logger)

    def tearDown(self):
        for event_tag in self.events:
            self.market.remove_listener(event_tag, self.market_logger)
        self.market_logger = None

    async def run_parallel_async(self, *tasks):
        future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks))
        while not future.done():
            now = time.time()
            next_iteration = now // 1.0 + 1
            await self.clock.run_til(next_iteration)
        return future.result()

    def run_parallel(self, *tasks):
        return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks))

    def test_buy_and_sell(self):
        self.assertGreater(self.market.get_balance("ETH"), 0.1)

        # Try to buy 0.02 ETH worth of ZRX from the exchange, and watch for completion event.
        current_price: float = self.market.get_price("ZRXETH", True)
        amount: float = 0.02 / current_price
        quantized_amount: Decimal = self.market.quantize_order_amount(
            "ZRXETH", amount)
        order_id = self.market.buy("ZRXETH", amount)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        trade_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded: float = sum(t.amount for t in trade_events)
        quote_amount_traded: float = sum(t.amount * t.price
                                         for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.MARKET for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(quantized_amount,
                         order_completed_event.base_asset_amount)
        self.assertEqual("ZRX", order_completed_event.base_asset)
        self.assertEqual("ETH", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, BuyOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))

        # Reset the logs
        self.market_logger.clear()

        # Try to sell back the same amount of ZRX to the exchange, and watch for completion event.
        amount = float(order_completed_event.base_asset_amount)
        quantized_amount = order_completed_event.base_asset_amount
        order_id = self.market.sell("ZRXETH", amount)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        trade_events = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded = sum(t.amount for t in trade_events)
        quote_amount_traded = sum(t.amount * t.price for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.MARKET for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(quantized_amount,
                         order_completed_event.base_asset_amount)
        self.assertEqual("ZRX", order_completed_event.base_asset)
        self.assertEqual("ETH", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, SellOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))

    def test_limit_buy_and_sell(self):
        self.assertGreater(self.market.get_balance("ETH"), 0.1)

        # Try to put limit buy order for 0.02 ETH worth of ZRX, and watch for completion event.
        current_bid_price: float = self.market.get_price("ZRXETH", True)
        bid_price: float = current_bid_price + 0.05 * current_bid_price
        quantize_bid_price: Decimal = self.market.quantize_order_price(
            "ZRXETH", bid_price)

        amount: float = 0.02 / bid_price
        quantized_amount: Decimal = self.market.quantize_order_amount(
            "ZRXETH", amount)

        order_id = self.market.buy("ZRXETH", quantized_amount, OrderType.LIMIT,
                                   quantize_bid_price)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        trade_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded: float = sum(t.amount for t in trade_events)
        quote_amount_traded: float = sum(t.amount * t.price
                                         for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(quantized_amount,
                         order_completed_event.base_asset_amount)
        self.assertEqual("ZRX", order_completed_event.base_asset)
        self.assertEqual("ETH", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, BuyOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))

        # Reset the logs
        self.market_logger.clear()

        # Try to put limit sell order for 0.02 ETH worth of ZRX, and watch for completion event.
        current_ask_price: float = self.market.get_price("ZRXETH", False)
        ask_price: float = current_ask_price - 0.05 * current_ask_price
        quantize_ask_price: Decimal = self.market.quantize_order_price(
            "ZRXETH", ask_price)

        amount = float(order_completed_event.base_asset_amount)
        quantized_amount = order_completed_event.base_asset_amount

        order_id = self.market.sell("ZRXETH", amount, OrderType.LIMIT,
                                    quantize_ask_price)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        trade_events = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded = sum(t.amount for t in trade_events)
        quote_amount_traded = sum(t.amount * t.price for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertEqual(quantized_amount,
                         order_completed_event.base_asset_amount)
        self.assertEqual("ZRX", order_completed_event.base_asset)
        self.assertEqual("ETH", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertGreater(order_completed_event.fee_amount, Decimal(0))
        self.assertTrue(
            any([
                isinstance(event, SellOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))

    @unittest.skipUnless(any("test_deposit_eth" in arg for arg in sys.argv),
                         "Deposit test requires manual action.")
    def test_deposit_eth(self):
        with open(realpath(join(__file__, "../../data/ZRXABI.json"))) as fd:
            zrx_abi: str = fd.read()
        local_wallet: MockWallet = MockWallet(
            conf.web3_test_private_key_a,
            MAINNET_RPC_URL,
            {"0xE41d2489571d322189246DaFA5ebDe1F4699F498": zrx_abi},
            chain_id=1)

        # Ensure the local wallet has enough balance for deposit testing.
        self.assertGreaterEqual(local_wallet.get_balance("ETH"), 0.02)

        # Deposit ETH to Binance, and wait.
        tracking_id: str = self.market.deposit(local_wallet, "ETH", 0.01)
        [received_asset_event] = self.run_parallel(
            self.market_logger.wait_for(MarketReceivedAssetEvent,
                                        timeout_seconds=1800))
        received_asset_event: MarketReceivedAssetEvent = received_asset_event
        self.assertEqual("ETH", received_asset_event.asset_name)
        self.assertEqual(tracking_id, received_asset_event.tx_hash)
        self.assertEqual(local_wallet.address,
                         received_asset_event.from_address)
        self.assertAlmostEqual(0.01, received_asset_event.amount_received)

    @unittest.skipUnless(any("test_deposit_zrx" in arg for arg in sys.argv),
                         "Deposit test requires manual action.")
    def test_deposit_zrx(self):
        with open(realpath(join(__file__, "../../data/ZRXABI.json"))) as fd:
            zrx_abi: str = fd.read()
        local_wallet: MockWallet = MockWallet(
            conf.web3_test_private_key_a,
            MAINNET_RPC_URL,
            {"0xE41d2489571d322189246DaFA5ebDe1F4699F498": zrx_abi},
            chain_id=1)

        # Ensure the local wallet has enough balance for deposit testing.
        self.assertGreaterEqual(local_wallet.get_balance("ZRX"), 1)

        # Deposit ZRX to Binance, and wait.
        tracking_id: str = self.market.deposit(local_wallet, "ZRX", 1)
        [received_asset_event] = self.run_parallel(
            self.market_logger.wait_for(MarketReceivedAssetEvent,
                                        timeout_seconds=1800))
        received_asset_event: MarketReceivedAssetEvent = received_asset_event
        self.assertEqual("ZRX", received_asset_event.asset_name)
        self.assertEqual(tracking_id, received_asset_event.tx_hash)
        self.assertEqual(local_wallet.address,
                         received_asset_event.from_address)
        self.assertEqual(1, received_asset_event.amount_received)

    @unittest.skipUnless(any("test_withdraw" in arg for arg in sys.argv),
                         "Withdraw test requires manual action.")
    def test_withdraw(self):
        with open(realpath(join(__file__, "../../data/ZRXABI.json"))) as fd:
            zrx_abi: str = fd.read()
        local_wallet: MockWallet = MockWallet(
            conf.web3_test_private_key_a,
            MAINNET_RPC_URL,
            {"0xE41d2489571d322189246DaFA5ebDe1F4699F498": zrx_abi},
            chain_id=1)

        # Ensure the market account has enough balance for withdraw testing.
        self.assertGreaterEqual(self.market.get_balance("ZRX"), 10)

        # Withdraw ZRX from Binance to test wallet.
        self.market.withdraw(local_wallet.address, "ZRX", 10)
        [withdraw_asset_event] = self.run_parallel(
            self.market_logger.wait_for(MarketWithdrawAssetEvent))
        withdraw_asset_event: MarketWithdrawAssetEvent = withdraw_asset_event
        print(withdraw_asset_event)
        self.assertEqual(local_wallet.address, withdraw_asset_event.to_address)
        self.assertEqual("ZRX", withdraw_asset_event.asset_name)
        self.assertEqual(10, withdraw_asset_event.amount)
        self.assertGreater(withdraw_asset_event.fee_amount, 0)

    def test_cancel_all(self):
        symbol = "LOOMETH"
        bid_price: float = self.market.get_price(symbol, True)
        ask_price: float = self.market.get_price(symbol, False)
        amount: float = 0.02 / bid_price
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        # Intentionally setting invalid price to prevent getting filled
        quantize_bid_price: Decimal = self.market.quantize_order_price(
            symbol, bid_price * 0.7)
        quantize_ask_price: Decimal = self.market.quantize_order_price(
            symbol, ask_price * 1.5)

        self.market.buy(symbol, quantized_amount, OrderType.LIMIT,
                        quantize_bid_price)
        self.market.sell(symbol, quantized_amount, OrderType.LIMIT,
                         quantize_ask_price)

        self.run_parallel(asyncio.sleep(1))
        [cancellation_results] = self.run_parallel(self.market.cancel_all(5))
        for cr in cancellation_results:
            self.assertEqual(cr.success, True)

    def test_server_time_offset(self):
        BinanceTime.get_instance().SERVER_TIME_OFFSET_CHECK_INTERVAL = 3.0
        self.run_parallel(asyncio.sleep(60))
        with patch("wings.binance_market.time") as market_time:

            def delayed_time():
                return time.time() - 30.0

            market_time.time = delayed_time
            self.run_parallel(asyncio.sleep(5.0))
            time_offset = BinanceTime.get_instance().time_offset_ms
            print("offest", time_offset)
            # check if it is less than 5% off
            self.assertTrue(time_offset > 0)
            self.assertTrue(abs(time_offset - 30.0 * 1e3) < 1.5 * 1e3)
Exemplo n.º 8
0
class CoinbaseProMarketUnitTest(unittest.TestCase):
    events: List[MarketEvent] = [
        MarketEvent.ReceivedAsset, MarketEvent.BuyOrderCompleted,
        MarketEvent.SellOrderCompleted, MarketEvent.WithdrawAsset,
        MarketEvent.OrderFilled, MarketEvent.OrderCancelled,
        MarketEvent.TransactionFailure, MarketEvent.BuyOrderCreated,
        MarketEvent.SellOrderCreated
    ]

    market: CoinbaseProMarket
    market_logger: EventLogger

    @classmethod
    def setUpClass(cls):
        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.market: CoinbaseProMarket = CoinbaseProMarket(
            web3_url=conf.test_web3_provider_list[0],
            coinbase_pro_api_key=conf.coinbase_pro_api_key,
            coinbase_pro_secret_key=conf.coinbase_pro_secret_key,
            coinbase_pro_passphrase=conf.coinbase_pro_passphrase,
            symbols=["ETH-USDC", "ETH-USD"])
        cls.wallet: Web3Wallet = Web3Wallet(
            private_key=conf.web3_private_key_coinbase_pro,
            backend_urls=conf.test_web3_provider_list,
            erc20_token_addresses=[
                conf.mn_weth_token_address, conf.mn_zerox_token_address
            ],
            chain=EthereumChain.MAIN_NET)
        print(
            "Initializing Coinbase Pro market... this will take about a minute."
        )
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        cls.clock.add_iterator(cls.market)
        cls.clock.add_iterator(cls.wallet)
        cls.ev_loop.run_until_complete(cls.clock.run_til(time.time() + 1))
        cls.ev_loop.run_until_complete(cls.wait_til_ready())
        print("Ready.")

    @classmethod
    async def wait_til_ready(cls):
        while True:
            if cls.market.ready:
                break
            await asyncio.sleep(1.0)

    def setUp(self):
        self.market_logger = EventLogger()
        for event_tag in self.events:
            self.market.add_listener(event_tag, self.market_logger)

    def tearDown(self):
        for event_tag in self.events:
            self.market.remove_listener(event_tag, self.market_logger)
        self.market_logger = None

    async def run_parallel_async(self, *tasks):
        future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks))
        while not future.done():
            now = time.time()
            next_iteration = now // 1.0 + 1
            await self.clock.run_til(next_iteration)
        return future.result()

    def run_parallel(self, *tasks):
        return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks))

    def test_limit_buy(self):
        self.assertGreater(self.market.get_balance("ETH"), 0.1)
        symbol = "ETH-USDC"
        amount: float = 0.02
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        current_bid_price: float = self.market.get_price(symbol, True)
        bid_price: float = current_bid_price + 0.05 * current_bid_price
        quantize_bid_price: Decimal = self.market.quantize_order_price(
            symbol, bid_price)

        order_id = self.market.buy(symbol, quantized_amount, OrderType.LIMIT,
                                   quantize_bid_price)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        trade_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded: float = sum(t.amount for t in trade_events)
        quote_amount_traded: float = sum(t.amount * t.price
                                         for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertAlmostEqual(float(quantized_amount),
                               order_completed_event.base_asset_amount)
        self.assertEqual("ETH", order_completed_event.base_asset)
        self.assertEqual("USDC", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertTrue(
            any([
                isinstance(event, BuyOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))
        # Reset the logs
        self.market_logger.clear()

    def test_limit_sell(self):
        symbol = "ETH-USDC"
        amount: float = 0.02
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)
        current_ask_price: float = self.market.get_price(symbol, False)
        ask_price: float = current_ask_price - 0.05 * current_ask_price
        quantize_ask_price: Decimal = self.market.quantize_order_price(
            symbol, ask_price)

        order_id = self.market.sell(symbol, amount, OrderType.LIMIT,
                                    quantize_ask_price)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        trade_events = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded = sum(t.amount for t in trade_events)
        quote_amount_traded = sum(t.amount * t.price for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertAlmostEqual(float(quantized_amount),
                               order_completed_event.base_asset_amount)
        self.assertEqual("ETH", order_completed_event.base_asset)
        self.assertEqual("USDC", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertTrue(
            any([
                isinstance(event, SellOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))
        # Reset the logs
        self.market_logger.clear()

    # NOTE that orders of non-USD pairs (including USDC pairs) are LIMIT only
    def test_market_buy(self):
        self.assertGreater(self.market.get_balance("ETH"), 0.1)
        symbol = "ETH-USD"
        amount: float = 0.02
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        order_id = self.market.buy(symbol, quantized_amount, OrderType.MARKET,
                                   0)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(BuyOrderCompletedEvent))
        order_completed_event: BuyOrderCompletedEvent = order_completed_event
        trade_events: List[OrderFilledEvent] = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded: float = sum(t.amount for t in trade_events)
        quote_amount_traded: float = sum(t.amount * t.price
                                         for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertAlmostEqual(float(quantized_amount),
                               order_completed_event.base_asset_amount)
        self.assertEqual("ETH", order_completed_event.base_asset)
        self.assertEqual("USD", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertTrue(
            any([
                isinstance(event, BuyOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))
        # Reset the logs
        self.market_logger.clear()

    # NOTE that orders of non-USD pairs (including USDC pairs) are LIMIT only
    def test_market_sell(self):
        symbol = "ETH-USD"
        amount: float = 0.02
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        order_id = self.market.sell(symbol, amount, OrderType.MARKET, 0)
        [order_completed_event] = self.run_parallel(
            self.market_logger.wait_for(SellOrderCompletedEvent))
        order_completed_event: SellOrderCompletedEvent = order_completed_event
        trade_events = [
            t for t in self.market_logger.event_log
            if isinstance(t, OrderFilledEvent)
        ]
        base_amount_traded = sum(t.amount for t in trade_events)
        quote_amount_traded = sum(t.amount * t.price for t in trade_events)

        self.assertTrue(
            [evt.order_type == OrderType.LIMIT for evt in trade_events])
        self.assertEqual(order_id, order_completed_event.order_id)
        self.assertAlmostEqual(float(quantized_amount),
                               order_completed_event.base_asset_amount)
        self.assertEqual("ETH", order_completed_event.base_asset)
        self.assertEqual("USD", order_completed_event.quote_asset)
        self.assertAlmostEqual(base_amount_traded,
                               float(order_completed_event.base_asset_amount))
        self.assertAlmostEqual(quote_amount_traded,
                               float(order_completed_event.quote_asset_amount))
        self.assertTrue(
            any([
                isinstance(event, SellOrderCreatedEvent)
                and event.order_id == order_id
                for event in self.market_logger.event_log
            ]))
        # Reset the logs
        self.market_logger.clear()

    def test_cancel_order(self):
        self.assertGreater(self.market.get_balance("ETH"), 10)
        symbol = "ETH-USDC"

        current_bid_price: float = self.market.get_price(symbol, True)
        amount: float = 10 / current_bid_price

        bid_price: float = current_bid_price - 0.1 * current_bid_price
        quantize_bid_price: Decimal = self.market.quantize_order_price(
            symbol, bid_price)
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        client_order_id = self.market.buy(symbol, quantized_amount,
                                          OrderType.LIMIT, quantize_bid_price)
        self.market.cancel(symbol, client_order_id)
        [order_cancelled_event] = self.run_parallel(
            self.market_logger.wait_for(OrderCancelledEvent))
        order_cancelled_event: OrderCancelledEvent = order_cancelled_event
        self.assertEqual(order_cancelled_event.order_id, client_order_id)

    def test_cancel_all(self):
        symbol = "ETH-USDC"
        bid_price: float = self.market.get_price(symbol, True) * 0.5
        ask_price: float = self.market.get_price(symbol, False) * 2
        amount: float = 10 / bid_price
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        # Intentionally setting invalid price to prevent getting filled
        quantize_bid_price: Decimal = self.market.quantize_order_price(
            symbol, bid_price * 0.7)
        quantize_ask_price: Decimal = self.market.quantize_order_price(
            symbol, ask_price * 1.5)

        self.market.buy(symbol, quantized_amount, OrderType.LIMIT,
                        quantize_bid_price)
        self.market.sell(symbol, quantized_amount, OrderType.LIMIT,
                         quantize_ask_price)
        self.run_parallel(asyncio.sleep(1))
        [cancellation_results] = self.run_parallel(self.market.cancel_all(5))
        for cr in cancellation_results:
            self.assertEqual(cr.success, True)

    @unittest.skipUnless(any("test_list_orders" in arg for arg in sys.argv),
                         "List order test requires manual action.")
    def test_list_orders(self):
        self.assertGreater(self.market.get_balance("ETH"), 0.1)
        symbol = "ETH-USDC"
        amount: float = 0.02
        quantized_amount: Decimal = self.market.quantize_order_amount(
            symbol, amount)

        current_bid_price: float = self.market.get_price(symbol, True)
        bid_price: float = current_bid_price + 0.05 * current_bid_price
        quantize_bid_price: Decimal = self.market.quantize_order_price(
            symbol, bid_price)

        self.market.buy(symbol, quantized_amount, OrderType.LIMIT,
                        quantize_bid_price)
        self.run_parallel(asyncio.sleep(1))
        [order_details] = self.run_parallel(self.market.list_orders())
        self.assertGreaterEqual(len(order_details), 1)

        self.market_logger.clear()

    @unittest.skipUnless(any("test_deposit_eth" in arg for arg in sys.argv),
                         "Deposit test requires manual action.")
    def test_deposit_eth(self):
        # Ensure the local wallet has enough balance for deposit testing.
        self.assertGreaterEqual(self.wallet.get_balance("ETH"), 0.02)

        # Deposit ETH to Binance, and wait.
        tracking_id: str = self.market.deposit(self.wallet, "ETH", 0.01)
        [received_asset_event] = self.run_parallel(
            self.market_logger.wait_for(MarketReceivedAssetEvent,
                                        timeout_seconds=1800))
        received_asset_event: MarketReceivedAssetEvent = received_asset_event
        self.assertEqual("ETH", received_asset_event.asset_name)
        self.assertEqual(tracking_id, received_asset_event.tx_hash)
        self.assertEqual(self.wallet.address,
                         received_asset_event.from_address)
        self.assertAlmostEqual(0.01, received_asset_event.amount_received)

    @unittest.skipUnless(any("test_deposit_zrx" in arg for arg in sys.argv),
                         "Deposit test requires manual action.")
    def test_deposit_zrx(self):
        # Ensure the local wallet has enough balance for deposit testing.
        self.assertGreaterEqual(self.wallet.get_balance("ZRX"), 1)

        # Deposit ZRX to Coinbase Pro, and wait.
        tracking_id: str = self.market.deposit(self.wallet, "ZRX", 1)
        [received_asset_event] = self.run_parallel(
            self.market_logger.wait_for(MarketReceivedAssetEvent,
                                        timeout_seconds=1800))
        received_asset_event: MarketReceivedAssetEvent = received_asset_event
        self.assertEqual("ZRX", received_asset_event.asset_name)
        self.assertEqual(tracking_id, received_asset_event.tx_hash)
        self.assertEqual(self.wallet.address,
                         received_asset_event.from_address)
        self.assertEqual(1, received_asset_event.amount_received)

    @unittest.skipUnless(any("test_withdraw" in arg for arg in sys.argv),
                         "Withdraw test requires manual action.")
    def test_withdraw(self):
        # Ensure the market account has enough balance for withdraw testing.
        self.assertGreaterEqual(self.market.get_balance("ZRX"), 1)

        # Withdraw ZRX from Coinbase Pro to test wallet.
        self.market.withdraw(self.wallet.address, "ZRX", 1)
        [withdraw_asset_event] = self.run_parallel(
            self.market_logger.wait_for(MarketWithdrawAssetEvent))
        withdraw_asset_event: MarketWithdrawAssetEvent = withdraw_asset_event
        self.assertEqual(self.wallet.address, withdraw_asset_event.to_address)
        self.assertEqual("ZRX", withdraw_asset_event.asset_name)
        self.assertEqual(1, withdraw_asset_event.amount)
        self.assertEqual(withdraw_asset_event.fee_amount, 0)