Skip to content

Commit

Permalink
Merge pull request hummingbot#7134 from Jbekker/feat/gate_io_add_time…
Browse files Browse the repository at this point in the history
…_sync

feat/Gate IO add server time sync
  • Loading branch information
nikspz authored Oct 24, 2024
2 parents facbfc5 + da38aa8 commit 5b1af2f
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 20 deletions.
5 changes: 2 additions & 3 deletions hummingbot/connector/exchange/gate_io/gate_io_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import hashlib
import hmac
import json
import time
from typing import Any, Dict
from urllib.parse import urlparse

Expand Down Expand Up @@ -68,11 +67,11 @@ def _get_auth_headers(self, request: RESTRequest) -> Dict[str, Any]:
def _sign_payload_ws(self, channel, event, time) -> str:
return self._sign(f"channel={channel}&event={event}&time={time}")

def _sign_payload(self, r: RESTRequest) -> (str, int):
def _sign_payload(self, r: RESTRequest) -> tuple[str, int]:
query_string = ""
body = r.data

ts = time.time()
ts = self.time_provider.time()
m = hashlib.sha512()
path = urlparse(r.url).path

Expand Down
6 changes: 6 additions & 0 deletions hummingbot/connector/exchange/gate_io/gate_io_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
TICKER_PATH_URL = "spot/tickers"
ORDER_BOOK_PATH_URL = "spot/order_book"
MY_TRADES_PATH_URL = "spot/my_trades"
SERVER_TIME_URL = "spot/time"

TRADES_ENDPOINT_NAME = "spot.trades"
ORDER_SNAPSHOT_ENDPOINT_NAME = "spot.order_book"
Expand Down Expand Up @@ -64,4 +65,9 @@
RateLimit(limit_id=TICKER_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]),
RateLimit(limit_id=ORDER_BOOK_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]),
RateLimit(limit_id=MY_TRADES_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]),
RateLimit(limit_id=SERVER_TIME_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]),
]

# ERROR LABELS, see https://www.gate.io/docs/developers/apiv4/#label-list
ERR_LABEL_ORDER_NOT_FOUND = "ORDER_NOT_FOUND"
ERR_LABEL_TIME_RELATED_ERROR = "REQUEST_EXPIRED"
16 changes: 4 additions & 12 deletions hummingbot/connector/exchange/gate_io/gate_io_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,26 +108,18 @@ def supported_order_types(self):
return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER]

def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception):
# API documentation does not clarify the error message for timestamp related problems
return False
return CONSTANTS.ERR_LABEL_TIME_RELATED_ERROR in str(request_exception)

def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool:
# TODO: implement this method correctly for the connector
# The default implementation was added when the functionality to detect not found orders was introduced in the
# ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update
# when replacing the dummy implementation
return False
return CONSTANTS.ERR_LABEL_ORDER_NOT_FOUND in str(status_update_exception)

def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool:
# TODO: implement this method correctly for the connector
# The default implementation was added when the functionality to detect not found orders was introduced in the
# ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the
# dummy implementation
return False
return CONSTANTS.ERR_LABEL_ORDER_NOT_FOUND in str(cancelation_exception)

def _create_web_assistants_factory(self) -> WebAssistantsFactory:
return web_utils.build_api_factory(
throttler=self._throttler,
time_synchronizer=self._time_synchronizer,
auth=self._auth)

def _create_order_book_data_source(self) -> OrderBookTrackerDataSource:
Expand Down
37 changes: 32 additions & 5 deletions hummingbot/connector/exchange/gate_io/gate_io_web_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import time
from typing import Any, Dict, Optional
from typing import Any, Callable, Dict, Optional

import hummingbot.connector.exchange.gate_io.gate_io_constants as CONSTANTS
from hummingbot.connector.time_synchronizer import TimeSynchronizer
from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor
from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
from hummingbot.core.web_assistant.auth import AuthBase
from hummingbot.core.web_assistant.connections.data_types import RESTMethod
from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory


Expand All @@ -27,11 +29,27 @@ def private_rest_url(endpoint: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> s

def build_api_factory(
throttler: Optional[AsyncThrottler] = None,
auth: Optional[AuthBase] = None) -> WebAssistantsFactory:
time_synchronizer: Optional[TimeSynchronizer] = None,
domain: str = CONSTANTS.DEFAULT_DOMAIN,
time_provider: Optional[Callable] = None,
auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory:
throttler = throttler or create_throttler()
time_synchronizer = time_synchronizer or TimeSynchronizer()
time_provider = time_provider or (lambda: get_current_server_time(
throttler=throttler,
domain=domain,
))
api_factory = WebAssistantsFactory(
throttler=throttler,
auth=auth)
auth=auth,
rest_pre_processors=[
TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider),
])
return api_factory


def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory:
api_factory = WebAssistantsFactory(throttler=throttler)
return api_factory


Expand All @@ -43,7 +61,16 @@ async def get_current_server_time(
throttler: Optional[AsyncThrottler] = None,
domain: str = CONSTANTS.DEFAULT_DOMAIN,
) -> float:
return time.time()
throttler = throttler or create_throttler()
api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler)
rest_assistant = await api_factory.get_rest_assistant()
response = await rest_assistant.execute_request(
url=public_rest_url(endpoint=CONSTANTS.SERVER_TIME_URL, domain=domain),
method=RESTMethod.GET,
throttler_limit_id=CONSTANTS.SERVER_TIME_URL,
)
server_time = response["server_time"]
return server_time


def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1802,3 +1802,8 @@ def test_initial_status_dict(self):

self.assertEqual(expected_initial_dict, status_dict)
self.assertFalse(self.exchange.ready)

def test_time_synchronizer_related_request_error_detection(self):
exception = IOError("HTTP status is 403. "
"Error: {'label':'REQUEST_EXPIRED','message':'gap between request Timestamp and server time exceeds 60'}")
self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception))

0 comments on commit 5b1af2f

Please sign in to comment.