def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: rules: list = exchange_info_dict.get("symbols", []) return_val: list = [] for rule in rules: try: trading_pair = convert_from_exchange_trading_pair(rule["symbol"]) filters = rule["filters"] filt_dict = {fil["filterType"]: fil for fil in filters} min_order_size = Decimal(filt_dict.get("LOT_SIZE").get("minQty")) step_size = Decimal(filt_dict.get("LOT_SIZE").get("stepSize")) tick_size = Decimal(filt_dict.get("PRICE_FILTER").get("tickSize")) # TODO: BINANCE PERPETUALS DOES NOT HAVE A MIN NOTIONAL VALUE, NEED TO CREATE NEW DERIVATIVES INFRASTRUCTURE # min_notional = 0 return_val.append( TradingRule(trading_pair, min_order_size=min_order_size, min_price_increment=Decimal(tick_size), min_base_amount_increment=Decimal(step_size), # min_notional_size=Decimal(min_notional)) ) ) except Exception as e: self.logger().error(f"Error parsing the trading pair rule {rule}. Error: {e}. Skipping...", exc_info=True) return return_val
async def fetch_trading_pairs(domain=None) -> List[str]: try: from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_utils import convert_from_exchange_trading_pair BASE_URL = TESTNET_BASE_URL if domain == "binance_perpetual_testnet" else PERPETUAL_BASE_URL async with aiohttp.ClientSession() as client: async with client.get(EXCHANGE_INFO_URL.format(BASE_URL), timeout=10) as response: if response.status == 200: data = await response.json() raw_trading_pairs = [ d["symbol"] for d in data["symbols"] if d["status"] == "TRADING" ] trading_pair_list: List[str] = [] for raw_trading_pair in raw_trading_pairs: try: trading_pair = convert_from_exchange_trading_pair( raw_trading_pair) if trading_pair is not None: trading_pair_list.append(trading_pair) else: continue except Exception: pass return trading_pair_list except Exception: pass return []
async def fetch_trading_pairs( domain: str = CONSTANTS.DOMAIN, throttler: Optional[AsyncThrottler] = None) -> List[str]: url = utils.rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL, domain=domain) throttler = throttler or BinancePerpetualAPIOrderBookDataSource._get_throttler_instance( ) async with throttler.execute_task( limit_id=CONSTANTS.EXCHANGE_INFO_URL): async with aiohttp.ClientSession() as client: async with client.get(url=url, timeout=10) as response: if response.status == 200: data = await response.json() # fetch d["pair"] for binance perpetual raw_trading_pairs = [ d["pair"] for d in data["symbols"] if d["status"] == "TRADING" ] trading_pair_targets = [ f"{d['baseAsset']}-{d['quoteAsset']}" for d in data["symbols"] if d["status"] == "TRADING" ] trading_pair_list: List[str] = [] for raw_trading_pair, pair_target in zip( raw_trading_pairs, trading_pair_targets): trading_pair = utils.convert_from_exchange_trading_pair( raw_trading_pair) if trading_pair is not None and trading_pair == pair_target: trading_pair_list.append(trading_pair) return trading_pair_list return []
async def _update_positions(self): # local_position_names = set(self._account_positions.keys()) # remote_position_names = set() positions = await self.request(path="/fapi/v2/positionRisk", add_timestamp=True, is_signed=True) for position in positions: trading_pair = position.get("symbol") position_side = PositionSide[position.get("positionSide")] unrealized_pnl = Decimal(position.get("unRealizedProfit")) entry_price = Decimal(position.get("entryPrice")) amount = Decimal(position.get("positionAmt")) leverage = Decimal(position.get("leverage")) if amount != 0: self._account_positions[trading_pair + position_side.name] = Position( trading_pair=convert_from_exchange_trading_pair(trading_pair), position_side=position_side, unrealized_pnl=unrealized_pnl, entry_price=entry_price, amount=amount, leverage=leverage ) else: if (trading_pair + position_side.name) in self._account_positions: del self._account_positions[trading_pair + position_side.name]
async def _user_stream_event_listener(self): async for event_message in self._iter_user_event_queue(): try: event_type = event_message.get("e") if event_type == "ORDER_TRADE_UPDATE": order_message = event_message.get("o") client_order_id = order_message.get("c") # If the order has already been cancelled if client_order_id not in self._in_flight_orders: continue tracked_order = self._in_flight_orders.get(client_order_id) tracked_order.update_with_execution_report(event_message) # Execution Type: Trade => Filled trade_type = TradeType.BUY if order_message.get("S") == "BUY" else TradeType.SELL if order_message.get("X") in ["PARTIALLY_FILLED", "FILLED"]: order_filled_event = OrderFilledEvent( timestamp=event_message.get("E") * 1e-3, order_id=client_order_id, trading_pair=convert_from_exchange_trading_pair(order_message.get("s")), trade_type=trade_type, order_type=OrderType.LIMIT if order_message.get("o") == "LIMIT" else OrderType.MARKET, price=Decimal(order_message.get("L")), amount=Decimal(order_message.get("l")), trade_fee=self.get_fee( base_currency=tracked_order.base_asset, quote_currency=tracked_order.quote_asset, order_type=tracked_order.order_type, order_side=trade_type, amount=Decimal(order_message.get("q")), price=Decimal(order_message.get("p")) ), exchange_trade_id=order_message.get("t") ) self.trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, order_filled_event) if tracked_order.is_done: if not tracked_order.is_failure: event_tag = None event_class = None if trade_type is TradeType.BUY: event_tag = self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG event_class = BuyOrderCompletedEvent else: event_tag = self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG event_class = SellOrderCompletedEvent self.logger().info(f"The {tracked_order.order_type.name.lower()} {trade_type} order {client_order_id} has completed " f"according to websocket delta.") self.trigger_event(event_tag, event_class(self.current_timestamp, client_order_id, tracked_order.base_asset, tracked_order.quote_asset, (tracked_order.fee_asset or tracked_order.quote_asset), tracked_order.executed_amount_base, tracked_order.executed_amount_quote, tracked_order.fee_paid, tracked_order.order_type)) else: if tracked_order.is_cancelled: if tracked_order.client_order_id in self._in_flight_orders: self.logger().info(f"Successfully cancelled order {tracked_order.client_order_id} according to websocket delta.") self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG, OrderCancelledEvent(self.current_timestamp, tracked_order.client_order_id)) else: self.logger().info(f"The {tracked_order.order_type.name.lower()} order {tracked_order.client_order_id} has failed " f"according to websocket delta.") self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, MarketOrderFailureEvent(self.current_timestamp, tracked_order.client_order_id, tracked_order.order_type)) self.stop_tracking_order(tracked_order.client_order_id) elif event_type == "ACCOUNT_UPDATE": update_data = event_message.get("a", {}) # update balances for asset in update_data.get("B", []): asset_name = asset["a"] self._account_balances[asset_name] = Decimal(asset["wb"]) self._account_available_balances[asset_name] = Decimal(asset["cw"]) # update position for asset in update_data.get("P", []): position = self._account_positions.get(f"{asset['s']}{asset['ps']}", None) if position is not None: position.update_position(position_side=PositionSide[asset["ps"]], unrealized_pnl = Decimal(asset["up"]), entry_price = Decimal(asset["ep"]), amount = Decimal(asset["pa"])) else: await self._update_positions() elif event_type == "MARGIN_CALL": positions = event_message.get("p", []) total_maint_margin_required = 0 # total_pnl = 0 negative_pnls_msg = "" for position in positions: existing_position = self._account_positions.get(f"{asset['s']}{asset['ps']}", None) if existing_position is not None: existing_position.update_position(position_side=PositionSide[asset["ps"]], unrealized_pnl = Decimal(asset["up"]), amount = Decimal(asset["pa"])) total_maint_margin_required += position.get("mm", 0) if position.get("up", 0) < 1: negative_pnls_msg += f"{position.get('s')}: {position.get('up')}, " self.logger().warning("Margin Call: Your position risk is too high, and you are at risk of " "liquidation. Close your positions or add additional margin to your wallet.") self.logger().info(f"Margin Required: {total_maint_margin_required}. Total Unrealized PnL: " f"{negative_pnls_msg}. Negative PnL assets: {negative_pnls_msg}.") except asyncio.CancelledError: raise except Exception as e: self.logger().error(f"Unexpected error in user stream listener loop: {e}", exc_info=True) await asyncio.sleep(5.0)
def test_parse_three_letters_base_and_four_letters_quote(self): parsed_pair = utils.convert_from_exchange_trading_pair("BTCUSDT") self.assertEqual(parsed_pair, "BTC-USDT")
def test_parse_three_letters_base_and_three_letters_quote_matching_with_a_four_letters_quote_candidate(self): parsed_pair = utils.convert_from_exchange_trading_pair("VETUSD") self.assertEqual(parsed_pair, "VET-USD")