diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_api_order_book_data_source.py b/hummingbot/connector/exchange/genius_yield/genius_yield_api_order_book_data_source.py index f83a9fd2600..d61b75be7d8 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_api_order_book_data_source.py @@ -42,14 +42,14 @@ async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any :return: the response from the exchange (JSON dictionary) """ + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) params = { - "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), - "limit": "1000" + "market-id": symbol } rest_assistant = await self._api_factory.get_rest_assistant() data = await rest_assistant.execute_request( - url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self._domain), + url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL.format(market_id=symbol), domain=self._domain), params=params, method=RESTMethod.GET, throttler_limit_id=CONSTANTS.SNAPSHOT_PATH_URL, @@ -69,14 +69,14 @@ async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): if "result" not in raw_message: - trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["s"]) + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["market_id"]) trade_message = GeniusYieldOrderBook.trade_message_from_exchange( raw_message, {"trading_pair": trading_pair}) message_queue.put_nowait(trade_message) async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): if "result" not in raw_message: - trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["s"]) + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["market_id"]) order_book_message: OrderBookMessage = GeniusYieldOrderBook.diff_message_from_exchange( raw_message, time.time(), {"trading_pair": trading_pair}) message_queue.put_nowait(order_book_message) diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_api_user_stream_data_source.py b/hummingbot/connector/exchange/genius_yield/genius_yield_api_user_stream_data_source.py index fe521b6b16c..dc088a47300 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_api_user_stream_data_source.py @@ -49,7 +49,6 @@ async def _listen_for_user_stream(self): while True: try: data = await self._request_user_stream() - # Process the user stream data except asyncio.CancelledError: raise except Exception as e: diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_auth.py b/hummingbot/connector/exchange/genius_yield/genius_yield_auth.py index cb3aa99bc18..7443f806f36 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_auth.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_auth.py @@ -1,7 +1,6 @@ import hashlib import hmac import json -from collections import OrderedDict from typing import Any, Dict from urllib.parse import urlencode @@ -23,13 +22,11 @@ async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: :param request: the request to be configured for authenticated interaction """ if request.method == RESTMethod.POST: - request.data = self.add_auth_to_params(params=json.loads(request.data)) + request.data = json.dumps(self.add_auth_to_params(params=json.loads(request.data))) else: request.params = self.add_auth_to_params(params=request.params) - headers = {} - if request.headers is not None: - headers.update(request.headers) + headers = request.headers or {} headers.update(self.header_for_authentication()) request.headers = headers @@ -38,7 +35,7 @@ async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: def add_auth_to_params(self, params: Dict[str, Any]) -> Dict[str, Any]: timestamp = int(self.time_provider.time() * 1e3) - request_params = OrderedDict(params or {}) + request_params = params or {} request_params["timestamp"] = timestamp signature = self._generate_signature(params=request_params) @@ -47,7 +44,7 @@ def add_auth_to_params(self, params: Dict[str, Any]) -> Dict[str, Any]: return request_params def header_for_authentication(self) -> Dict[str, str]: - return {"X-MBX-APIKEY": self.api_key} + return {"api-key": self.api_key} def _generate_signature(self, params: Dict[str, Any]) -> str: encoded_params_str = urlencode(params) diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_constants.py b/hummingbot/connector/exchange/genius_yield/genius_yield_constants.py index a04d52d44c6..74f81e07796 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_constants.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_constants.py @@ -7,7 +7,7 @@ MAX_ORDER_ID_LEN = 32 # Base URL -REST_URL = "https://api.genius_yield.{}/api/" +REST_URL = "https://api.genius_yield.{}/v0/" WSS_URL = "wss://stream.genius_yield.{}:9443/ws" PUBLIC_API_VERSION = "v0" @@ -25,6 +25,7 @@ ACCOUNTS_PATH_URL = "/balances/{address}" MY_TRADES_PATH_URL = "/orders/details/{nft-token}" ORDER_PATH_URL = "/orders" +CANCEL_ORDER_PATH_URL = "/orders" GENIUS_YIELD_USER_STREAM_PATH_URL = "/userDataStream" WS_HEARTBEAT_TIME_INTERVAL = 30 diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_exchange.py b/hummingbot/connector/exchange/genius_yield/genius_yield_exchange.py index b879d136350..5d8a08b818e 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_exchange.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_exchange.py @@ -110,7 +110,14 @@ def supported_order_types(self): return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] async def get_all_pairs_prices(self) -> List[Dict[str, str]]: - pairs_prices = await self._api_get(path_url=CONSTANTS.TICKER_BOOK_PATH_URL) + response = await self._api_get(path_url=CONSTANTS.ALL_MARKETS_PATH_URL) + pairs_prices = [ + { + "symbol": market["market_id"], + "price": market["base_close"] + } + for market in response + ] return pairs_prices def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): @@ -167,24 +174,20 @@ async def _place_order(self, type_str = GeniusYieldExchange.genius_yield_order_type(order_type) side_str = CONSTANTS.SIDE_BUY if trade_type is TradeType.BUY else CONSTANTS.SIDE_SELL symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) - api_params = {"symbol": symbol, - "side": side_str, - "quantity": amount_str, - "type": type_str, - "newClientOrderId": order_id} - if order_type is OrderType.LIMIT or order_type is OrderType.LIMIT_MAKER: - price_str = f"{price:f}" - api_params["price"] = price_str - if order_type == OrderType.LIMIT: - api_params["timeInForce"] = CONSTANTS.TIME_IN_FORCE_GTC + api_params = { + "offer_token": symbol.split('_')[0], + "offer_amount": str(amount), + "price_token": symbol.split('_')[1], + "price_amount": str(price) + } try: order_result = await self._api_post( path_url=CONSTANTS.ORDER_PATH_URL, data=api_params, is_auth_required=True) - o_id = str(order_result["orderId"]) - transact_time = order_result["transactTime"] * 1e-3 + o_id = str(order_result["transaction_id"]) + transact_time = self._time_synchronizer.time() except IOError as e: error_description = str(e) is_server_overloaded = ("status is 503" in error_description @@ -197,97 +200,52 @@ async def _place_order(self, return o_id, transact_time async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): - symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) api_params = { - "symbol": symbol, - "origClientOrderId": order_id, + "order_references": [order_id] } cancel_result = await self._api_delete( - path_url=CONSTANTS.ORDER_PATH_URL, - params=api_params, + path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, + data=api_params, is_auth_required=True) - if cancel_result.get("status") == "CANCELED": + if cancel_result.get("transaction_id"): return True return False async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: - """ - Example: - { - "symbol": "ETHBTC", - "baseAssetPrecision": 8, - "quotePrecision": 8, - "orderTypes": ["LIMIT", "MARKET"], - "filters": [ - { - "filterType": "PRICE_FILTER", - "minPrice": "0.00000100", - "maxPrice": "100000.00000000", - "tickSize": "0.00000100" - }, { - "filterType": "LOT_SIZE", - "minQty": "0.00100000", - "maxQty": "100000.00000000", - "stepSize": "0.00100000" - }, { - "filterType": "MIN_NOTIONAL", - "minNotional": "0.00100000" - } - ] - } - """ - trading_pair_rules = exchange_info_dict.get("symbols", []) + trading_pair_rules = exchange_info_dict retval = [] - for rule in filter(genius_yield_utils.is_exchange_information_valid, trading_pair_rules): + for rule in trading_pair_rules: try: - trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule.get("symbol")) - filters = rule.get("filters") - price_filter = [f for f in filters if f.get("filterType") == "PRICE_FILTER"][0] - lot_size_filter = [f for f in filters if f.get("filterType") == "LOT_SIZE"][0] - min_notional_filter = [f for f in filters if f.get("filterType") in ["MIN_NOTIONAL", "NOTIONAL"]][0] - - min_order_size = Decimal(lot_size_filter.get("minQty")) - tick_size = price_filter.get("tickSize") - step_size = Decimal(lot_size_filter.get("stepSize")) - min_notional = Decimal(min_notional_filter.get("minNotional")) + trading_pair = combine_to_hb_trading_pair(base=rule["base_asset"], quote=rule["target_asset"]) + min_order_size = Decimal(rule["filters"]["minQty"]) + tick_size = Decimal(rule["filters"]["tickSize"]) + step_size = Decimal(rule["filters"]["stepSize"]) + min_notional = Decimal(rule["filters"]["minNotional"]) retval.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))) + min_price_increment=tick_size, + min_base_amount_increment=step_size, + min_notional_size=min_notional)) except Exception: self.logger().exception(f"Error parsing the trading pair rule {rule}. Skipping.") return retval - async def _status_polling_loop_fetch_updates(self): - await self._update_order_fills_from_trades() - await super()._status_polling_loop_fetch_updates() - - async def _update_trading_fees(self): - """ - Update fees information from the exchange - """ - pass - async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: - trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) updated_order_data = await self._api_get( - path_url=CONSTANTS.ORDER_PATH_URL, - params={ - "symbol": trading_pair, - "origClientOrderId": tracked_order.client_order_id}, + path_url=f"{CONSTANTS.ORDER_PATH_URL}/{tracked_order.exchange_order_id}", is_auth_required=True) new_state = CONSTANTS.ORDER_STATE[updated_order_data["status"]] order_update = OrderUpdate( client_order_id=tracked_order.client_order_id, - exchange_order_id=str(updated_order_data["orderId"]), + exchange_order_id=str(updated_order_data["transaction_id"]), trading_pair=tracked_order.trading_pair, - update_timestamp=updated_order_data["updateTime"] * 1e-3, + update_timestamp=self._time_synchronizer.time(), new_state=new_state, ) @@ -301,11 +259,10 @@ async def _update_balances(self): path_url=CONSTANTS.ACCOUNTS_PATH_URL, is_auth_required=True) - balances = account_info["balances"] - for balance_entry in balances: - asset_name = balance_entry["asset"] - free_balance = Decimal(balance_entry["free"]) - total_balance = Decimal(balance_entry["free"]) + Decimal(balance_entry["locked"]) + balances = account_info + for asset_name, balance in balances.items(): + free_balance = Decimal(balance) + total_balance = free_balance self._account_available_balances[asset_name] = free_balance self._account_balances[asset_name] = total_balance remote_asset_names.add(asset_name) @@ -317,14 +274,14 @@ async def _update_balances(self): def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): mapping = bidict() - for symbol_data in filter(genius_yield_utils.is_exchange_information_valid, exchange_info["symbols"]): - mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data["baseAsset"], - quote=symbol_data["quoteAsset"]) + for symbol_data in exchange_info: + mapping[symbol_data["market_id"]] = combine_to_hb_trading_pair(base=symbol_data["base_asset"], + quote=symbol_data["target_asset"]) self._set_trading_pair_symbol_map(mapping) async def _get_last_traded_price(self, trading_pair: str) -> float: params = { - "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + "market-id": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) } resp_json = await self._api_request( @@ -333,4 +290,5 @@ async def _get_last_traded_price(self, trading_pair: str) -> float: params=params ) - return float(resp_json["lastPrice"]) + return float(resp_json["price"]) + diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_order_book.py b/hummingbot/connector/exchange/genius_yield/genius_yield_order_book.py index 0e953475c54..59d8d257c7d 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_order_book.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_order_book.py @@ -22,11 +22,9 @@ def snapshot_message_from_exchange(cls, :param metadata: a dictionary with extra information to add to the snapshot data :return: a snapshot message with the snapshot information received from the exchange """ - if metadata: - msg.update(metadata) return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { - "trading_pair": msg["trading_pair"], - "update_id": msg["lastUpdateId"], + "market_id": msg["market_pair_id"], + "update_id": msg["timestamp"], "bids": msg["bids"], "asks": msg["asks"] }, timestamp=timestamp) @@ -43,14 +41,12 @@ def diff_message_from_exchange(cls, :param metadata: a dictionary with extra information to add to the difference data :return: a diff message with the changes in the order book notified by the exchange """ - if metadata: - msg.update(metadata) return OrderBookMessage(OrderBookMessageType.DIFF, { - "trading_pair": msg["trading_pair"], - "first_update_id": msg["U"], - "update_id": msg["u"], - "bids": msg["b"], - "asks": msg["a"] + "market_id": msg["market_pair_id"], + "first_update_id": msg["first_update_id"], + "update_id": msg["last_update_id"], + "bids": msg["bids"], + "asks": msg["asks"] }, timestamp=timestamp) @classmethod @@ -61,14 +57,12 @@ def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dic :param metadata: a dictionary with extra information to add to trade message :return: a trade message with the details of the trade as provided by the exchange """ - if metadata: - msg.update(metadata) - ts = msg["E"] + ts = msg["timestamp"] return OrderBookMessage(OrderBookMessageType.TRADE, { - "trading_pair": msg["trading_pair"], - "trade_type": float(TradeType.SELL.value) if msg["m"] else float(TradeType.BUY.value), - "trade_id": msg["t"], + "market_id": msg["market_pair_id"], + "trade_type": float(TradeType.SELL.value) if msg["is_sell"] else float(TradeType.BUY.value), + "trade_id": msg["transaction_id"], "update_id": ts, - "price": msg["p"], - "amount": msg["q"] + "price": msg["price"], + "amount": msg["amount"] }, timestamp=ts * 1e-3) diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_utils.py b/hummingbot/connector/exchange/genius_yield/genius_yield_utils.py index 228d98e01ee..722b3bf4f54 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_utils.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_utils.py @@ -7,7 +7,7 @@ from hummingbot.core.data_type.trade_fee import TradeFeeSchema CENTRALIZED = True -EXAMPLE_PAIR = "ZRX-ETH" +EXAMPLE_PAIR = "ADA-USDT" DEFAULT_FEES = TradeFeeSchema( maker_percent_fee_decimal=Decimal("0.001"), @@ -22,18 +22,8 @@ def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: :param exchange_info: the exchange information for a trading pair :return: True if the trading pair is enabled, False otherwise """ - is_spot = False - is_trading = False - - if exchange_info.get("status", None) == "TRADING": - is_trading = True - - permissions_sets = exchange_info.get("permissionSets", list()) - for permission_set in permissions_sets: - # PermissionSet is a list, find if in this list we have "SPOT" value or not - if "SPOT" in permission_set: - is_spot = True - break + is_trading = exchange_info.get("status") == "TRADING" + is_spot = any("SPOT" in permission_set for permission_set in exchange_info.get("permissionSets", [])) return is_trading and is_spot @@ -43,7 +33,7 @@ class GeniusYieldConfigMap(BaseConnectorConfigMap): genius_yield_api_key: SecretStr = Field( default=..., client_data=ClientFieldData( - prompt=lambda cm: "Enter your Genius Yield API key", + prompt=lambda cm: "Genius Yield API key", is_secure=True, is_connect_key=True, prompt_on_new=True, @@ -52,7 +42,7 @@ class GeniusYieldConfigMap(BaseConnectorConfigMap): genius_yield_api_secret: SecretStr = Field( default=..., client_data=ClientFieldData( - prompt=lambda cm: "Enter your Genius Yield API secret", + prompt=lambda cm: "Genius Yield API secret", is_secure=True, is_connect_key=True, prompt_on_new=True, diff --git a/hummingbot/connector/exchange/genius_yield/genius_yield_web_utils.py b/hummingbot/connector/exchange/genius_yield/genius_yield_web_utils.py index 3f66c544fad..0245e5e6a54 100644 --- a/hummingbot/connector/exchange/genius_yield/genius_yield_web_utils.py +++ b/hummingbot/connector/exchange/genius_yield/genius_yield_web_utils.py @@ -16,7 +16,7 @@ def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> st :param domain: the Genius Yield domain to connect to ("com" or "us"). The default value is "com" :return: the full URL to the endpoint """ - return CONSTANTS.REST_URL.format(domain) + CONSTANTS.PUBLIC_API_VERSION + path_url + return CONSTANTS.REST_URL.format(domain) + path_url def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: @@ -26,7 +26,7 @@ def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> s :param domain: the Genius Yield domain to connect to ("com" or "us"). The default value is "com" :return: the full URL to the endpoint """ - return CONSTANTS.REST_URL.format(domain) + CONSTANTS.PRIVATE_API_VERSION + path_url + return CONSTANTS.REST_URL.format(domain) + path_url def build_api_factory( @@ -70,5 +70,5 @@ async def get_current_server_time( method=RESTMethod.GET, throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, ) - server_time = response["serverTime"] + server_time = response["timestamp"] return server_time