diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py index e6ab188942..d51ea38f2c 100644 --- a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py +++ b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py @@ -6,6 +6,7 @@ from enum import Enum from typing import Any, Dict, List, Optional, Tuple +import aiohttp from grpc.aio import UnaryStreamCall from pyinjective.async_client import AsyncClient from pyinjective.orderhash import OrderHashResponse @@ -20,7 +21,6 @@ FundingPaymentsResponse, FundingRate, FundingRatesResponse, - MarketsResponse, OrderbooksV2Response, OrdersHistoryResponse, PositionsResponse, @@ -28,7 +28,6 @@ StreamOrdersHistoryResponse, StreamPositionsResponse, StreamTradesResponse, - TokenMeta, TradesResponse, ) from pyinjective.proto.exchange.injective_explorer_rpc_pb2 import GetTxByTxHashResponse, StreamTxsResponse, TxDetailData @@ -98,7 +97,7 @@ def __init__( # Market Info Attributes self._market_id_to_active_perp_markets: Dict[str, DerivativeMarketInfo] = {} - self._denom_to_token_meta: Dict[str, TokenMeta] = {} + self._denom_to_token_meta: Dict[str, Dict[str, Any]] = {} # Listener(s) and Loop Task(s) self._update_market_info_loop_task: Optional[asyncio.Task] = None @@ -190,10 +189,10 @@ async def set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> T async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: market_info = self._markets_info[trading_pair] - price_scaler: Decimal = Decimal(f"1e-{market_info.quote_token_meta.decimals}") + price_scaler: Decimal = Decimal(f"1e-{market_info['quoteTokenMeta']['decimals']}") async with self._throttler.execute_task(limit_id=CONSTANTS.ORDER_BOOK_LIMIT_ID): response: OrderbooksV2Response = await self._client.get_derivative_orderbooksV2( - market_ids=[market_info.market_id] + market_ids=[market_info["marketId"]] ) snapshot_ob: DerivativeLimitOrderbookV2 = response.orderbooks[0].orderbook @@ -201,7 +200,7 @@ async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: [entry.timestamp for entry in list(snapshot_ob.buys) + list(snapshot_ob.sells)] + [0] ) snapshot_content: Dict[str, Any] = { - "trading_pair": combine_to_hb_trading_pair(base=market_info.oracle_base, quote=market_info.oracle_quote), + "trading_pair": combine_to_hb_trading_pair(base=market_info["baseTokenMeta"]["decimals"], quote=market_info["quoteTokenMeta"]["decimals"]), "update_id": snapshot_timestamp_ms, "bids": [(Decimal(entry.price) * price_scaler, entry.quantity) for entry in snapshot_ob.buys], "asks": [(Decimal(entry.price) * price_scaler, entry.quantity) for entry in snapshot_ob.sells], @@ -501,8 +500,8 @@ async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: for bank_entry in bank_balances: denom_meta = self._denom_to_token_meta.get(bank_entry.denom) if denom_meta is not None: - asset_name: str = denom_meta.symbol - denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + asset_name: str = denom_meta["symbol"] + denom_scaler: Decimal = Decimal(f"1e-{denom_meta['decimals']}") available_balance: Decimal = Decimal(bank_entry.amount) * denom_scaler total_balance: Decimal = available_balance @@ -517,8 +516,8 @@ async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: denom_meta = self._denom_to_token_meta.get(entry.denom) if denom_meta is not None: - asset_name: str = denom_meta.symbol - denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + asset_name: str = denom_meta["symbol"] + denom_scaler: Decimal = Decimal(f"1e-{denom_meta['decimals']}") total_balance: Decimal = Decimal(entry.deposit.total_balance) * denom_scaler available_balance: Decimal = Decimal(entry.deposit.available_balance) * denom_scaler @@ -558,7 +557,7 @@ async def fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decima async with self._throttler.execute_task(limit_id=CONSTANTS.FUNDING_PAYMENT_LIMIT_ID): response: FundingPaymentsResponse = await self._client.get_funding_payments( - subaccount_id=self._account_id, market_id=self._markets_info[trading_pair].market_id, limit=1 + subaccount_id=self._account_id, market_id=self._markets_info[trading_pair]["marketId"], limit=1 ) if len(response.payments) != 0: @@ -568,7 +567,7 @@ async def fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decima # FundingPayment does not include price, hence we have to fetch latest funding rate funding_rate: Decimal = await self._request_last_funding_rate(trading_pair=trading_pair) - amount_scaler: Decimal = Decimal(f"1e-{self._markets_info[trading_pair].quote_token_meta.decimals}") + amount_scaler: Decimal = Decimal(f"1e-{self._markets_info[trading_pair]['quoteTokenMeta']['decimals']}") payment: Decimal = Decimal(latest_funding_payment.amount) * amount_scaler return timestamp, funding_rate, payment @@ -620,30 +619,26 @@ async def _update_market_info_loop(self): async def _update_markets(self): """Fetches and updates trading pair maps of active perpetual markets.""" - perpetual_markets: MarketsResponse = await self._fetch_derivative_markets() + perpetual_markets: Dict[str, Any] = await self._fetch_derivative_markets() self._update_market_map_attributes(markets=perpetual_markets) - spot_markets: MarketsResponse = await self._fetch_spot_markets() - self._update_denom_to_token_meta(markets=spot_markets) - - async def _fetch_derivative_markets(self) -> MarketsResponse: - market_status: str = "active" - async with self._throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): - derivative_markets = await self._client.get_derivative_markets(market_status=market_status) - return derivative_markets - - async def _fetch_spot_markets(self) -> MarketsResponse: - market_status: str = "active" - async with self._throttler.execute_task(limit_id=CONSTANTS.SPOT_MARKETS_LIMIT_ID): - spot_markets = await self._client.get_spot_markets(market_status=market_status) - return spot_markets - - def _update_market_map_attributes(self, markets: MarketsResponse): + + async def _fetch_derivative_markets(self) -> Dict[str, Any]: + async with aiohttp.ClientSession() as client: + resp = await client.get(CONSTANTS.MARKETS_LIST_URL) + return json.loads(await resp.read()) + + def _update_market_map_attributes(self, markets: Dict[str, Any]): """Parses MarketsResponse and re-populate the market map attributes""" - active_perp_markets: Dict[str, DerivativeMarketInfo] = {} - market_id_to_market_map: Dict[str, DerivativeMarketInfo] = {} - for market in markets.markets: - trading_pair: str = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) - market_id: str = market.market_id + active_perp_markets: Dict[str, Any] = {} + market_id_to_market_map: Dict[str, Any] = {} + for market in markets["markets"]: + base_meta = market.get("baseTokenMeta") + quote_meta = market.get("quoteTokenMeta") + if base_meta is not None and quote_meta is not None: + self._denom_to_token_meta[base_meta["address"]] = base_meta + self._denom_to_token_meta[quote_meta["address"]] = quote_meta + trading_pair: str = combine_to_hb_trading_pair(base=base_meta["symbol"], quote=quote_meta["symbol"]) + market_id: str = market["marketId"] active_perp_markets[trading_pair] = market market_id_to_market_map[market_id] = market @@ -654,19 +649,11 @@ def _update_market_map_attributes(self, markets: MarketsResponse): self._markets_info.update(active_perp_markets) self._market_id_to_active_perp_markets.update(market_id_to_market_map) - def _update_denom_to_token_meta(self, markets: MarketsResponse): - self._denom_to_token_meta.clear() - for market in markets.markets: - if market.base_token_meta.symbol != "": # the meta is defined - self._denom_to_token_meta[market.base_denom] = market.base_token_meta - if market.quote_token_meta.symbol != "": # the meta is defined - self._denom_to_token_meta[market.quote_denom] = market.quote_token_meta - def _parse_trading_rule(self, trading_pair: str, market_info: Any) -> TradingRule: min_price_tick_size = ( - Decimal(market_info.min_price_tick_size) * Decimal(f"1e-{market_info.quote_token_meta.decimals}") + Decimal(market_info.get("minPriceTickSize")) * Decimal(f"1e-{market_info.get('quoteTokenMeta').get('decimals', '0')}") ) - min_quantity_tick_size = Decimal(market_info.min_quantity_tick_size) + min_quantity_tick_size = Decimal(market_info.get("minQuantityTickSize")) trading_rule = TradingRule( trading_pair=trading_pair, min_order_size=min_quantity_tick_size, @@ -679,7 +666,7 @@ def _parse_trading_rule(self, trading_pair: str, market_info: Any) -> TradingRul def _compose_derivative_order_for_local_hash_computation(self, order: GatewayInFlightOrder) -> DerivativeOrder: market = self._markets_info[order.trading_pair] return self._composer.DerivativeOrder( - market_id=market.market_id, + market_id=market["marketId"], subaccount_id=self._account_id.lower(), fee_recipient=self._account_address.lower(), price=float(order.price), @@ -694,7 +681,7 @@ async def _fetch_order_history(self, order: GatewayInFlightOrder) -> Optional[De trading_pair: str = order.trading_pair order_hash: str = await order.get_exchange_order_id() - market: DerivativeMarketInfo = self._markets_info[trading_pair] + market: Dict[str, Any] = self._markets_info[trading_pair] direction: str = "buy" if order.trade_type == TradeType.BUY else "sell" trade_type: TradeType = order.trade_type order_type: OrderType = order.order_type @@ -705,7 +692,7 @@ async def _fetch_order_history(self, order: GatewayInFlightOrder) -> Optional[De while not search_completed: async with self._throttler.execute_task(limit_id=CONSTANTS.HISTORICAL_DERIVATIVE_ORDERS_LIMIT_ID): response: OrdersHistoryResponse = await self._client.get_historical_derivative_orders( - market_id=market.market_id, + market_id=market["marketId"], subaccount_id=self._account_id, direction=direction, start_time=int(order.creation_timestamp * 1e3), @@ -731,9 +718,9 @@ async def _fetch_order_fills(self, order: InFlightOrder) -> List[DerivativeTrade all_trades: List[DerivativeTrade] = [] search_completed = False - market_info: DerivativeMarketInfo = self._markets_info[order.trading_pair] + market_info: Dict[str, Any] = self._markets_info[order.trading_pair] - market_id: str = market_info.market_id + market_id: str = market_info["marketId"] direction: str = "buy" if order.trade_type == TradeType.BUY else "sell" while not search_completed: @@ -770,35 +757,35 @@ def _update_local_balances(self, balances: Dict[str, Dict[str, Decimal]]): async def _request_last_funding_rate(self, trading_pair: str) -> Decimal: # NOTE: Can be removed when GatewayHttpClient.clob_perp_funding_info is used. - market_info: DerivativeMarketInfo = self._markets_info[trading_pair] + market_info: Dict[str, Any] = self._markets_info[trading_pair] async with self._throttler.execute_task(limit_id=CONSTANTS.FUNDING_RATES_LIMIT_ID): response: FundingRatesResponse = await self._client.get_funding_rates( - market_id=market_info.market_id, limit=1 + market_id=market_info["marketId"], limit=1 ) funding_rate: FundingRate = response.funding_rates[0] # We only want the latest funding rate. return Decimal(funding_rate.rate) - async def _request_oracle_price(self, market_info: DerivativeMarketInfo) -> Decimal: + async def _request_oracle_price(self, market_info: Dict[str, Any]) -> Decimal: # NOTE: Can be removed when GatewayHttpClient.clob_perp_funding_info is used. """ According to Injective, Oracle Price refers to mark price. """ async with self._throttler.execute_task(limit_id=CONSTANTS.ORACLE_PRICES_LIMIT_ID): response = await self._client.get_oracle_prices( - base_symbol=market_info.oracle_base, - quote_symbol=market_info.oracle_quote, - oracle_type=market_info.oracle_type, + base_symbol=market_info["baseTokenMeta"]["symbol"], + quote_symbol=market_info["quoteTokenMeta"]["symbol"], + oracle_type='bandibc', oracle_scale_factor=0, ) return Decimal(response.price) async def _request_last_trade_price(self, trading_pair: str) -> Decimal: # NOTE: Can be replaced by calling GatewayHTTPClient.clob_perp_last_trade_price - market_info: DerivativeMarketInfo = self._markets_info[trading_pair] + market_info: Dict[str, Any] = self._markets_info[trading_pair] async with self._throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_TRADES_LIMIT_ID): - response: TradesResponse = await self._client.get_derivative_trades(market_id=market_info.market_id) + response: TradesResponse = await self._client.get_derivative_trades(market_id=market_info["marketId"]) last_trade: DerivativeTrade = response.trades[0] - price_scaler: Decimal = Decimal(f"1e-{market_info.quote_token_meta.decimals}") + price_scaler: Decimal = Decimal(f"1e-{market_info['quoteTokenMeta']['decimals']}") last_trade_price: Decimal = Decimal(last_trade.position_delta.execution_price) * price_scaler return last_trade_price @@ -824,9 +811,9 @@ def _parse_derivative_ob_message(self, message: StreamOrderbookV2Response) -> Or """ update_ts_ms: int = message.timestamp market_id: str = message.market_id - market: DerivativeMarketInfo = self._market_id_to_active_perp_markets[market_id] - trading_pair: str = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) - price_scaler: Decimal = Decimal(f"1e-{market.quote_token_meta.decimals}") + market: Dict[str, Any] = self._market_id_to_active_perp_markets[market_id] + trading_pair: str = combine_to_hb_trading_pair(base=market["baseTokenMeta"]["symbol"], quote=market["quoteTokenMeta"]["symbol"]) + price_scaler: Decimal = Decimal(f"1e-{market['quoteTokenMeta']['decimals']}") bids = [(Decimal(bid.price) * price_scaler, Decimal(bid.quantity)) for bid in message.orderbook.buys] asks = [(Decimal(ask.price) * price_scaler, Decimal(ask.quantity)) for ask in message.orderbook.sells] snapshot_msg = OrderBookMessage( @@ -864,11 +851,11 @@ def _parse_backend_trade( ) -> Tuple[OrderBookMessage, TradeUpdate]: exchange_order_id: str = backend_trade.order_hash market_id: str = backend_trade.market_id - market: DerivativeMarketInfo = self._market_id_to_active_perp_markets[market_id] - trading_pair: str = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) + market: Dict[str, Any] = self._market_id_to_active_perp_markets[market_id] + trading_pair: str = combine_to_hb_trading_pair(base=market["baseTokenMeta"]["symbol"], quote=market["quoteTokenMeta"]["symbol"]) trade_id: str = backend_trade.trade_id - price_scaler: Decimal = Decimal(f"1e-{market.quote_token_meta.decimals}") + price_scaler: Decimal = Decimal(f"1e-{market['quoteTokenMeta']['decimals']}") price: Decimal = Decimal(backend_trade.position_delta.execution_price) * price_scaler size: Decimal = Decimal(backend_trade.position_delta.execution_quantity) is_taker: bool = backend_trade.execution_side == "taker" @@ -951,8 +938,7 @@ async def _listen_to_bank_balances_streams(self): def _process_bank_balance_stream_event(self, message: StreamAccountPortfolioResponse): denom_meta = self._denom_to_token_meta[message.denom] - symbol = denom_meta.symbol - safe_ensure_future(self._issue_balance_update(token=symbol)) + safe_ensure_future(self._issue_balance_update(token=denom_meta["symbol"])) async def _listen_to_subaccount_balances_stream(self): while True: @@ -970,7 +956,7 @@ async def _listen_to_subaccount_balances_stream(self): def _process_subaccount_balance_stream_event(self, message: StreamSubaccountBalanceResponse): denom_meta = self._denom_to_token_meta[message.balance.denom] - symbol = denom_meta.symbol + symbol = denom_meta["symbol"] safe_ensure_future(self._issue_balance_update(token=symbol)) async def _issue_balance_update(self, token: str): @@ -1100,7 +1086,7 @@ async def _process_order_update_event(self, message: StreamOrdersHistoryResponse def _get_trading_pair_from_market_id(self, market_id: str) -> str: market = self._market_id_to_active_perp_markets[market_id] - trading_pair = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) + trading_pair = combine_to_hb_trading_pair(base=market["baseTokenMeta"]["symbol"], quote=market["quoteTokenMeta"]["symbol"]) return trading_pair async def _listen_order_updates_stream(self, market_id: str): @@ -1153,16 +1139,16 @@ def _parse_backed_position_to_position(self, backend_position: DerivativePositio return position def _parse_backend_position_to_position_event(self, backend_position: DerivativePosition) -> PositionUpdateEvent: - market_info: DerivativeMarketInfo = self._market_id_to_active_perp_markets[backend_position.market_id] - trading_pair: str = combine_to_hb_trading_pair(base=market_info.oracle_base, quote=market_info.oracle_quote) + market_info: Dict[str, Any] = self._market_id_to_active_perp_markets[backend_position.market_id] + trading_pair: str = combine_to_hb_trading_pair(base=market_info["baseTokenMeta"]["symbol"], quote=market_info["quoteTokenMeta"]["symbol"]) amount: Decimal = Decimal(backend_position.quantity) if backend_position.direction != "": position_side = PositionSide[backend_position.direction.upper()] entry_price: Decimal = ( - Decimal(backend_position.entry_price) * Decimal(f"1e-{market_info.quote_token_meta.decimals}") + Decimal(backend_position.entry_price) * Decimal(f"1e-{market_info['quoteTokenMeta']['decimals']}") ) mark_price: Decimal = ( - Decimal(backend_position.mark_price) * Decimal(f"1e-{market_info.oracle_scale_factor}") + Decimal(backend_position.mark_price) * Decimal(f"1e-{market_info['quoteTokenMeta']['decimals']}") ) leverage = Decimal( round( @@ -1205,11 +1191,12 @@ async def _listen_to_positions_stream(self): raise except Exception: self.logger().exception("Unexpected error in position listener loop.") - self.logger().info("Restarting position stream.") - stream.cancel() + finally: + self.logger().info("Restarting position stream.") + stream.cancel() - async def _process_funding_info_event(self, market_info: DerivativeMarketInfo, message: StreamPricesResponse): - trading_pair: str = combine_to_hb_trading_pair(base=market_info.oracle_base, quote=market_info.oracle_quote) + async def _process_funding_info_event(self, market_info: Dict[str, Any], message: StreamPricesResponse): + trading_pair: str = combine_to_hb_trading_pair(base=market_info["baseTokenMeta"]["symbol"], quote=market_info["quoteTokenMeta"]["symbol"]) funding_info = await self._request_funding_info(trading_pair=trading_pair) funding_info_event = FundingInfoUpdate( trading_pair=trading_pair, @@ -1223,19 +1210,15 @@ async def _process_funding_info_event(self, market_info: DerivativeMarketInfo, m async def _request_funding_info(self, trading_pair: str) -> FundingInfo: # NOTE: Can be replaced with GatewayHttpClient.clob_perp_funding_info() self._check_markets_initialized() or await self._update_markets() - market_info: DerivativeMarketInfo = self._markets_info[trading_pair] + market_info: Dict[str, Any] = self._markets_info[trading_pair] last_funding_rate: Decimal = await self._request_last_funding_rate(trading_pair=trading_pair) oracle_price: Decimal = await self._request_oracle_price(market_info=market_info) last_trade_price: Decimal = await self._request_last_trade_price(trading_pair=trading_pair) - async with self._throttler.execute_task(limit_id=CONSTANTS.SINGLE_DERIVATIVE_MARKET_LIMIT_ID): - updated_market_info = await self._client.get_derivative_market(market_id=market_info.market_id) funding_info = FundingInfo( trading_pair=trading_pair, index_price=last_trade_price, # Default to using last trade price mark_price=oracle_price, - next_funding_utc_timestamp=( - updated_market_info.market.perpetual_market_info.next_funding_timestamp * 1e-3 - ), + next_funding_utc_timestamp=int(self._time()) + (3600 - int(self._time() % 3600)), # funding settlement occurs every 1 hr rate=last_funding_rate, ) return funding_info @@ -1245,9 +1228,9 @@ async def _listen_to_funding_info_stream(self, market_id: str): while True: market_info = self._market_id_to_active_perp_markets[market_id] stream: UnaryStreamCall = await self._client.stream_oracle_prices( - base_symbol=market_info.oracle_base, - quote_symbol=market_info.oracle_quote, - oracle_type=market_info.oracle_type, + base_symbol=market_info["baseTokenMeta"]["symbol"], + quote_symbol=market_info["quoteTokenMeta"]["symbol"], + oracle_type="bandibc", ) try: async for message in stream: @@ -1279,7 +1262,7 @@ async def _start_streams(self): self._positions_stream_listener = self._positions_stream_listener or safe_ensure_future( coro=self._listen_to_positions_stream() ) - for market_id in [self._markets_info[tp].market_id for tp in self._trading_pairs]: + for market_id in [self._markets_info[tp]["marketId"] for tp in self._trading_pairs]: if market_id not in self._order_listeners: self._order_listeners[market_id] = safe_ensure_future( coro=self._listen_order_updates_stream(market_id=market_id) @@ -1310,16 +1293,16 @@ async def _stop_streams(self): self._positions_stream_listener = None def _get_exchange_trading_pair_from_market_info(self, market_info: Any) -> str: - return market_info.market_id + return market_info.get("marketId") def _get_maker_taker_exchange_fee_rates_from_market_info( self, market_info: Any ) -> MakerTakerExchangeFeeRates: # Since we are using the API, we are the service provider. # Reference: https://api.injective.exchange/#overview-trading-fees-and-gas - fee_scaler = Decimal("1") - Decimal(market_info.service_provider_fee) - maker_fee = Decimal(market_info.maker_fee_rate) * fee_scaler - taker_fee = Decimal(market_info.taker_fee_rate) * fee_scaler + fee_scaler = Decimal("1") - Decimal(market_info.get("serviceProviderFee")) + maker_fee = Decimal(market_info.get("makerFeeRate")) * fee_scaler + taker_fee = Decimal(market_info.get("takerFeeRate")) * fee_scaler maker_taker_exchange_fee_rates = MakerTakerExchangeFeeRates( maker=maker_fee, taker=taker_fee, maker_flat_fees=[], taker_flat_fees=[] ) @@ -1331,7 +1314,7 @@ def _get_gateway_instance(self) -> GatewayHttpClient: def _get_market_ids(self) -> List[str]: market_ids = [ - self._markets_info[trading_pair].market_id + self._markets_info[trading_pair]["marketId"] for trading_pair in self._trading_pairs ] return market_ids diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py index 85f8424edf..0f8c90bd17 100644 --- a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py +++ b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py @@ -36,6 +36,8 @@ MSG_CANCEL_DERIVATIVE_ORDER = "/injective.exchange.v1beta1.MsgCancelDerivativeOrder" MSG_BATCH_UPDATE_ORDERS = "/injective.exchange.v1beta1.MsgBatchUpdateOrders" +MARKETS_LIST_URL = "https://raw.githubusercontent.com/InjectiveLabs/utils-go/master/pkg/curatedLists/hummingbot/markets.json" + INJ_DERIVATIVE_TX_EVENT_TYPES = [ MSG_CREATE_DERIVATIVE_LIMIT_ORDER, MSG_CANCEL_DERIVATIVE_ORDER, diff --git a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py index 8050e0da87..3a711460c7 100644 --- a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py +++ b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py @@ -6,6 +6,7 @@ import grpc import pandas as pd +from aioresponses import aioresponses from pyinjective.orderhash import OrderHashResponse from pyinjective.proto.exchange.injective_accounts_rpc_pb2 import ( StreamSubaccountBalanceResponse, @@ -14,7 +15,6 @@ ) from pyinjective.proto.exchange.injective_derivative_exchange_rpc_pb2 import ( DerivativeLimitOrderbookV2, - DerivativeMarketInfo, DerivativeOrderHistory, DerivativePosition, DerivativeTrade, @@ -22,13 +22,9 @@ FundingPaymentsResponse, FundingRate, FundingRatesResponse, - MarketResponse, - MarketsResponse, OrderbooksV2Response, OrdersHistoryResponse, Paging, - PerpetualMarketFunding, - PerpetualMarketInfo, PositionDelta, PositionsResponse, PriceLevel, @@ -37,7 +33,6 @@ StreamOrdersHistoryResponse, StreamPositionsResponse, StreamTradesResponse, - TokenMeta, TradesResponse, ) from pyinjective.proto.exchange.injective_explorer_rpc_pb2 import ( @@ -56,11 +51,6 @@ SubaccountBalanceV2, SubaccountDeposit, ) -from pyinjective.proto.exchange.injective_spot_exchange_rpc_pb2 import ( - MarketsResponse as SpotMarketsResponse, - SpotMarketInfo, - TokenMeta as SpotTokenMeta, -) from hummingbot.connector.constants import s_decimal_0 from hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_constants import ( @@ -68,6 +58,7 @@ DERIVATIVE_CANCEL_ORDER_GAS, DERIVATIVE_SUBMIT_ORDER_GAS, GAS_BUFFER, + MARKETS_LIST_URL, ) from hummingbot.core.data_type.common import OrderType, PositionSide, TradeType from hummingbot.core.data_type.in_flight_order import InFlightOrder @@ -182,7 +173,7 @@ def taker_fee_rate(self) -> Decimal: def exchange_trading_pair(self) -> str: return self.market_id - def start(self): + def start(self, mock_api): self.injective_async_client_mock = self.injective_async_client_mock_patch.start() self.injective_async_client_mock.return_value = self.injective_async_client_mock self.gateway_instance_mock = self.gateway_instance_mock_patch.start() @@ -201,7 +192,23 @@ def start(self): self.injective_async_client_mock.stream_oracle_prices.return_value = StreamMock() self.injective_async_client_mock.stream_derivative_positions.return_value = StreamMock() - self.configure_active_derivative_markets_response(timestamp=self.initial_timestamp) + params = { + "inj_base": self.inj_base, + "base": self.base, + "quote": self.quote, + "base_coin_address": self.base_coin_address, + "quote_coin_address": self.quote_coin_address, + "base_decimals": self.base_decimals, + "quote_decimals": self.quote_decimals, + "initial_timestamp": self.initial_timestamp, + "market_id": self.market_id, + "inj_market_id": self.inj_market_id, + "base_tokens": [self.inj_base, self.base], + "min_price_tick_size": self.min_price_tick_size, + "min_quantity_tick_size": self.min_quantity_tick_size, + } + + InjectivePerpetualClientMock.configure_active_derivative_markets_response(mock_api=mock_api, **params) self.configure_get_funding_info_response( index_price=Decimal("200"), mark_price=Decimal("202"), @@ -923,14 +930,6 @@ def configure_get_funding_info_response( taker_fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0"))]), ) - derivative_market_info = self._get_derivative_market_info( - market_id=self.market_id, - base_token=self.base, - next_funding_time=next_funding_time, - ) - market_info_response = MarketResponse(market=derivative_market_info) - self.injective_async_client_mock.get_derivative_market.return_value = market_info_response - def configure_get_funding_payments_response( self, timestamp: float, funding_rate: Decimal, amount: Decimal ): @@ -1176,79 +1175,12 @@ def configure_cancelation_transaction_stream_event(self, timestamp: float, trans ) self.injective_async_client_mock.stream_txs.return_value.add(transaction_event) - def configure_active_derivative_markets_response(self, timestamp: float): - custom_derivative_market_info = self._get_derivative_market_info( - market_id=self.market_id, base_token=self.base - ) - - inj_derivative_market_info = self._get_derivative_market_info( - market_id=self.inj_market_id, base_token=self.inj_base - ) - perp_markets = MarketsResponse() - perp_markets.markets.append(custom_derivative_market_info) - perp_markets.markets.append(inj_derivative_market_info) - - self.injective_async_client_mock.get_derivative_markets.return_value = perp_markets - - min_spot_price_tick_size = str( - self.min_price_tick_size * Decimal(f"1e{self.quote_decimals - self.base_decimals}")) - min_spot_quantity_tick_size = str(self.min_quantity_tick_size * Decimal(f"1e{self.base_decimals}")) - inj_spot_pair_min_price_tick_size = str(self.min_price_tick_size * Decimal(f"1e{18 - self.base_decimals}")) - inj_spot_pair_min_quantity_tick_size = str(self.min_quantity_tick_size * Decimal(f"1e{self.base_decimals}")) - base_token_meta = SpotTokenMeta( - name="Coin", - address=self.base_coin_address, - symbol=self.base, - decimals=self.base_decimals, - updated_at=int(timestamp * 1e3), - ) - quote_token_meta = SpotTokenMeta( - name="Alpha", - address=self.quote_coin_address, - symbol=self.quote, - decimals=self.quote_decimals, - updated_at=int(timestamp * 1e3), - ) - inj_token_meta = SpotTokenMeta( - name="Injective Protocol", - address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock - symbol=self.inj_base, - decimals=18, - updated_at=int(timestamp * 1e3), - ) - custom_spot_market_info = SpotMarketInfo( - market_id=self.market_id, - market_status="active", - ticker=f"{self.base}/{self.quote}", - base_denom=self.base_denom, - base_token_meta=base_token_meta, - quote_denom=self.quote_denom, - quote_token_meta=quote_token_meta, - maker_fee_rate=str(self.maker_fee_rate), - taker_fee_rate=str(self.taker_fee_rate), - service_provider_fee="0.4", - min_price_tick_size=min_spot_price_tick_size, - min_quantity_tick_size=min_spot_quantity_tick_size, - ) - inj_spot_market_info = SpotMarketInfo( - market_id=self.inj_market_id, - market_status="active", - ticker=f"{self.inj_base}/{self.quote}", - base_denom="inj", - base_token_meta=inj_token_meta, - quote_denom=self.quote_denom, - quote_token_meta=quote_token_meta, - maker_fee_rate=str(self.maker_fee_rate), - taker_fee_rate=str(self.taker_fee_rate), - service_provider_fee="0.4", - min_price_tick_size=inj_spot_pair_min_price_tick_size, - min_quantity_tick_size=inj_spot_pair_min_quantity_tick_size, - ) - spot_markets = SpotMarketsResponse() - spot_markets.markets.append(custom_spot_market_info) - spot_markets.markets.append(inj_spot_market_info) - - self.injective_async_client_mock.get_spot_markets.return_value = spot_markets + @staticmethod + def configure_active_derivative_markets_response(mock_api: aioresponses, **kwargs): + url = MARKETS_LIST_URL + response = InjectivePerpetualClientMock.get_derivative_market_info(**kwargs) + mock_api.get(url, body=json.dumps(response), repeat=True) + return url def configure_get_derivative_positions_response( self, @@ -1285,7 +1217,7 @@ def configure_get_derivative_positions_response( entry_price=str(main_position_price * Decimal(f"1e{self.quote_decimals}")), margin=str(margin), liquidation_price="0", - mark_price=str(main_position_mark_price * Decimal(f"1e{self.oracle_scale_factor}")), + mark_price=str(main_position_mark_price * Decimal(f"1e{self.quote_decimals}")), aggregate_reduce_only_quantity="0", updated_at=1680511486496, created_at=-62135596800000, @@ -1307,7 +1239,7 @@ def configure_get_derivative_positions_response( entry_price=str(inj_position_price * Decimal(f"1e{self.quote_decimals}")), margin=str(margin), liquidation_price="0", - mark_price=str(inj_position_mark_price * Decimal(f"1e{self.oracle_scale_factor}")), + mark_price=str(inj_position_mark_price * Decimal(f"1e{self.quote_decimals}")), aggregate_reduce_only_quantity="0", updated_at=1680511486496, created_at=-62135596800000, @@ -1332,7 +1264,7 @@ def configure_position_event( direction="long" if side == PositionSide.LONG else "short", subaccount_id=self.sub_account_id, quantity=str(size), - mark_price=str(mark_price * Decimal(f"1e{self.oracle_scale_factor}")), + mark_price=str(mark_price * Decimal(f"1e{self.quote_decimals}")), entry_price=str(entry_price * Decimal(f"1e{self.quote_decimals}")), margin=str(margin * Decimal(f"1e{self.quote_decimals}")), aggregate_reduce_only_quantity="0", @@ -1342,54 +1274,57 @@ def configure_position_event( position_event = StreamPositionsResponse(position=position) self.injective_async_client_mock.stream_derivative_positions.return_value.add(position_event) - def _get_derivative_market_info( - self, - market_id: str, - base_token: str, - next_funding_time: float = 123123123 + @staticmethod + def get_derivative_market_info( + inj_base: str, + base: str, + quote: str, + initial_timestamp: float, + base_coin_address = "someBaseCoinAddress", + quote_coin_address = "someQuoteCoinAddress", + base_decimals: int = 18, + quote_decimals: int = 8, + market_id: str = "someMarketId", + inj_market_id: str = "anotherMarketId", + base_tokens: List[str] = [], + min_price_tick_size=Decimal("0.000001"), + min_quantity_tick_size=Decimal("0.001"), ): - quote_token_meta = TokenMeta( - name="Alpha", - address=self.quote_coin_address, - symbol=self.quote, - decimals=self.quote_decimals, - updated_at=int(self.initial_timestamp * 1e3), - ) - min_perpetual_price_tick_size = str(self.min_price_tick_size * Decimal(f"1e{self.quote_decimals}")) - min_perpetual_quantity_tick_size = str(self.min_quantity_tick_size) - perpetual_market_info = PerpetualMarketInfo( - hourly_funding_rate_cap="0.0000625", - hourly_interest_rate="0.00000416666", - next_funding_timestamp=int(next_funding_time * 1e3), - funding_interval=3600, - ) - perpetual_market_funding = PerpetualMarketFunding( - cumulative_funding="6749828879.286921884648585187", - cumulative_price="1.502338165156193724", - last_timestamp=1677660809, - ) - derivative_market_info = DerivativeMarketInfo( - market_id=market_id, - market_status="active", - ticker=f"{base_token}/{self.quote} PERP", - oracle_base=base_token, - oracle_quote=self.quote, - oracle_type="bandibc", - oracle_scale_factor=self.oracle_scale_factor, - initial_margin_ratio="0.095", - maintenance_margin_ratio="0.05", - quote_denom=self.quote_coin_address, - quote_token_meta=quote_token_meta, - maker_fee_rate=str(self.maker_fee_rate), - taker_fee_rate=str(self.taker_fee_rate), - service_provider_fee="0.4", - is_perpetual=True, - min_price_tick_size=min_perpetual_price_tick_size, - min_quantity_tick_size=min_perpetual_quantity_tick_size, - perpetual_market_info=perpetual_market_info, - perpetual_market_funding=perpetual_market_funding, - ) - return derivative_market_info + market_ids = {base: market_id, inj_base: inj_market_id} + markets = {"markets": []} + for base_token in base_tokens: + markets["markets"].append( + { + "marketId": market_ids[base_token], + "ticker": f"{base_token}/{quote} PERP", + "baseDenom": base_coin_address, + "baseTokenMeta": + { + "name": base_token, + "address": base_coin_address, + "symbol": base_token, + "logo": "https://static.alchemyapi.io/images/assets/2396.png", + "decimals": base_decimals, + "updatedAt": int(initial_timestamp * 1e3) + }, + "quoteDenom": quote_coin_address, + "quoteTokenMeta": + { + "name": "Alpha", + "address": quote_coin_address, + "symbol": quote, + "logo": "https://static.alchemyapi.io/images/assets/3408.png", + "decimals": quote_decimals, + "updatedAt": int(initial_timestamp * 1e3) + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": str(min_price_tick_size * Decimal(f"1e{quote_decimals}")), + "minQuantityTickSize": str(min_quantity_tick_size) + } + ) + return markets def configure_fetch_last_fee_payment_response( self, amount: Decimal, funding_rate: Decimal, timestamp: float diff --git a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py index 448b84c2cc..f16f9cb095 100644 --- a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py +++ b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py @@ -7,6 +7,7 @@ from typing import Awaitable, List from unittest.mock import AsyncMock, patch +from aioresponses.core import aioresponses from bidict import bidict from hummingbot.client.config.client_config_map import ClientConfigMap @@ -57,7 +58,8 @@ def setUpClass(cls) -> None: cls.inj_trading_pair = combine_to_hb_trading_pair(base="INJ", quote=cls.quote) cls.sub_account_id = "0x72B52e007d01cc5aC36349288F24CE1Bd912CEDf000000000000000000000000" # noqa: mock - def setUp(self) -> None: + @aioresponses() + def setUp(self, mock_api: aioresponses) -> None: super().setUp() self.initial_timestamp = 1669100347689 self.injective_async_client_mock = InjectivePerpetualClientMock( @@ -66,7 +68,7 @@ def setUp(self) -> None: base=self.base, quote=self.quote, ) - self.injective_async_client_mock.start() + self.injective_async_client_mock.start(mock_api=mock_api) client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) @@ -99,7 +101,6 @@ def setUp(self) -> None: self.data_source.add_listener(event_tag=MarketEvent.FundingInfo, listener=self.funding_info_logger) self.data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=self.trades_logger) self.data_source.add_listener(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=self.snapshots_logger) - self.async_run_with_timeout(coro=self.data_source.start()) @staticmethod @@ -595,7 +596,16 @@ def test_get_account_balances_using_default_account(self): self.assertEqual(quote_total_balance, sub_account_balances[self.quote]["total_balance"]) self.assertEqual(quote_available_balance, sub_account_balances[self.quote]["available_balance"]) - def test_get_account_balances_using_non_default_account(self): + @aioresponses() + def test_get_account_balances_using_non_default_account(self, mock_api): + params = { + "inj_base": "INJ", + "base": self.base, + "quote": self.quote, + "base_tokens": [self.base], + "initial_timestamp": self.initial_timestamp + } + InjectivePerpetualClientMock.configure_active_derivative_markets_response(mock_api=mock_api, **params) sub_account_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock connector_spec = { "chain": "injective", @@ -632,6 +642,7 @@ def test_get_account_balances_using_non_default_account(self): self.assertEqual(base_available_balance, sub_account_balances[self.base]["available_balance"]) self.assertEqual(quote_total_balance, sub_account_balances[self.quote]["total_balance"]) self.assertEqual(quote_available_balance, sub_account_balances[self.quote]["available_balance"]) + self.async_run_with_timeout(coro=data_source.stop()) def test_get_order_status_update_success(self): creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock @@ -826,12 +837,22 @@ def test_delivers_bank_balance_events(self): self.assertEqual(self.quote, balance_event.asset_name) self.assertEqual(target_available_balance, balance_event.available_balance) - def test_non_default_account_ignores_bank_balance_events(self): + @aioresponses() + def test_non_default_account_ignores_bank_balance_events(self, mock_api): + params = { + "inj_base": "INJ", + "base": self.base, + "quote": self.quote, + "base_tokens": [self.base], + "initial_timestamp": self.initial_timestamp + } + InjectivePerpetualClientMock.configure_active_derivative_markets_response(mock_api=mock_api, **params) sub_account_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock connector_spec = { "chain": "injective", "network": "mainnet", "wallet_address": sub_account_id, + "base_tokens": ["INJ", self.base] } data_source = InjectivePerpetualAPIDataSource( trading_pairs=[self.trading_pair], @@ -852,10 +873,15 @@ def test_non_default_account_ignores_bank_balance_events(self): self.assertEqual(0, len(self.balance_logger.event_log)) - def test_delivers_funding_info_events(self): + @patch( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.InjectivePerpetualAPIDataSource" + "._time" + ) + def test_delivers_funding_info_events(self, time_mock): + time_mock.return_value = 123120123 target_index_price = Decimal("100") target_mark_price = Decimal("101") - next_funding_time = 123123123 + next_funding_time = 123123600 target_rate = Decimal("0.0001") self.injective_async_client_mock.configure_funding_info_stream_event( index_price=target_index_price, @@ -1004,14 +1030,14 @@ def test_parses_transaction_event_for_order_cancelation(self): def test_parse_position_event(self): expected_size = Decimal("1") expected_side = PositionSide.LONG - expecte_unrealized_pnl = Decimal("2") + expected_unrealized_pnl = Decimal("2") expected_entry_price = Decimal("1") expected_leverage = Decimal("3") self.injective_async_client_mock.configure_position_event( size=expected_size, side=expected_side, - unrealized_pnl=expecte_unrealized_pnl, + unrealized_pnl=expected_unrealized_pnl, entry_price=expected_entry_price, leverage=expected_leverage, ) @@ -1025,7 +1051,7 @@ def test_parse_position_event(self): self.assertEqual(self.trading_pair, position_event.trading_pair) self.assertEqual(expected_size, position_event.amount) self.assertEqual(expected_side, position_event.position_side) - self.assertEqual(expecte_unrealized_pnl, position_event.unrealized_pnl) + self.assertEqual(expected_unrealized_pnl, position_event.unrealized_pnl) self.assertEqual(expected_entry_price, position_event.entry_price) self.assertEqual(expected_leverage, position_event.leverage) @@ -1086,10 +1112,15 @@ def test_fetch_positions(self): self.assertEqual(inj_position_expected_size, inj_position.amount) self.assertEqual(inj_position_expected_leverage, inj_position.leverage) - def test_get_funding_info(self): + @patch( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.InjectivePerpetualAPIDataSource" + "._time" + ) + def test_get_funding_info(self, time_mock): + time_mock.return_value = 123120123 target_index_price = Decimal("100") target_mark_price = Decimal("101") - next_funding_time = 123123123 + next_funding_time = 123123600 target_rate = Decimal("0.0001") self.injective_async_client_mock.configure_get_funding_info_response( diff --git a/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py index 14a7eea3eb..d0ad3cc0f3 100644 --- a/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py +++ b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py @@ -7,6 +7,8 @@ from typing import Awaitable, Dict, List, Mapping from unittest.mock import AsyncMock, MagicMock, patch +from aioresponses.core import aioresponses + from hummingbot.client.config.client_config_map import ClientConfigMap from hummingbot.client.config.config_helpers import ClientConfigAdapter from hummingbot.client.config.fee_overrides_config_map import init_fee_overrides_config @@ -57,7 +59,8 @@ def setUpClass(cls) -> None: cls.wallet_address = "someWalletAddress" cls.clock_tick_size = 1 - def setUp(self) -> None: + @aioresponses() + def setUp(self, mock_api: aioresponses) -> None: super().setUp() self.log_records = [] @@ -70,7 +73,7 @@ def setUp(self) -> None: base=self.base_asset, quote=self.quote_asset, ) - self.clob_data_source_mock.start() + self.clob_data_source_mock.start(mock_api) client_config_map = ClientConfigAdapter(ClientConfigMap()) connector_spec = { @@ -413,8 +416,17 @@ def test_initial_status_dict(self): self.assertEqual(expected_initial_dict, status_dict) self.assertFalse(exchange.ready) + @patch("hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.InjectivePerpetualAPIDataSource._fetch_derivative_markets") @patch("hummingbot.core.data_type.order_book_tracker.OrderBookTracker._sleep") - def test_full_initialization_and_de_initialization(self, _: AsyncMock): + def test_full_initialization_and_de_initialization(self, _: AsyncMock, mock_markets: MagicMock): + params = { + "inj_base": "INJ", + "base": "COINALPHA", + "quote": "HBOT", + "base_tokens": ["COINALPHA"], + "initial_timestamp": 123123123 + } + mock_markets.return_value = InjectivePerpetualClientMock.get_derivative_market_info(**params) self.clob_data_source_mock.configure_get_account_balances_response( base_total_balance=Decimal("10"), base_available_balance=Decimal("9"), @@ -1483,12 +1495,16 @@ def test_user_stream_logs_errors(self): self.assertTrue(self.is_logged("INFO", "Restarting account balances stream.")) - def test_funding_info_update(self): + @patch( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.InjectivePerpetualAPIDataSource" + "._time" + ) + def test_funding_info_update(self, time_mock): initial_funding_info = self.exchange.get_funding_info(self.trading_pair) - update_target_index_price = initial_funding_info.index_price + 1 update_target_mark_price = initial_funding_info.mark_price + 2 update_target_next_funding_time = initial_funding_info.next_funding_utc_timestamp * 2 + time_mock.return_value = update_target_next_funding_time - 3600 update_target_funding_rate = initial_funding_info.rate + Decimal("0.0003") self.clob_data_source_mock.configure_funding_info_stream_event( diff --git a/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py index 3bc1729bda..408578f14b 100644 --- a/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py +++ b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py @@ -5,7 +5,9 @@ InjectivePerpetualClientMock, ) from typing import Awaitable -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch + +from aioresponses.core import aioresponses from hummingbot.client.config.client_config_map import ClientConfigMap from hummingbot.client.config.config_helpers import ClientConfigAdapter @@ -44,7 +46,8 @@ def setUpClass(cls) -> None: cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) cls.sub_account_id = "someSubAccountId" - def setUp(self) -> None: + @aioresponses() + def setUp(self, mock_api: aioresponses) -> None: super().setUp() self.listening_tasks = [] @@ -55,7 +58,7 @@ def setUp(self) -> None: base=self.base, quote=self.quote, ) - self.injective_async_client_mock.start() + self.injective_async_client_mock.start(mock_api) client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) self.api_data_source = InjectivePerpetualAPIDataSource( @@ -204,7 +207,11 @@ def test_listen_for_funding_info_cancelled_when_listening(self): self.listening_tasks.append(listening_task) self.async_run_with_timeout(listening_task) - def test_listen_for_funding_info_successful(self): + @patch( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.InjectivePerpetualAPIDataSource" + "._time" + ) + def test_listen_for_funding_info_successful(self, time_mock): initial_funding_info = self.async_run_with_timeout( coro=self.ob_data_source.get_funding_info(self.trading_pair) ) @@ -212,6 +219,7 @@ def test_listen_for_funding_info_successful(self): update_target_index_price = initial_funding_info.index_price + 1 update_target_mark_price = initial_funding_info.mark_price + 2 update_target_next_funding_time = initial_funding_info.next_funding_utc_timestamp * 2 + time_mock.return_value = update_target_next_funding_time - 3600 update_target_funding_rate = initial_funding_info.rate + Decimal("0.0003") self.injective_async_client_mock.configure_funding_info_stream_event( @@ -232,10 +240,15 @@ def test_listen_for_funding_info_successful(self): self.assertEqual(update_target_next_funding_time, updated_funding_info.next_funding_utc_timestamp) self.assertEqual(update_target_funding_rate, updated_funding_info.rate) - def test_get_funding_info(self): + @patch( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.InjectivePerpetualAPIDataSource" + "._time" + ) + def test_get_funding_info(self, time_mock): expected_index_price = Decimal("10") expected_mark_price = Decimal("10.1") - expected_next_funding_time = 1610000000 + expected_next_funding_time = 1610002800 + time_mock.return_value = expected_next_funding_time - 3600 expected_funding_rate = Decimal("0.0009") self.injective_async_client_mock.configure_get_funding_info_response(