def test_get_quote_price_updates_fee_overrides_config_map( self, mocked_api, mocked_http_client): mocked_http_client.return_value = aiohttp.ClientSession() url = f"https://{self.gateway_host}:{self.gateway_port}/eth/balancer/price" mock_response = { "price": 10, "gasLimit": 30000, "gasPrice": 1, "gasCost": 2, "swaps": [], } mocked_api.post(url, body=json.dumps(mock_response)) self.connector._account_balances = {"ETH": Decimal("10000")} self.connector._allowances = {self.quote: Decimal("10000")} self.async_run_with_timeout( self.connector.get_quote_price(self.trading_pair, is_buy=True, amount=Decimal("2"))) self.assertEqual( fee_overrides_config_map["balancer_maker_fixed_fees"].value, [TokenAmount("ETH", Decimal(str("2")))]) self.assertEqual( fee_overrides_config_map["balancer_taker_fixed_fees"].value, [TokenAmount("ETH", Decimal(str("2")))])
def test_populate_collateral_fields_fixed_fees_in_quote_token(self): trade_fee_schema = TradeFeeSchema( maker_fixed_fees=[TokenAmount(self.quote_asset, Decimal("1"))], taker_fixed_fees=[TokenAmount(self.base_asset, Decimal("2"))], ) exchange = MockPaperExchange(trade_fee_schema) budget_checker: BudgetChecker = exchange.budget_checker order_candidate = OrderCandidate( trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, order_side=TradeType.BUY, amount=Decimal("10"), price=Decimal("2"), ) populated_candidate = budget_checker.populate_collateral_entries(order_candidate) self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) self.assertIsNone(populated_candidate.percent_fee_collateral) self.assertIsNone(populated_candidate.percent_fee_value) self.assertEqual(1, len(populated_candidate.fixed_fee_collaterals)) fixed_fee_collateral = populated_candidate.fixed_fee_collaterals[0] self.assertEqual(self.quote_asset, fixed_fee_collateral.token) self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) self.assertEqual(self.base_asset, populated_candidate.potential_returns.token) self.assertEqual(Decimal("10"), populated_candidate.potential_returns.amount)
def test_deducted_from_return_spot_fee_created_for_sell(self): schema = TradeFeeSchema( percent_fee_token="HBOT", maker_percent_fee_decimal=Decimal("1"), taker_percent_fee_decimal=Decimal("1"), buy_percent_fee_deducted_from_returns=False, ) fee = TradeFeeBase.new_spot_fee( fee_schema=schema, trade_type=TradeType.SELL, percent=Decimal("1.1"), percent_token="HBOT", flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))]) self.assertEqual(DeductedFromReturnsTradeFee, type(fee)) self.assertEqual(Decimal("1.1"), fee.percent) self.assertEqual("HBOT", fee.percent_token) self.assertEqual( [TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) schema.percent_fee_token = None schema.buy_percent_fee_deducted_from_returns = True fee = TradeFeeBase.new_spot_fee( fee_schema=schema, trade_type=TradeType.SELL, percent=Decimal("1.1"), percent_token="HBOT", flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))]) self.assertEqual(DeductedFromReturnsTradeFee, type(fee))
def test_calculate_fees_in_quote_for_one_trade_with_fees_different_tokens( self): rate_oracle = RateOracle() rate_oracle._prices["DAI-COINALPHA"] = Decimal("2") rate_oracle._prices["USDT-DAI"] = Decimal("0.9") RateOracle._shared_instance = rate_oracle performance_metric = PerformanceMetrics() flat_fees = [ TokenAmount(token="USDT", amount=Decimal("10")), TokenAmount(token="DAI", amount=Decimal("5")), ] trade = Trade(trading_pair="HBOT-COINALPHA", side=TradeType.BUY, price=Decimal("1000"), amount=Decimal("1"), order_type=OrderType.LIMIT, market="binance", timestamp=1640001112.223, trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"), percent_token="COINALPHA", flat_fees=flat_fees)) self.async_run_with_timeout( performance_metric._calculate_fees(quote="COINALPHA", trades=[trade])) expected_fee_amount = trade.amount * trade.price * trade.trade_fee.percent expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal( "2") expected_fee_amount += flat_fees[1].amount * Decimal("2") self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote)
def test_added_to_cost_perpetual_fee_created_when_opening_positions(self): schema = TradeFeeSchema( maker_percent_fee_decimal=Decimal("1"), taker_percent_fee_decimal=Decimal("1"), buy_percent_fee_deducted_from_returns=False, ) fee = TradeFeeBase.new_perpetual_fee( fee_schema=schema, position_action=PositionAction.OPEN, percent=Decimal("1.1"), percent_token="HBOT", flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))]) self.assertEqual(AddedToCostTradeFee, type(fee)) self.assertEqual(Decimal("1.1"), fee.percent) self.assertEqual("HBOT", fee.percent_token) self.assertEqual( [TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) schema.percent_fee_token = "HBOT" fee = TradeFeeBase.new_perpetual_fee( fee_schema=schema, position_action=PositionAction.OPEN, percent=Decimal("1.1"), percent_token="HBOT", flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))]) self.assertEqual(AddedToCostTradeFee, type(fee))
def test_json_serialization(self): amount = TokenAmount(token="HBOT-COINALPHA", amount=Decimal("1000.50")) expected_json = { "token": "HBOT-COINALPHA", "amount": "1000.50", } self.assertEqual(expected_json, amount.to_json())
def test_process_trade_update_does_not_trigger_filled_event_update_status_when_completely_filled(self): order: InFlightOrder = InFlightOrder( client_order_id="someClientOrderId", exchange_order_id="someExchangeOrderId", trading_pair=self.trading_pair, order_type=OrderType.LIMIT, trade_type=TradeType.BUY, amount=Decimal("1000.0"), creation_timestamp=1640001112.0, price=Decimal("1.0"), initial_state=OrderState.OPEN, ) self.tracker.start_tracking_order(order) fee_paid: Decimal = self.trade_fee_percent * order.amount trade_update: TradeUpdate = TradeUpdate( trade_id=1, client_order_id=order.client_order_id, exchange_order_id=order.exchange_order_id, trading_pair=order.trading_pair, fill_price=order.price, fill_base_amount=order.amount, fill_quote_amount=order.price * order.amount, fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=fee_paid)]), fill_timestamp=1, ) self.tracker.process_trade_update(trade_update) fetched_order: InFlightOrder = self.tracker.fetch_order(order.client_order_id) self.assertTrue(fetched_order.is_filled) self.assertIn(fetched_order.client_order_id, self.tracker.active_orders) self.assertNotIn(fetched_order.client_order_id, self.tracker.cached_orders) self.assertTrue( self._is_logged( "INFO", f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to " f"{order.amount}/{order.amount} {order.base_asset} has been filled.", ) ) self.assertEqual(1, len(self.order_filled_logger.event_log)) self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) order_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] self.assertIsNotNone(order_filled_event) self.assertEqual(order_filled_event.order_id, order.client_order_id) self.assertEqual(order_filled_event.price, trade_update.fill_price) self.assertEqual(order_filled_event.amount, trade_update.fill_base_amount) self.assertEqual( order_filled_event.trade_fee, AddedToCostTradeFee(flat_fees=[TokenAmount(self.quote_asset, fee_paid)]) )
def test_deducted_from_returns_json_serialization(self): token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) fee = DeductedFromReturnsTradeFee(percent=Decimal("0.5"), percent_token="COINALPHA", flat_fees=[token_amount]) expected_json = { "fee_type": DeductedFromReturnsTradeFee.type_descriptor_for_json(), "percent": "0.5", "percent_token": "COINALPHA", "flat_fees": [token_amount.to_json()] } self.assertEqual(expected_json, fee.to_json())
def test_update_with_trade_update_duplicate_trade_update(self): order: InFlightOrder = InFlightOrder( client_order_id=self.client_order_id, trading_pair=self.trading_pair, order_type=OrderType.LIMIT, trade_type=TradeType.BUY, amount=Decimal("1000.0"), creation_timestamp=1640001112.0, price=Decimal("1.0"), ) trade_update: TradeUpdate = TradeUpdate( trade_id="someTradeId", client_order_id=self.client_order_id, exchange_order_id=self.exchange_order_id, trading_pair=self.trading_pair, fill_price=Decimal("1.0"), fill_base_amount=Decimal("500.0"), fill_quote_amount=Decimal("500.0"), fee=AddedToCostTradeFee( flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * Decimal("500.0"))]), fill_timestamp=1, ) self.assertTrue(order.update_with_trade_update(trade_update)) self.assertEqual(order.executed_amount_base, trade_update.fill_base_amount) self.assertEqual(order.executed_amount_quote, trade_update.fill_quote_amount) self.assertEqual(order.last_update_timestamp, trade_update.fill_timestamp) self.assertEqual(1, len(order.order_fills)) self.assertIn(trade_update.trade_id, order.order_fills) # Ignores duplicate trade update self.assertFalse(order.update_with_trade_update(trade_update))
def test_adjust_candidate_insufficient_funds_for_flat_fees_and_percent_fees(self): trade_fee_schema = TradeFeeSchema( maker_percent_fee_decimal=Decimal("0.1"), maker_fixed_fees=[TokenAmount(self.quote_asset, Decimal("1"))], ) exchange = MockPaperExchange(trade_fee_schema) budget_checker: BudgetChecker = exchange.budget_checker exchange.set_balance(self.quote_asset, Decimal("12")) order_candidate = OrderCandidate( trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, order_side=TradeType.BUY, amount=Decimal("10"), price=Decimal("2"), ) adjusted_candidate = budget_checker.adjust_candidate(order_candidate, all_or_none=False) self.assertEqual(Decimal("5"), adjusted_candidate.amount) self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) self.assertEqual(Decimal("10"), adjusted_candidate.order_collateral.amount) self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) self.assertEqual(Decimal("1"), adjusted_candidate.percent_fee_collateral.amount) self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) self.assertEqual(Decimal("1"), adjusted_candidate.percent_fee_value.amount) self.assertEqual(1, len(adjusted_candidate.fixed_fee_collaterals)) fixed_fee_collateral = adjusted_candidate.fixed_fee_collaterals[0] self.assertEqual(self.quote_asset, fixed_fee_collateral.token) self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) self.assertEqual(Decimal("5"), adjusted_candidate.potential_returns.amount)
def test_tolerance_level(self, estimate_fee_mock): """ Test tolerance level """ estimate_fee_mock.return_value = AddedToCostTradeFee( percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))]) # initiate strategy and add active orders self.clock.add_iterator(self.default_strategy) self.clock.backtest_til(self.start_timestamp + 9) # the order tolerance is 1% # set the orders to the same values proposal = Proposal("ETH-USDT", PriceSize(100, 1), PriceSize(100, 1)) self.assertTrue( self.default_strategy.is_within_tolerance( self.default_strategy.active_orders, proposal)) # update orders to withint the tolerance proposal = Proposal("ETH-USDT", PriceSize(109, 1), PriceSize(91, 1)) self.assertTrue( self.default_strategy.is_within_tolerance( self.default_strategy.active_orders, proposal)) # push the orders beyond the tolerance, this proposal should return False proposal = Proposal("ETH-USDT", PriceSize(150, 1), PriceSize(50, 1)) self.assertFalse( self.default_strategy.is_within_tolerance( self.default_strategy.active_orders, proposal))
def test_simulate_maker_market_trade(self, estimate_fee_mock): """ Test that we can set up a liquidity mining strategy, and a trade """ estimate_fee_mock.return_value = AddedToCostTradeFee( percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] ) # initiate self.clock.add_iterator(self.default_strategy) self.clock.backtest_til(self.start_timestamp) # assert that there are no active trades on initialization and before clock has moved forward self.assertEqual(0, len(self.default_strategy.active_orders)) # advance by one tick, the strategy will initiate two orders per pair self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(4, len(self.default_strategy.active_orders)) # assert that a buy and sell order is made for each pair self.assertTrue(self.has_limit_order(self.default_strategy.active_orders, 'ETH-USDT', True, Decimal(99.95), Decimal(2.0))) self.assertTrue(self.has_limit_order(self.default_strategy.active_orders, 'ETH-USDT', False, Decimal(100.05), Decimal(2.0))) self.assertTrue(self.has_limit_order(self.default_strategy.active_orders, 'ETH-BTC', True, Decimal(99.95), Decimal(1.0005))) self.assertTrue(self.has_limit_order(self.default_strategy.active_orders, 'ETH-BTC', False, Decimal(100.05), Decimal(2))) # Simulate buy order fill self.clock.backtest_til(self.start_timestamp + 8) self.simulate_maker_market_trade(False, Decimal("50"), Decimal("1"), "ETH-USDT") self.assertEqual(3, len(self.default_strategy.active_orders))
def _process_trade_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and trigger order filled event for trade message received. Triggers order completed event if the total executed amount equals to the specified order amount. """ # Only process trade when trade fees have been accounted for; when trade status is "settled". if order_msg["status"] != "settled": return ex_order_id = order_msg["order_id"] client_order_id = None for track_order in self.in_flight_orders.values(): if track_order.exchange_order_id == ex_order_id: client_order_id = track_order.client_order_id break if client_order_id is None: return tracked_order = self.in_flight_orders[client_order_id] updated = tracked_order.update_with_trade_update(order_msg) if not updated: return self.trigger_event( MarketEvent.OrderFilled, OrderFilledEvent( self.current_timestamp, tracked_order.client_order_id, tracked_order.trading_pair, tracked_order.trade_type, tracked_order.order_type, Decimal(str(order_msg["price"])), Decimal(str(order_msg["quantity"])), AddedToCostTradeFee(flat_fees=[ TokenAmount(order_msg["fee_currency_id"], Decimal(str(order_msg["fee_amount"]))) ]), exchange_trade_id=order_msg["id"])) if math.isclose(tracked_order.executed_amount_base, tracked_order.amount) or \ tracked_order.executed_amount_base >= tracked_order.amount: tracked_order.last_state = "filled" self.logger().info( f"The {tracked_order.trade_type.name} order " f"{tracked_order.client_order_id} has completed " f"according to order status API.") event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ else MarketEvent.SellOrderCompleted event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ else SellOrderCompletedEvent self.trigger_event( event_tag, event_class( self.current_timestamp, tracked_order.client_order_id, tracked_order.base_asset, tracked_order.quote_asset, tracked_order.executed_amount_base, tracked_order.executed_amount_quote, tracked_order.order_type, tracked_order.exchange_order_id)) self.stop_tracking_order(tracked_order.client_order_id)
def test_json_serialization(self): token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) fee = DeductedFromReturnsTradeFee(percent=Decimal("0.5"), percent_token="COINALPHA", flat_fees=[token_amount]) trade_update = TradeUpdate( trade_id="12345", client_order_id="OID1", exchange_order_id="EOID1", trading_pair="HBOT-COINALPHA", fill_timestamp=1640001112, fill_price=Decimal("1000.11"), fill_base_amount=Decimal("2"), fill_quote_amount=Decimal("2000.22"), fee=fee, ) expected_json = trade_update._asdict() expected_json.update({ "fill_price": "1000.11", "fill_base_amount": "2", "fill_quote_amount": "2000.22", "fee": fee.to_json(), }) self.assertEqual(expected_json, trade_update.to_json())
def test_deducted_from_returns_json_deserialization(self): token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) fee = DeductedFromReturnsTradeFee(percent=Decimal("0.5"), percent_token="COINALPHA", flat_fees=[token_amount]) self.assertEqual(fee, TradeFeeBase.from_json(fee.to_json()))
def test_trade_update_does_not_change_exchange_order_id(self): order: InFlightOrder = InFlightOrder( client_order_id=self.client_order_id, trading_pair=self.trading_pair, order_type=OrderType.LIMIT, trade_type=TradeType.BUY, amount=Decimal("1000.0"), creation_timestamp=1640001112.0, price=Decimal("1.0"), ) trade_update: TradeUpdate = TradeUpdate( trade_id="someTradeId", client_order_id=self.client_order_id, exchange_order_id=self.exchange_order_id, trading_pair=self.trading_pair, fill_price=Decimal("1.0"), fill_base_amount=Decimal("500.0"), fill_quote_amount=Decimal("500.0"), fee=AddedToCostTradeFee( flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * Decimal("500.0"))]), fill_timestamp=1, ) self.assertTrue(order.update_with_trade_update(trade_update)) self.assertIsNone(order.exchange_order_id) self.assertFalse(order.exchange_order_id_update_event.is_set())
def test_inventory_skew(self, estimate_fee_mock): """ When inventory_skew_enabled is true, the strategy will try to balance the amounts of base to match it """ estimate_fee_mock.return_value = AddedToCostTradeFee( percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] ) # initiate with similar balances so the skew is obvious usdt_balance = 1000 busd_balance = 1000 eth_balance = 1000 btc_balance = 1000 trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD", "BTC"])) market, market_infos = self.create_market(trading_pairs, 100, {"USDT": usdt_balance, "BUSD": busd_balance, "ETH": eth_balance, "BTC": btc_balance}) skewed_base_strategy = LiquidityMiningStrategy() skewed_base_strategy.init_params( exchange=market, market_infos=market_infos, token="ETH", order_amount=Decimal(2), spread=Decimal(0.0005), inventory_skew_enabled=True, target_base_pct=Decimal(0.1), # less base, more quote order_refresh_time=5, order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change ) unskewed_strategy = LiquidityMiningStrategy() unskewed_strategy.init_params( exchange=market, market_infos=market_infos, token="ETH", order_amount=Decimal(2), spread=Decimal(0.0005), inventory_skew_enabled=False, order_refresh_time=5, target_base_pct=Decimal(0.1), # this does nothing when inventory_skew_enabled is False order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change ) self.clock.add_iterator(skewed_base_strategy) self.clock.backtest_til(self.start_timestamp + 10) self.clock.add_iterator(unskewed_strategy) self.clock.backtest_til(self.start_timestamp + 20) # iterate through pairs in skewed and unskewed strategy for unskewed_order in unskewed_strategy.active_orders: for skewed_base_order in skewed_base_strategy.active_orders: # if the trading_pair and trade type are the same, compare them if skewed_base_order.trading_pair == unskewed_order.trading_pair and \ skewed_base_order.is_buy == unskewed_order.is_buy: if skewed_base_order.is_buy: # the skewed strategy tries to buy more quote thant the unskewed one self.assertGreater(skewed_base_order.price, unskewed_order.price) else: # trying to keep less base self.assertLessEqual(skewed_base_order.price, unskewed_order.price)
async def _process_order_fill_update(self, order: InFlightOrder, fill_update: Dict[str, Any]): fills_data = fill_update["data"]["trades"] for fill_data in fills_data: fee = TradeFeeBase.new_spot_fee( fee_schema=self.trade_fee_schema(), trade_type=order.trade_type, percent_token=fill_data["fee_coin_name"], flat_fees=[ TokenAmount(amount=Decimal(fill_data["fees"]), token=fill_data["fee_coin_name"]) ]) trade_update = TradeUpdate( trade_id=str(fill_data["detail_id"]), client_order_id=order.client_order_id, exchange_order_id=str(fill_data["order_id"]), trading_pair=order.trading_pair, fee=fee, fill_base_amount=Decimal(fill_data["size"]), fill_quote_amount=Decimal(fill_data["size"]) * Decimal(fill_data["price_avg"]), fill_price=Decimal(fill_data["price_avg"]), fill_timestamp=int(fill_data["create_time"]) * 1e-3, ) self._order_tracker.process_trade_update(trade_update)
def test_single_complete_fill_is_processed_correctly(self): self.exchange.start_tracking_order( order_id="OID1", trading_pair=self.trading_pair, order_type=OrderType.LIMIT, trade_type=TradeType.BUY, price=Decimal("10000"), amount=Decimal("1"), ) order = self.exchange.in_flight_orders.get("OID1") order.update_exchange_order_id("5821066005") complete_fill = self._trade_info(2, 1, 10060.0, 30.0) message = { "channel": "user_account_usdt_orders", "data": json.dumps(complete_fill), "event": "updated", } complete_fill = self._trade_info(1, 1, 10060.0, 30.0, "filled") message["data"] = json.dumps(complete_fill) mock_user_stream = AsyncMock() mock_user_stream.get.side_effect = [message, asyncio.CancelledError()] self.exchange.user_stream_tracker._user_stream = mock_user_stream self.test_task = asyncio.get_event_loop().create_task( self.exchange._user_stream_event_listener()) try: self.async_run_with_timeout(self.test_task) except asyncio.CancelledError: pass self.assertEqual(Decimal("30"), order.fee_paid) self.assertEqual(1, len(self.order_filled_logger.event_log)) fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) self.assertEqual([ TokenAmount(complete_fill["funding_currency"], Decimal(complete_fill["order_fee"])) ], fill_event.trade_fee.flat_fees) self.assertTrue( self._is_logged( "INFO", f"The market buy order {order.client_order_id} has completed according to Liquid user stream." )) self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) buy_complete_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[ 0] self.assertEqual(Decimal(30), buy_complete_event.fee_amount) self.assertEqual(complete_fill["funding_currency"], buy_complete_event.fee_asset)
def test_added_to_cost_json_deserialization(self): token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) fee = AddedToCostTradeFee(percent=Decimal("0.5"), percent_token="COINALPHA", flat_fees=[token_amount]) self.assertEqual(fee, TradeFeeBase.from_json(fee.to_json()))
def test_adjust_candidate_insufficient_funds_for_flat_fees_and_percent_fees_third_token( self): fc_token = "PFC" trade_fee_schema = TradeFeeSchema( percent_fee_token=fc_token, maker_percent_fee_decimal=Decimal("0.01"), taker_percent_fee_decimal=Decimal("0.01"), maker_fixed_fees=[TokenAmount(fc_token, Decimal("1"))]) exchange = MockPaperExchange(client_config_map=ClientConfigAdapter( ClientConfigMap()), trade_fee_schema=trade_fee_schema) pfc_quote_pair = combine_to_hb_trading_pair(self.quote_asset, fc_token) exchange.set_balanced_order_book( # the quote to pfc price will be 1:2 trading_pair=pfc_quote_pair, mid_price=1.5, min_price=1, max_price=2, price_step_size=1, volume_step_size=1, ) budget_checker: BudgetChecker = exchange.budget_checker exchange.set_balance(self.quote_asset, Decimal("20")) exchange.set_balance( fc_token, Decimal("1.2") ) # 0.2 less than required; will result in 50% order reduction order_candidate = OrderCandidate( trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, order_side=TradeType.BUY, amount=Decimal("10"), price=Decimal("2"), ) adjusted_candidate = budget_checker.adjust_candidate(order_candidate, all_or_none=False) self.assertEqual(Decimal("5"), adjusted_candidate.amount) self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) self.assertEqual(Decimal("10"), adjusted_candidate.order_collateral.amount) self.assertEqual(fc_token, adjusted_candidate.percent_fee_collateral.token) self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_collateral.amount) self.assertEqual(fc_token, adjusted_candidate.percent_fee_value.token) self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_value.amount) self.assertEqual(1, len(adjusted_candidate.fixed_fee_collaterals)) fixed_fee_collateral = adjusted_candidate.fixed_fee_collaterals[0] self.assertEqual(fc_token, fixed_fee_collateral.token) self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) self.assertEqual(Decimal("5"), adjusted_candidate.potential_returns.amount)
async def test_arb_not_profitable_from_gas_prices(self): self.amm_1.set_prices(TRADING_PAIR, True, 101) self.amm_1.set_prices(TRADING_PAIR, False, 100) self.amm_2.set_prices(TRADING_PAIR, True, 110) self.amm_2.set_prices(TRADING_PAIR, False, 109) self.amm_1.network_transaction_fee = TokenAmount("ETH", Decimal("0.01")) await asyncio.sleep(2) taker_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders self.assertTrue(len(taker_orders) == 0)
def test_updating_order_states_with_both_process_order_update_and_process_trade_update( self): order: InFlightOrder = InFlightOrder( client_order_id="someClientOrderId", trading_pair=self.trading_pair, order_type=OrderType.LIMIT, trade_type=TradeType.BUY, amount=Decimal("1000.0"), creation_timestamp=1640001112.0, price=Decimal("1.0"), ) self.tracker.start_tracking_order(order) order_creation_update: OrderUpdate = OrderUpdate( client_order_id=order.client_order_id, exchange_order_id="someExchangeOrderId", trading_pair=self.trading_pair, update_timestamp=1, new_state=OrderState.OPEN, ) update_future = self.tracker.process_order_update( order_creation_update) self.async_run_with_timeout(update_future) open_order: InFlightOrder = self.tracker.fetch_tracked_order( order.client_order_id) # Check order_creation_update has been successfully applied self.assertEqual(open_order.exchange_order_id, order_creation_update.exchange_order_id) self.assertTrue(open_order.exchange_order_id_update_event.is_set()) self.assertEqual(open_order.current_state, order_creation_update.new_state) self.assertTrue(open_order.is_open) self.assertEqual(0, len(open_order.order_fills)) trade_filled_price: Decimal = order.price trade_filled_amount: Decimal = order.amount fee_paid: Decimal = self.trade_fee_percent * trade_filled_amount trade_update: TradeUpdate = TradeUpdate( trade_id=1, client_order_id=order.client_order_id, exchange_order_id=order.exchange_order_id, trading_pair=order.trading_pair, fill_price=trade_filled_price, fill_base_amount=trade_filled_amount, fill_quote_amount=trade_filled_price * trade_filled_amount, fee=AddedToCostTradeFee(flat_fees=[ TokenAmount(token=self.quote_asset, amount=fee_paid) ]), fill_timestamp=2, ) self.tracker.process_trade_update(trade_update) self.assertEqual(1, len(self.tracker.active_orders)) self.assertEqual(0, len(self.tracker.cached_orders))
async def test_arb_profitable_after_gas_prices(self): self.amm_1.set_prices(TRADING_PAIR, True, 101) self.amm_1.set_prices(TRADING_PAIR, False, 100) self.amm_2.set_prices(TRADING_PAIR, True, 105) self.amm_2.set_prices(TRADING_PAIR, False, 104) self.amm_1.network_transaction_fee = TokenAmount("ETH", Decimal("0.0002")) await asyncio.sleep(2) placed_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders self.assertEqual(2, len(placed_orders))
def _populate_percent_fee_value(self, exchange: 'ExchangeBase', fee: TradeFeeBase): cost_impact = fee.get_fee_impact_on_order_cost(self, exchange) if cost_impact is not None: self.percent_fee_value = cost_impact else: returns_impact = fee.get_fee_impact_on_order_returns(self, exchange) if returns_impact is not None: impact_token = self.potential_returns.token self.percent_fee_value = TokenAmount(impact_token, returns_impact)
def get_size_token_and_order_size(self) -> TokenAmount: trading_pair = self.trading_pair base, quote = split_hb_trading_pair(trading_pair) if self.order_side == TradeType.BUY: order_size = self.amount * self.price size_token = quote else: order_size = self.amount size_token = base return TokenAmount(size_token, order_size)
def test_update_order_fills_from_trades_with_repeated_fill_triggers_only_one_event(self, mock_api): self.exchange._set_current_timestamp(1640780000) self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) trade_fill_non_tracked_order = { "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), "id": 30000, "orderId": 99999, "orderListId": -1, "price": "4.00000100", "qty": "12.00000000", "quoteQty": "48.000012", "commission": "10.10000000", "commissionAsset": "BNB", "time": 1499865549590, "isBuyer": True, "isMaker": False, "isBestMatch": True } mock_response = [trade_fill_non_tracked_order, trade_fill_non_tracked_order] mock_api.get(regex_url, body=json.dumps(mock_response)) self.exchange.add_exchange_order_ids_from_market_recorder( {str(trade_fill_non_tracked_order["orderId"]): "OID99"}) self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) request = self._all_executed_requests(mock_api, url)[0] self.validate_auth_credentials_present(request) request_params = request.kwargs["params"] self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) self.assertEqual(1, len(self.order_filled_logger.event_log)) fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] self.assertEqual(float(trade_fill_non_tracked_order["time"]) * 1e-3, fill_event.timestamp) self.assertEqual("OID99", fill_event.order_id) self.assertEqual(self.trading_pair, fill_event.trading_pair) self.assertEqual(TradeType.BUY, fill_event.trade_type) self.assertEqual(OrderType.LIMIT, fill_event.order_type) self.assertEqual(Decimal(trade_fill_non_tracked_order["price"]), fill_event.price) self.assertEqual(Decimal(trade_fill_non_tracked_order["qty"]), fill_event.amount) self.assertEqual(0.0, fill_event.trade_fee.percent) self.assertEqual([ TokenAmount(trade_fill_non_tracked_order["commissionAsset"], Decimal(trade_fill_non_tracked_order["commission"]))], fill_event.trade_fee.flat_fees) self.assertTrue(self.is_logged( "INFO", f"Recreating missing trade in TradeFill: {trade_fill_non_tracked_order}" ))
async def _process_trade_message(self, trade_msg: Dict[str, Any]): """ Updates in-flight order and trigger order filled event for trade message received. Triggers order completed event if the total executed amount equals to the specified order amount. """ for order in self._in_flight_orders.values(): await order.get_exchange_order_id() track_order = [ o for o in self._in_flight_orders.values() if trade_msg["order_id"] == o.exchange_order_id ] if not track_order: return tracked_order = track_order[0] updated = tracked_order.update_with_trade_update(trade_msg) if not updated: return self.logger().info("_process_trade_message") self.logger().info(trade_msg) self.trigger_event( MarketEvent.OrderFilled, OrderFilledEvent(self.current_timestamp, tracked_order.client_order_id, tracked_order.trading_pair, tracked_order.trade_type, tracked_order.order_type, Decimal(str(trade_msg["traded_price"])), Decimal(str(trade_msg["traded_quantity"])), AddedToCostTradeFee(flat_fees=[ TokenAmount(trade_msg["fee_currency"], Decimal(str(trade_msg["fee"]))) ]), exchange_trade_id=trade_msg["trade_id"])) if math.isclose(tracked_order.executed_amount_base, tracked_order.amount) or \ tracked_order.executed_amount_base >= tracked_order.amount: tracked_order.last_state = "FILLED" self.logger().info( f"The {tracked_order.trade_type.name} order " f"{tracked_order.client_order_id} has completed " f"according to order status API.") event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ else MarketEvent.SellOrderCompleted event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ else SellOrderCompletedEvent self.trigger_event( event_tag, event_class(self.current_timestamp, tracked_order.client_order_id, tracked_order.base_asset, tracked_order.quote_asset, tracked_order.fee_asset, tracked_order.executed_amount_base, tracked_order.executed_amount_quote, tracked_order.fee_paid, tracked_order.order_type)) self.stop_tracking_order(tracked_order.client_order_id)
def test_budget_allocation(self, estimate_fee_mock): """ Liquidity mining strategy budget allocation is different from pmm, it depends on the token base and it splits its budget between the quote tokens. """ estimate_fee_mock.return_value = AddedToCostTradeFee( percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))]) # initiate usdt_balance = 1000 busd_balance = 900 eth_balance = 100 btc_balance = 10 trading_pairs = list( map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD", "BTC"])) market, market_infos = self.create_market( trading_pairs, 100, { "USDT": usdt_balance, "BUSD": busd_balance, "ETH": eth_balance, "BTC": btc_balance }) strategy = LiquidityMiningStrategy() strategy.init_params( exchange=market, market_infos=market_infos, token="ETH", order_amount=Decimal(2), spread=Decimal(0.0005), inventory_skew_enabled=False, target_base_pct=Decimal(0.5), order_refresh_time=5, order_refresh_tolerance_pct=Decimal( 0.1), # tolerance of 10 % change ) self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + 10) # there should be a buy and sell budget for each pair self.assertEqual(len(strategy.sell_budgets), 3) self.assertEqual(len(strategy.buy_budgets), 3) # the buy budgets use all of the available balance for the quote tokens self.assertEqual(strategy.buy_budgets["ETH-USDT"], usdt_balance) self.assertEqual(strategy.buy_budgets["ETH-BTC"], btc_balance) self.assertEqual(strategy.buy_budgets["ETH-BUSD"], busd_balance) # the sell budget tries to evenly split the base token between the quote tokens self.assertLess(strategy.sell_budgets["ETH-USDT"], eth_balance * 0.4) self.assertLess(strategy.sell_budgets["ETH-BTC"], eth_balance * 0.4) self.assertLess(strategy.sell_budgets["ETH-BUSD"], eth_balance * 0.4)
def test_calculate_fees_in_quote_for_one_trade_fill_with_fees_different_tokens( self): rate_oracle = RateOracle() rate_oracle._prices["DAI-COINALPHA"] = Decimal("2") rate_oracle._prices["USDT-DAI"] = Decimal("0.9") RateOracle._shared_instance = rate_oracle performance_metric = PerformanceMetrics() flat_fees = [ TokenAmount(token="USDT", amount=Decimal("10")), TokenAmount(token="DAI", amount=Decimal("5")), ] trade = TradeFill( config_file_path="some-strategy.yml", strategy="pure_market_making", market="binance", symbol="HBOT-COINALPHA", base_asset="HBOT", quote_asset="COINALPHA", timestamp=int(time.time()), order_id="someId0", trade_type="BUY", order_type="LIMIT", price=1000, amount=1, trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"), percent_token="COINALPHA", flat_fees=flat_fees).to_json(), exchange_trade_id="someExchangeId0", position=PositionAction.NIL.value, ) self.async_run_with_timeout( performance_metric._calculate_fees(quote="COINALPHA", trades=[trade])) expected_fee_amount = Decimal(str(trade.amount)) * Decimal( str(trade.price)) * Decimal("0.1") expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal( "2") expected_fee_amount += flat_fees[1].amount * Decimal("2") self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote)