|
| 1 | +import logging |
| 2 | +from datetime import datetime |
| 3 | +from typing import Any, Dict, List, Optional |
| 4 | + |
| 5 | +from hummingbot.core.network_iterator import NetworkStatus |
| 6 | +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase |
| 7 | +from hummingbot.data_feed.candles_feed.dexalot_spot_candles import constants as CONSTANTS |
| 8 | +from hummingbot.logger import HummingbotLogger |
| 9 | + |
| 10 | + |
| 11 | +class DexalotSpotCandles(CandlesBase): |
| 12 | + _logger: Optional[HummingbotLogger] = None |
| 13 | + |
| 14 | + @classmethod |
| 15 | + def logger(cls) -> HummingbotLogger: |
| 16 | + if cls._logger is None: |
| 17 | + cls._logger = logging.getLogger(__name__) |
| 18 | + return cls._logger |
| 19 | + |
| 20 | + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): |
| 21 | + super().__init__(trading_pair, interval, max_records) |
| 22 | + |
| 23 | + @property |
| 24 | + def name(self): |
| 25 | + return f"dexalot_{self._trading_pair}" |
| 26 | + |
| 27 | + @property |
| 28 | + def rest_url(self): |
| 29 | + return CONSTANTS.REST_URL |
| 30 | + |
| 31 | + @property |
| 32 | + def wss_url(self): |
| 33 | + return CONSTANTS.WSS_URL |
| 34 | + |
| 35 | + @property |
| 36 | + def health_check_url(self): |
| 37 | + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT |
| 38 | + |
| 39 | + @property |
| 40 | + def candles_url(self): |
| 41 | + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT |
| 42 | + |
| 43 | + @property |
| 44 | + def candles_endpoint(self): |
| 45 | + return CONSTANTS.CANDLES_ENDPOINT |
| 46 | + |
| 47 | + @property |
| 48 | + def candles_max_result_per_rest_request(self): |
| 49 | + return CONSTANTS.MAX_RESULTS_PER_CANDLESTICK_REST_REQUEST |
| 50 | + |
| 51 | + @property |
| 52 | + def rate_limits(self): |
| 53 | + return CONSTANTS.RATE_LIMITS |
| 54 | + |
| 55 | + @property |
| 56 | + def intervals(self): |
| 57 | + return CONSTANTS.INTERVALS |
| 58 | + |
| 59 | + async def check_network(self) -> NetworkStatus: |
| 60 | + rest_assistant = await self._api_factory.get_rest_assistant() |
| 61 | + await rest_assistant.execute_request(url=self.health_check_url, |
| 62 | + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) |
| 63 | + return NetworkStatus.CONNECTED |
| 64 | + |
| 65 | + def get_exchange_trading_pair(self, trading_pair): |
| 66 | + return trading_pair.replace("-", "/") |
| 67 | + |
| 68 | + @property |
| 69 | + def _is_first_candle_not_included_in_rest_request(self): |
| 70 | + return False |
| 71 | + |
| 72 | + @property |
| 73 | + def _is_last_candle_not_included_in_rest_request(self): |
| 74 | + return False |
| 75 | + |
| 76 | + def _get_rest_candles_params(self, |
| 77 | + start_time: Optional[int] = None, |
| 78 | + end_time: Optional[int] = None, |
| 79 | + limit: Optional[int] = CONSTANTS.MAX_RESULTS_PER_CANDLESTICK_REST_REQUEST) -> dict: |
| 80 | + """ |
| 81 | + For API documentation, please refer to: |
| 82 | +
|
| 83 | + startTime and endTime must be used at the same time. |
| 84 | + """ |
| 85 | + _intervalstr = self.interval[-1] |
| 86 | + if _intervalstr == 'm': |
| 87 | + intervalstr = 'minute' |
| 88 | + elif _intervalstr == 'h': |
| 89 | + intervalstr = 'hour' |
| 90 | + elif _intervalstr == 'd': |
| 91 | + intervalstr = 'day' |
| 92 | + else: |
| 93 | + intervalstr = '' |
| 94 | + params = { |
| 95 | + "pair": self._ex_trading_pair, |
| 96 | + "intervalnum": CONSTANTS.INTERVALS[self.interval][1:], |
| 97 | + "intervalstr": intervalstr, |
| 98 | + } |
| 99 | + if start_time is not None or end_time is not None: |
| 100 | + start_time = start_time if start_time is not None else end_time - limit * self.interval_in_seconds |
| 101 | + start_isotime = f"{datetime.fromtimestamp(start_time).isoformat(timespec='milliseconds')}Z" |
| 102 | + params["periodfrom"] = start_isotime |
| 103 | + end_time = end_time if end_time is not None else start_time + limit * self.interval_in_seconds |
| 104 | + end_isotiome = f"{datetime.fromtimestamp(end_time).isoformat(timespec='milliseconds')}Z" |
| 105 | + params["periodto"] = end_isotiome |
| 106 | + return params |
| 107 | + |
| 108 | + def _parse_rest_candles(self, data: dict, end_time: Optional[int] = None) -> List[List[float]]: |
| 109 | + if data is not None and len(data) > 0: |
| 110 | + return [[self.ensure_timestamp_in_seconds(datetime.strptime(row["date"], '%Y-%m-%dT%H:%M:%S.%fZ').timestamp()), |
| 111 | + row["open"] if row["open"] != 'None' else None, |
| 112 | + row["high"] if row["high"] != 'None' else None, |
| 113 | + row["low"] if row["low"] != 'None' else None, |
| 114 | + row["close"] if row["close"] != 'None' else None, |
| 115 | + row["volume"] if row["volume"] != 'None' else None, |
| 116 | + 0., 0., 0., 0.] for row in data] |
| 117 | + |
| 118 | + def ws_subscription_payload(self): |
| 119 | + interval = CONSTANTS.INTERVALS[self.interval] |
| 120 | + trading_pair = self.get_exchange_trading_pair(self._trading_pair) |
| 121 | + |
| 122 | + payload = { |
| 123 | + "pair": trading_pair, |
| 124 | + "chart": interval, |
| 125 | + "type": "chart-v2-subscribe" |
| 126 | + } |
| 127 | + return payload |
| 128 | + |
| 129 | + def _parse_websocket_message(self, data): |
| 130 | + candles_row_dict: Dict[str, Any] = {} |
| 131 | + if data is not None and data.get("type") == 'liveCandle': |
| 132 | + candle = data.get("data")[-1] |
| 133 | + timestamp = datetime.strptime(candle["date"], '%Y-%m-%dT%H:%M:%SZ').timestamp() |
| 134 | + candles_row_dict["timestamp"] = self.ensure_timestamp_in_seconds(timestamp) |
| 135 | + candles_row_dict["open"] = candle["open"] |
| 136 | + candles_row_dict["low"] = candle["low"] |
| 137 | + candles_row_dict["high"] = candle["high"] |
| 138 | + candles_row_dict["close"] = candle["close"] |
| 139 | + candles_row_dict["volume"] = candle["volume"] |
| 140 | + candles_row_dict["quote_asset_volume"] = 0. |
| 141 | + candles_row_dict["n_trades"] = 0. |
| 142 | + candles_row_dict["taker_buy_base_volume"] = 0. |
| 143 | + candles_row_dict["taker_buy_quote_volume"] = 0. |
| 144 | + return candles_row_dict |
0 commit comments