forked from hummingbot/hummingbot
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Xeggex.com
authored
Mar 4, 2024
1 parent
30f1345
commit b6c3c7a
Showing
22 changed files
with
9,817 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
Binary file added
BIN
+173 Bytes
hummingbot/connector/exchange/xeggexconnector/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+6.04 KB
...or/exchange/xeggexconnector/__pycache__/xeggex_api_order_book_data_source.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+3.53 KB
...r/exchange/xeggexconnector/__pycache__/xeggex_api_user_stream_data_source.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+3.12 KB
hummingbot/connector/exchange/xeggexconnector/__pycache__/xeggex_auth.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+2.72 KB
hummingbot/connector/exchange/xeggexconnector/__pycache__/xeggex_constants.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+18 KB
hummingbot/connector/exchange/xeggexconnector/__pycache__/xeggex_exchange.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+3.58 KB
hummingbot/connector/exchange/xeggexconnector/__pycache__/xeggex_order_book.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+2.29 KB
hummingbot/connector/exchange/xeggexconnector/__pycache__/xeggex_utils.cpython-310.pyc
Binary file not shown.
Binary file added
BIN
+3.01 KB
hummingbot/connector/exchange/xeggexconnector/__pycache__/xeggex_web_utils.cpython-310.pyc
Binary file not shown.
8,657 changes: 8,657 additions & 0 deletions
8,657
hummingbot/connector/exchange/xeggexconnector/dummy.cpp
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file added
BIN
+75.5 KB
hummingbot/connector/exchange/xeggexconnector/dummy.cpython-310-x86_64-linux-gnu.so
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cdef class dummy(): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cdef class dummy(): | ||
pass |
141 changes: 141 additions & 0 deletions
141
hummingbot/connector/exchange/xeggexconnector/xeggex_api_order_book_data_source.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import asyncio | ||
import time | ||
from typing import TYPE_CHECKING, Any, Dict, List, Optional | ||
|
||
from hummingbot.connector.exchange.xeggex import xeggex_constants as CONSTANTS, xeggex_web_utils as web_utils | ||
from hummingbot.connector.exchange.xeggex.xeggex_order_book import XeggexOrderBook | ||
from hummingbot.core.data_type.order_book_message import OrderBookMessage | ||
from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource | ||
from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest | ||
from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory | ||
from hummingbot.core.web_assistant.ws_assistant import WSAssistant | ||
from hummingbot.logger import HummingbotLogger | ||
|
||
if TYPE_CHECKING: | ||
from hummingbot.connector.exchange.xeggex.xeggex_exchange import XeggexExchange | ||
|
||
|
||
class XeggexAPIOrderBookDataSource(OrderBookTrackerDataSource): | ||
HEARTBEAT_TIME_INTERVAL = 30.0 | ||
TRADE_STREAM_ID = 1 | ||
DIFF_STREAM_ID = 2 | ||
ONE_HOUR = 60 * 60 | ||
|
||
_logger: Optional[HummingbotLogger] = None | ||
|
||
def __init__(self, | ||
trading_pairs: List[str], | ||
connector: 'XeggexExchange', | ||
api_factory: WebAssistantsFactory, | ||
domain: str = CONSTANTS.DEFAULT_DOMAIN): | ||
super().__init__(trading_pairs) | ||
self._connector = connector | ||
self._trade_messages_queue_key = CONSTANTS.TRADE_EVENT_TYPE | ||
self._diff_messages_queue_key = CONSTANTS.DIFF_EVENT_TYPE | ||
self._domain = domain | ||
self._api_factory = api_factory | ||
|
||
async def get_last_traded_prices(self, | ||
trading_pairs: List[str], | ||
domain: Optional[str] = None) -> Dict[str, float]: | ||
return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) | ||
|
||
async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: | ||
""" | ||
Retrieves a copy of the full order book from the exchange, for a particular trading pair. | ||
:param trading_pair: the trading pair for which the order book will be retrieved | ||
:return: the response from the exchange (JSON dictionary) | ||
""" | ||
params = { | ||
"ticker_id": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), | ||
"depth": "1000" | ||
} | ||
|
||
rest_assistant = await self._api_factory.get_rest_assistant() | ||
data = await rest_assistant.execute_request( | ||
url=web_utils.public_rest_url(path_url=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL), | ||
params=params, | ||
method=RESTMethod.GET, | ||
throttler_limit_id=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, | ||
) | ||
|
||
return data | ||
|
||
async def _subscribe_channels(self, ws: WSAssistant): | ||
""" | ||
Subscribes to the trade events and diff orders events through the provided websocket connection. | ||
:param ws: the websocket assistant used to connect to the exchange | ||
""" | ||
try: | ||
for trading_pair in self._trading_pairs: | ||
symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) | ||
|
||
payload = { | ||
"method": CONSTANTS.WS_METHOD_SUBSCRIBE_TRADES, | ||
"params": {"symbol": symbol} | ||
|
||
} | ||
subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=payload) | ||
|
||
payload = { | ||
"method": CONSTANTS.WS_METHOD_SUBSCRIBE_ORDERBOOK, | ||
"params": {"symbol": symbol, "limit": 100} | ||
} | ||
|
||
subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=payload) | ||
|
||
await ws.send(subscribe_trade_request) | ||
await ws.send(subscribe_orderbook_request) | ||
|
||
self.logger().info("Subscribed to public order book and trade channels...") | ||
except asyncio.CancelledError: | ||
raise | ||
except Exception: | ||
self.logger().error( | ||
"Unexpected error occurred subscribing to order book trading and delta streams...", | ||
exc_info=True | ||
) | ||
raise | ||
|
||
async def _connected_websocket_assistant(self) -> WSAssistant: | ||
ws: WSAssistant = await self._api_factory.get_ws_assistant() | ||
await ws.connect(ws_url=CONSTANTS.WS_URL, | ||
ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) | ||
return ws | ||
|
||
async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: | ||
snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) | ||
snapshot_timestamp: float = time.time() | ||
snapshot_msg: OrderBookMessage = XeggexOrderBook.snapshot_message_from_exchange( | ||
snapshot, | ||
snapshot_timestamp, | ||
metadata={"trading_pair": trading_pair} | ||
) | ||
return snapshot_msg | ||
|
||
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.get("params", {}).get("symbol")) | ||
trade_message = XeggexOrderBook.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.get("params", {}).get("symbol")) | ||
order_book_message: OrderBookMessage = XeggexOrderBook.diff_message_from_exchange( | ||
raw_message, time.time(), {"trading_pair": trading_pair}) | ||
|
||
message_queue.put_nowait(order_book_message) | ||
|
||
def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: | ||
channel = "" | ||
if "result" not in event_message: | ||
event_type = event_message.get("method") | ||
if event_type == CONSTANTS.TRADE_EVENT_TYPE: | ||
channel = self._trade_messages_queue_key | ||
elif event_type == CONSTANTS.DIFF_EVENT_TYPE: | ||
channel = self._diff_messages_queue_key | ||
return channel |
81 changes: 81 additions & 0 deletions
81
hummingbot/connector/exchange/xeggexconnector/xeggex_api_user_stream_data_source.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import asyncio | ||
import time | ||
from typing import TYPE_CHECKING, List, Optional | ||
|
||
from hummingbot.connector.exchange.xeggex import xeggex_constants as CONSTANTS, xeggex_web_utils as web_utils | ||
from hummingbot.connector.exchange.xeggex.xeggex_auth import XeggexAuth | ||
from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource | ||
from hummingbot.core.utils.async_utils import safe_ensure_future | ||
from hummingbot.core.web_assistant.connections.data_types import RESTMethod | ||
from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory | ||
from hummingbot.core.web_assistant.ws_assistant import WSAssistant | ||
from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest | ||
from hummingbot.logger import HummingbotLogger | ||
|
||
if TYPE_CHECKING: | ||
from hummingbot.connector.exchange.Xeggex.Xeggex_exchange import XeggexExchange | ||
|
||
|
||
class XeggexAPIUserStreamDataSource(UserStreamTrackerDataSource): | ||
|
||
HEARTBEAT_TIME_INTERVAL = 30.0 | ||
|
||
_logger: Optional[HummingbotLogger] = None | ||
|
||
def __init__(self, | ||
auth: XeggexAuth, | ||
trading_pairs: List[str], | ||
connector: 'XeggexExchange', | ||
api_factory: WebAssistantsFactory, | ||
domain: str = CONSTANTS.DEFAULT_DOMAIN): | ||
super().__init__() | ||
self._auth: XeggexAuth = auth | ||
self._domain = domain | ||
self._api_factory = api_factory | ||
|
||
async def _connected_websocket_assistant(self) -> WSAssistant: | ||
""" | ||
Creates an instance of WSAssistant connected to the exchange | ||
""" | ||
|
||
ws: WSAssistant = await self._get_ws_assistant() | ||
await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) | ||
await self._authenticate_ws_connection(ws) | ||
return ws | ||
|
||
async def _subscribe_channels(self, websocket_assistant: WSAssistant): | ||
""" | ||
Subscribes to the trade events and diff orders events through the provided websocket connection. | ||
:param websocket_assistant: the websocket assistant used to connect to the exchange | ||
""" | ||
subscribe_user_orders_payload = { | ||
"method": CONSTANTS.WS_METHOD_SUBSCRIBE_USER_ORDERS , | ||
"params": {} | ||
} | ||
subscribe_user_balance_payload = { | ||
"method": CONSTANTS.WS_METHOD_SUBSCRIBE_USER_BALANCE , | ||
"params": {} | ||
} | ||
|
||
subscribe_user_orders_request: WSJSONRequest = WSJSONRequest(payload=subscribe_user_orders_payload) | ||
await websocket_assistant.send(subscribe_user_orders_request) | ||
self.logger().info("Subscribed to user orders") | ||
subscribe_user_balance_request: WSJSONRequest = WSJSONRequest(payload=subscribe_user_balance_payload) | ||
await websocket_assistant.send(subscribe_user_balance_request) | ||
self.logger().info("Subscribed to user balance") | ||
pass | ||
|
||
|
||
async def _get_ws_assistant(self) -> WSAssistant: | ||
if self._ws_assistant is None: | ||
self._ws_assistant = await self._api_factory.get_ws_assistant() | ||
return self._ws_assistant | ||
|
||
|
||
async def _authenticate_ws_connection(self, ws: WSAssistant): | ||
""" | ||
Sends the authentication message. | ||
:param ws: the websocket assistant used to connect to the exchange | ||
""" | ||
auth_message: WSJSONRequest = WSJSONRequest(payload=self._auth.generate_ws_authentication_message()) | ||
await ws.send(auth_message) |
75 changes: 75 additions & 0 deletions
75
hummingbot/connector/exchange/xeggexconnector/xeggex_auth.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import hashlib | ||
import hmac | ||
import json | ||
import random | ||
import string | ||
import time | ||
from collections import OrderedDict | ||
from typing import Any, Dict | ||
from urllib.parse import urlencode | ||
|
||
from hummingbot.connector.time_synchronizer import TimeSynchronizer | ||
from hummingbot.core.web_assistant.auth import AuthBase | ||
from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest | ||
|
||
|
||
class XeggexAuth(AuthBase): | ||
def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): | ||
self.api_key = api_key | ||
self.secret_key = secret_key | ||
self.time_provider = time_provider | ||
|
||
async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: | ||
""" | ||
Adds the server time and the signature to the request, required for authenticated interactions. It also adds | ||
the required parameter in the request header. | ||
:param request: the request to be configured for authenticated interaction | ||
""" | ||
|
||
headers = {} | ||
if request.headers is not None: | ||
headers.update(request.headers) | ||
|
||
if request.method == RESTMethod.GET: | ||
url = f"{request.url}?{urlencode(request.params)}" if request.params else request.url | ||
headers.update(self.header_for_authentication(data=(url))) | ||
|
||
elif request.method == RESTMethod.POST: | ||
json_str = (json.dumps(json.loads(request.data))).replace(" ", "") | ||
to_sign = f"{request.url}{json_str}" | ||
headers.update(self.header_for_authentication(to_sign)) | ||
|
||
request.headers = headers | ||
return request | ||
|
||
async def ws_authenticate(self, request: WSRequest) -> WSRequest: | ||
""" | ||
This method is intended to configure a websocket request to be authenticated. Xeggex does not use this | ||
functionality | ||
""" | ||
return request # pass-through | ||
|
||
def generate_ws_authentication_message(self, request: WSRequest = None, ) -> Dict[str, any]: | ||
random_str = str(random.choices(string.ascii_letters + string.digits, k=14)) | ||
payload = { | ||
"method": "login", | ||
"params": { | ||
"algo": "HS256", | ||
"pKey": self.api_key, | ||
"nonce": random_str, | ||
"signature": self._generate_signature(random_str) | ||
} | ||
} | ||
return payload | ||
|
||
def header_for_authentication(self, data: str) -> Dict[str, str]: | ||
timestamp = int(time.time() * 1000) | ||
message_to_sign = f"{self.api_key}{data}{timestamp}" | ||
signature = self._generate_signature(message_to_sign) | ||
return {"X-API-KEY": self.api_key, | ||
"X-API-NONCE": str(timestamp), | ||
"X-API-SIGN": signature} | ||
|
||
def _generate_signature(self, data: str) -> str: | ||
digest = hmac.new(self.secret_key.encode("utf8"), data.encode("utf8"), hashlib.sha256).hexdigest() | ||
return digest |
Oops, something went wrong.