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)
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))
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)
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)
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)