def profit_pct(self, account_for_fee: bool = False, rate_source: Any = FixedRateSource(), first_side_quote_eth_rate: Decimal = None, second_side_quote_eth_rate: Decimal = None) -> Decimal: """ Returns a profit in percentage value (e.g. 0.01 for 1% profitability) Assumes the base token is the same in both arbitrage sides """ buy = self.first_side if self.first_side.is_buy else self.second_side sell = self.first_side if not self.first_side.is_buy else self.second_side base_conversion_pair = f"{sell.market_info.base_asset}-{buy.market_info.base_asset}" quote_conversion_pair = f"{sell.market_info.quote_asset}-{buy.market_info.quote_asset}" sell_base_to_buy_base_rate = Decimal(1) sell_quote_to_buy_quote_rate = rate_source.rate(quote_conversion_pair) buy_fee_amount = s_decimal_0 sell_fee_amount = s_decimal_0 result = s_decimal_0 if sell_quote_to_buy_quote_rate and sell_base_to_buy_base_rate: if account_for_fee: buy_trade_fee = estimate_fee(buy.market_info.market.name, False) sell_trade_fee = estimate_fee(sell.market_info.market.name, False) buy_quote_eth_rate = (first_side_quote_eth_rate if self.first_side.is_buy else second_side_quote_eth_rate) sell_quote_eth_rate = (first_side_quote_eth_rate if not self.first_side.is_buy else second_side_quote_eth_rate) if buy_quote_eth_rate is not None and buy_trade_fee.flat_fees[0].token.upper() == "ETH": buy_fee_amount = buy_trade_fee.flat_fees[0].amount / buy_quote_eth_rate else: buy_fee_amount = buy_trade_fee.fee_amount_in_quote(buy.market_info.trading_pair, buy.quote_price, buy.amount) if sell_quote_eth_rate is not None and sell_trade_fee.flat_fees[0].token.upper() == "ETH": sell_fee_amount = sell_trade_fee.flat_fees[0].amount / sell_quote_eth_rate else: sell_fee_amount = sell_trade_fee.fee_amount_in_quote(sell.market_info.trading_pair, sell.quote_price, sell.amount) buy_spent_net = (buy.amount * buy.quote_price) + buy_fee_amount sell_gained_net = (sell.amount * sell.quote_price) - sell_fee_amount sell_gained_net_in_buy_quote_currency = (sell_gained_net * sell_quote_to_buy_quote_rate / sell_base_to_buy_base_rate) result = (((sell_gained_net_in_buy_quote_currency - buy_spent_net) / buy_spent_net) if buy_spent_net != s_decimal_0 else s_decimal_0) else: self.logger().warning("The arbitrage proposal profitability could not be calculated due to a missing rate" f" ({base_conversion_pair}={sell_base_to_buy_base_rate}," f" {quote_conversion_pair}={sell_quote_to_buy_quote_rate})") return result
def test_estimate_fee(self): """ test the estimate_fee function """ # test against centralized exchanges self.assertEqual(estimate_fee("kucoin", True), TradeFee(percent=Decimal('0.001'), flat_fees=[])) self.assertEqual(estimate_fee("kucoin", False), TradeFee(percent=Decimal('0.001'), flat_fees=[])) self.assertEqual(estimate_fee("binance", True), TradeFee(percent=Decimal('0.001'), flat_fees=[])) self.assertEqual(estimate_fee("binance", False), TradeFee(percent=Decimal('0.001'), flat_fees=[])) # test against decentralized exchanges self.assertEqual(estimate_fee("beaxy", True), TradeFee(percent=Decimal('0.0015'), flat_fees=[])) self.assertEqual(estimate_fee("beaxy", False), TradeFee(percent=Decimal('0.0025'), flat_fees=[])) # test against an exchange with flat fees self.assertEqual( estimate_fee("bamboo_relay", True), TradeFee(percent=Decimal(0), flat_fees=[('ETH', Decimal('0'))])) self.assertEqual( estimate_fee("bamboo_relay", False), TradeFee(percent=Decimal(0), flat_fees=[('ETH', Decimal('0.00001'))])) # test against exchanges that do not exist in hummingbot.client.settings.CONNECTOR_SETTINGS self.assertRaisesRegex(Exception, "^Invalid connector", estimate_fee, "does_not_exist", True) self.assertRaisesRegex(Exception, "Invalid connector", estimate_fee, "does_not_exist", False)
async def _process_trade_message_rest(self, trade_msg: Dict[str, Any]): """ Updates in-flight order and trigger order filled event for trade message received from REST API. 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 str(trade_msg["order_id"]) == o.exchange_order_id ] if not track_order: return tracked_order = track_order[0] (delta_trade_amount, delta_trade_price, trade_id) = tracked_order.update_with_trade_update_rest(trade_msg) if not delta_trade_amount: 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, delta_trade_price, delta_trade_amount, # TradeFee(0.0, [(trade_msg["fee_coin_name"], Decimal(str(trade_msg["fees"])))]), estimate_fee.estimate_fee( self.name, tracked_order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER]), exchange_trade_id=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 trade status rest 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 profit_pct(self, account_for_fee: bool = False) -> Decimal: """ Returns a profit in percentage value (e.g. 0.01 for 1% profitability) """ buy = self.first_side if self.first_side.is_buy else self.second_side sell = self.first_side if not self.first_side.is_buy else self.second_side if buy.quote_price == 0: return s_decimal_nan if not account_for_fee: return (sell.quote_price - buy.quote_price) / buy.quote_price buy_trade_fee = estimate_fee(buy.market_info.market.name, False) sell_trade_fee = estimate_fee(sell.market_info.market.name, False) buy_fee_amount = buy_trade_fee.fee_amount_in_quote( buy.market_info.trading_pair, buy.quote_price, buy.amount) sell_fee_amount = sell_trade_fee.fee_amount_in_quote( sell.market_info.trading_pair, sell.quote_price, sell.amount) sell_gained_net = (sell.amount * sell.quote_price) - sell_fee_amount buy_spent_net = (buy.amount * buy.quote_price) + buy_fee_amount return ( sell_gained_net - buy_spent_net ) / buy_spent_net if buy_spent_net != s_decimal_0 else s_decimal_nan
def test_estimate_fee(self): """ test the estimate_fee function """ # test against centralized exchanges self.assertEqual( estimate_fee("kucoin", True), AddedToCostTradeFee(percent=Decimal('0.001'), flat_fees=[])) self.assertEqual( estimate_fee("kucoin", False), AddedToCostTradeFee(percent=Decimal('0.001'), flat_fees=[])) self.assertEqual( estimate_fee("binance", True), DeductedFromReturnsTradeFee(percent=Decimal('0.001'), flat_fees=[])) self.assertEqual( estimate_fee("binance", False), DeductedFromReturnsTradeFee(percent=Decimal('0.001'), flat_fees=[])) # test against decentralized exchanges self.assertEqual( estimate_fee("beaxy", True), AddedToCostTradeFee(percent=Decimal('0.0015'), flat_fees=[])) self.assertEqual( estimate_fee("beaxy", False), AddedToCostTradeFee(percent=Decimal('0.0025'), flat_fees=[])) # test against exchanges that do not exist in hummingbot.client.settings.CONNECTOR_SETTINGS self.assertRaisesRegex(Exception, "^Invalid connector", estimate_fee, "does_not_exist", True) self.assertRaisesRegex(Exception, "Invalid connector", estimate_fee, "does_not_exist", False)
def profit_pct(self, account_for_fee: bool = False, first_side_quote_eth_rate: Decimal = None, second_side_quote_eth_rate: Decimal = None) -> Decimal: """ Returns a profit in percentage value (e.g. 0.01 for 1% profitability) """ buy = self.first_side if self.first_side.is_buy else self.second_side sell = self.first_side if not self.first_side.is_buy else self.second_side if buy.quote_price == 0: return s_decimal_0 if not account_for_fee: return (sell.quote_price - buy.quote_price) / buy.quote_price buy_trade_fee = estimate_fee(buy.market_info.market.name, False) sell_trade_fee = estimate_fee(sell.market_info.market.name, False) buy_quote_eth_rate = first_side_quote_eth_rate if self.first_side.is_buy else second_side_quote_eth_rate sell_quote_eth_rate = first_side_quote_eth_rate if not self.first_side.is_buy else second_side_quote_eth_rate if buy_quote_eth_rate is not None and buy_trade_fee.flat_fees[0][ 0].upper() == "ETH": buy_fee_amount = buy_trade_fee.flat_fees[0][1] / buy_quote_eth_rate else: buy_fee_amount = buy_trade_fee.fee_amount_in_quote( buy.market_info.trading_pair, buy.quote_price, buy.amount) if sell_quote_eth_rate is not None and sell_trade_fee.flat_fees[0][ 0].upper() == "ETH": sell_fee_amount = sell_trade_fee.flat_fees[0][ 1] / sell_quote_eth_rate else: sell_fee_amount = sell_trade_fee.fee_amount_in_quote( sell.market_info.trading_pair, sell.quote_price, sell.amount) # buy_fee_amount = buy_trade_fee.fee_amount_in_quote(buy.market_info.trading_pair, # buy.quote_price, buy.amount) # sell_fee_amount = sell_trade_fee.fee_amount_in_quote(sell.market_info.trading_pair, sell.quote_price, # sell.amount) sell_gained_net = (sell.amount * sell.quote_price) - sell_fee_amount buy_spent_net = (buy.amount * buy.quote_price) + buy_fee_amount return ((sell_gained_net - buy_spent_net) / buy_spent_net) if buy_spent_net != s_decimal_0 else s_decimal_0
def _process_rest_trade_details(self, order_detail_msg: Any): for trade_msg in order_detail_msg['detail']: """ 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() tracked_order = self.find_exchange_order(trade_msg['order_id']) if tracked_order is None: return updated = tracked_order.update_with_rest_order_detail(trade_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(trade_msg["executed_price"])), Decimal(str(trade_msg["executed_amount"])), estimate_fee( self.name, tracked_order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER]), # TradeFee(0.0, [(trade_msg["fee_currency"], Decimal(str(trade_msg["fee"])))]), exchange_trade_id=trade_msg["tid"])) 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)) self.stop_tracking_order(tracked_order.client_order_id)
def apply_budget_constraint(self, proposals: List[Proposal]): balances = self._token_balances.copy() for proposal in proposals: if balances[proposal.base()] < proposal.sell.size: proposal.sell.size = balances[proposal.base()] proposal.sell.size = self._exchange.quantize_order_amount(proposal.market, proposal.sell.size) balances[proposal.base()] -= proposal.sell.size quote_size = proposal.buy.size * proposal.buy.price quote_size = balances[proposal.quote()] if balances[proposal.quote()] < quote_size else quote_size buy_fee = estimate_fee(self._exchange.name, True) buy_size = quote_size / (proposal.buy.price * (Decimal("1") + buy_fee.percent)) proposal.buy.size = self._exchange.quantize_order_amount(proposal.market, buy_size) balances[proposal.quote()] -= quote_size
def _process_order_message_traded(self, order_msg): tracked_order: DigifinexInFlightOrder = self.find_exchange_order( order_msg['id']) if tracked_order is None: return (delta_trade_amount, delta_trade_price) = tracked_order.update_with_order_update(order_msg) if not delta_trade_amount: 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, delta_trade_price, delta_trade_amount, estimate_fee( self.name, tracked_order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER]), # TradeFee(0.0, [(trade_msg["fee_currency"], Decimal(str(trade_msg["fee"])))]), exchange_trade_id=str(int(self._time() * 1e6)))) if math.isclose(tracked_order.executed_amount_base, tracked_order.amount) or \ tracked_order.executed_amount_base >= tracked_order.amount: tracked_order.last_state = "2" 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)) self.stop_tracking_order(tracked_order.client_order_id)
def get_fee(self, base_currency: str, quote_currency: str, order_type: object, order_side: object, amount: object, price: object): is_maker = order_type is OrderType.LIMIT return estimate_fee("binance_perpetual", is_maker)
async def _update_order_status(self): """ Calls REST API to get status update for each in-flight order. """ if len(self._in_flight_orders) > 0: tracked_orders = list(self._in_flight_orders.values()) tasks = [] for tracked_order in tracked_orders: order_id = await tracked_order.get_exchange_order_id() tasks.append(self._api_request("post", "perpfi/receipt", {"txHash": order_id})) update_results = await safe_gather(*tasks, return_exceptions=True) for update_result in update_results: self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") if isinstance(update_result, Exception): raise update_result if "txHash" not in update_result: self.logger().info(f"_update_order_status txHash not in resp: {update_result}") continue if update_result["confirmed"] is True: if update_result["receipt"]["status"] == 1: fee = estimate_fee("perpetual_finance", False) fee = TradeFee(fee.percent, [("XDAI", Decimal(str(update_result["receipt"]["gasUsed"])))]) 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(tracked_order.price)), Decimal(str(tracked_order.amount)), fee, exchange_trade_id=order_id, leverage=self._leverage[tracked_order.trading_pair], position=tracked_order.position ) ) 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, float(fee.fee_amount_in_quote(tracked_order.trading_pair, Decimal(str(tracked_order.price)), Decimal(str(tracked_order.amount)))), # this ignores the gas fee, which is fine for now tracked_order.order_type)) self.stop_tracking_order(tracked_order.client_order_id) else: self.logger().info( f"The market order {tracked_order.client_order_id} has failed according to order status API. ") self.trigger_event(MarketEvent.OrderFailure, MarketOrderFailureEvent( self.current_timestamp, tracked_order.client_order_id, tracked_order.order_type )) self.stop_tracking_order(tracked_order.client_order_id)