Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
Xeggex.com authored Mar 4, 2024
1 parent 3adbce9 commit 9724d45
Show file tree
Hide file tree
Showing 22 changed files with 9,817 additions and 0 deletions.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8,657 changes: 8,657 additions & 0 deletions hummingbot/connector/exchange/xeggex/dummy.cpp

Large diffs are not rendered by default.

Binary file not shown.
2 changes: 2 additions & 0 deletions hummingbot/connector/exchange/xeggex/dummy.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cdef class dummy():
pass
2 changes: 2 additions & 0 deletions hummingbot/connector/exchange/xeggex/dummy.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cdef class dummy():
pass
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
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 hummingbot/connector/exchange/xeggex/xeggex_auth.py
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
Loading

0 comments on commit 9724d45

Please sign in to comment.