mexc-websocket added
All checks were successful
SonarQube Scan / SonarQube Trigger (push) Successful in 1m12s
All checks were successful
SonarQube Scan / SonarQube Trigger (push) Successful in 1m12s
This commit is contained in:
25
mexc-socket.py
Normal file
25
mexc-socket.py
Normal file
@ -0,0 +1,25 @@
|
||||
from pymexc import spot, futures
|
||||
from dotenv import load_dotenv
|
||||
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
api_key = os.getenv("API_KEY")
|
||||
api_secret = os.getenv("API_SECRET")
|
||||
|
||||
def handle_message(message):
|
||||
print(message)
|
||||
|
||||
|
||||
|
||||
# initialize WebSocket client
|
||||
ws_spot_client = spot.WebSocket(api_key = api_key, api_secret = api_secret)
|
||||
|
||||
# make http request to api
|
||||
|
||||
# create websocket connection to public channel (spot@public.deals.v3.api@BTCUSDT)
|
||||
# all messages will be handled by function `handle_message`
|
||||
ws_spot_client.deals_stream(handle_message, "BTCUSDT")
|
||||
while True:
|
||||
...
|
60
pymexc/__init__.py
Normal file
60
pymexc/__init__.py
Normal file
@ -0,0 +1,60 @@
|
||||
"""
|
||||
### Usage
|
||||
|
||||
```python
|
||||
from pymexc import spot, futures
|
||||
|
||||
api_key = "YOUR API KEY"
|
||||
api_secret = "YOUR API SECRET KEY"
|
||||
|
||||
def handle_message(message):
|
||||
# handle websocket message
|
||||
print(message)
|
||||
|
||||
|
||||
# SPOT V3
|
||||
# initialize HTTP client
|
||||
spot_client = spot.HTTP(api_key = api_key, api_secret = api_secret)
|
||||
# initialize WebSocket client
|
||||
ws_spot_client = spot.WebSocket(api_key = api_key, api_secret = api_secret)
|
||||
|
||||
# make http request to api
|
||||
print(spot_client.exchange_info())
|
||||
|
||||
# create websocket connection to public channel (spot@public.deals.v3.api@BTCUSDT)
|
||||
# all messages will be handled by function `handle_message`
|
||||
ws_spot_client.deals_stream(handle_message, "BTCUSDT")
|
||||
|
||||
|
||||
# FUTURES V1
|
||||
|
||||
# initialize HTTP client
|
||||
futures_client = futures.HTTP(api_key = api_key, api_secret = api_secret)
|
||||
# initialize WebSocket client
|
||||
ws_futures_client = futures.WebSocket(api_key = api_key, api_secret = api_secret)
|
||||
|
||||
# make http request to api
|
||||
print(futures_client.index_price("MX_USDT"))
|
||||
|
||||
# create websocket connection to public channel (sub.tickers)
|
||||
# all messages will be handled by function `handle_message`
|
||||
ws_futures_client.tickers_stream(handle_message)
|
||||
|
||||
# loop forever for save websocket connection
|
||||
while True:
|
||||
...
|
||||
|
||||
"""
|
||||
try:
|
||||
from . import futures
|
||||
from . import spot
|
||||
except ImportError:
|
||||
import futures
|
||||
import spot
|
||||
|
||||
|
||||
|
||||
__all__ = [
|
||||
"futures",
|
||||
"spot"
|
||||
]
|
164
pymexc/base.py
Normal file
164
pymexc/base.py
Normal file
@ -0,0 +1,164 @@
|
||||
from abc import ABC, abstractclassmethod
|
||||
from typing import Union, Literal
|
||||
import hmac
|
||||
import hashlib
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
import logging
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MexcAPIError(Exception):
|
||||
pass
|
||||
|
||||
class MexcSDK(ABC):
|
||||
"""
|
||||
Initializes a new instance of the class with the given `api_key` and `api_secret` parameters.
|
||||
|
||||
:param api_key: A string representing the API key.
|
||||
:param api_secret: A string representing the API secret.
|
||||
:param base_url: A string representing the base URL of the API.
|
||||
"""
|
||||
def __init__(self, api_key: str, api_secret: str, base_url: str, proxies: dict = None):
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
|
||||
self.recvWindow = 5000
|
||||
|
||||
self.base_url = base_url
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
|
||||
if proxies:
|
||||
self.session.proxies.update(proxies)
|
||||
|
||||
|
||||
@abstractclassmethod
|
||||
def sign(self, **kwargs) -> str:
|
||||
...
|
||||
|
||||
@abstractclassmethod
|
||||
def call(self, method: Union[Literal["GET"], Literal["POST"], Literal["PUT"], Literal["DELETE"]], router: str, *args, **kwargs) -> dict:
|
||||
...
|
||||
|
||||
class _SpotHTTP(MexcSDK):
|
||||
def __init__(self, api_key: str = None, api_secret: str = None, proxies: dict = None):
|
||||
super().__init__(api_key, api_secret, "https://api.mexc.com", proxies = proxies)
|
||||
|
||||
self.session.headers.update({
|
||||
"X-MEXC-APIKEY": self.api_key
|
||||
})
|
||||
|
||||
def sign(self, query_string: str) -> str:
|
||||
"""
|
||||
Generates a signature for an API request using HMAC SHA256 encryption.
|
||||
|
||||
Args:
|
||||
**kwargs: Arbitrary keyword arguments representing request parameters.
|
||||
|
||||
Returns:
|
||||
A hexadecimal string representing the signature of the request.
|
||||
"""
|
||||
# Generate signature
|
||||
signature = hmac.new(self.api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
return signature
|
||||
|
||||
def call(self, method: Union[Literal["GET"], Literal["POST"], Literal["PUT"], Literal["DELETE"]], router: str, auth: bool = True, *args, **kwargs) -> dict:
|
||||
if not router.startswith("/"):
|
||||
router = f"/{router}"
|
||||
|
||||
# clear None values
|
||||
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
||||
|
||||
if kwargs.get('params'):
|
||||
kwargs['params'] = {k: v for k, v in kwargs['params'].items() if v is not None}
|
||||
else:
|
||||
kwargs['params'] = {}
|
||||
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
kwargs['params']['timestamp'] = timestamp
|
||||
kwargs['params']['recvWindow'] = self.recvWindow
|
||||
|
||||
kwargs['params'] = {k: v for k, v in sorted(kwargs['params'].items())}
|
||||
params = urlencode(kwargs.pop('params'), doseq=True).replace('+', '%20')
|
||||
|
||||
if self.api_key and self.api_secret and auth:
|
||||
params += "&signature=" + self.sign(params)
|
||||
|
||||
|
||||
response = self.session.request(method, f"{self.base_url}{router}", params = params, *args, **kwargs)
|
||||
|
||||
if not response.ok:
|
||||
raise MexcAPIError(f'(code={response.json()["code"]}): {response.json()["msg"]}')
|
||||
|
||||
return response.json()
|
||||
|
||||
class _FuturesHTTP(MexcSDK):
|
||||
def __init__(self, api_key: str = None, api_secret: str = None, proxies: dict = None):
|
||||
super().__init__(api_key, api_secret, "https://contract.mexc.com", proxies = proxies)
|
||||
|
||||
self.session.headers.update({
|
||||
"Content-Type": "application/json",
|
||||
"ApiKey": self.api_key
|
||||
})
|
||||
|
||||
def sign(self, timestamp: str, **kwargs) -> str:
|
||||
"""
|
||||
Generates a signature for an API request using HMAC SHA256 encryption.
|
||||
|
||||
:param timestamp: A string representing the timestamp of the request.
|
||||
:type timestamp: str
|
||||
:param kwargs: Arbitrary keyword arguments representing request parameters.
|
||||
:type kwargs: dict
|
||||
|
||||
:return: A hexadecimal string representing the signature of the request.
|
||||
:rtype: str
|
||||
"""
|
||||
# Generate signature
|
||||
query_string = "&".join([f"{k}={v}" for k, v in sorted(kwargs.items())])
|
||||
query_string = self.api_key + timestamp + query_string
|
||||
signature = hmac.new(self.api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
return signature
|
||||
|
||||
def call(self, method: Union[Literal["GET"], Literal["POST"], Literal["PUT"], Literal["DELETE"]], router: str, *args, **kwargs) -> dict:
|
||||
"""
|
||||
Makes a request to the specified HTTP method and router using the provided arguments.
|
||||
|
||||
:param method: A string that represents the HTTP method(GET, POST, PUT, or DELETE) to be used.
|
||||
:type method: str
|
||||
:param router: A string that represents the API endpoint to be called.
|
||||
:type router: str
|
||||
:param *args: Variable length argument list.
|
||||
:type *args: list
|
||||
:param **kwargs: Arbitrary keyword arguments.
|
||||
:type **kwargs: dict
|
||||
|
||||
:return: A dictionary containing the JSON response of the request.
|
||||
"""
|
||||
|
||||
if not router.startswith("/"):
|
||||
router = f"/{router}"
|
||||
|
||||
# clear None values
|
||||
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
||||
|
||||
for variant in ('params', 'json'):
|
||||
if kwargs.get(variant):
|
||||
kwargs[variant] = {k: v for k, v in kwargs[variant].items() if v is not None}
|
||||
|
||||
if self.api_key and self.api_secret:
|
||||
# add signature
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
|
||||
kwargs['headers'] = {
|
||||
"Request-Time": timestamp,
|
||||
"Signature": self.sign(timestamp, **kwargs[variant])
|
||||
}
|
||||
|
||||
response = self.session.request(method, f"{self.base_url}{router}", *args, **kwargs)
|
||||
|
||||
return response.json()
|
509
pymexc/base_websocket.py
Normal file
509
pymexc/base_websocket.py
Normal file
@ -0,0 +1,509 @@
|
||||
import websocket
|
||||
import threading
|
||||
import logging
|
||||
import time
|
||||
import json
|
||||
import hmac
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SPOT = "wss://wbs.mexc.com/ws"
|
||||
FUTURES = "wss://contract.mexc.com/ws"
|
||||
|
||||
class _WebSocketManager:
|
||||
def __init__(self, callback_function, ws_name, api_key=None, api_secret=None,
|
||||
ping_interval=20, ping_timeout=10, retries=10,
|
||||
restart_on_error=True, trace_logging=False,
|
||||
http_proxy_host = None,
|
||||
http_proxy_port = None,
|
||||
http_no_proxy = None,
|
||||
http_proxy_auth = None,
|
||||
http_proxy_timeout = None):
|
||||
|
||||
self.proxy_settings = dict(
|
||||
http_proxy_host = http_proxy_host,
|
||||
http_proxy_port = http_proxy_port,
|
||||
http_no_proxy = http_no_proxy,
|
||||
http_proxy_auth = http_proxy_auth,
|
||||
http_proxy_timeout = http_proxy_timeout
|
||||
)
|
||||
|
||||
# Set API keys.
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
|
||||
self.callback = callback_function
|
||||
self.ws_name = ws_name
|
||||
if api_key:
|
||||
self.ws_name += " (Auth)"
|
||||
|
||||
# Setup the callback directory following the format:
|
||||
# {
|
||||
# "topic_name": function
|
||||
# }
|
||||
self.callback_directory = {}
|
||||
|
||||
# Record the subscriptions made so that we can resubscribe if the WSS
|
||||
# connection is broken.
|
||||
self.subscriptions = []
|
||||
|
||||
# Set ping settings.
|
||||
self.ping_interval = ping_interval
|
||||
self.ping_timeout = ping_timeout
|
||||
self.retries = retries
|
||||
|
||||
# Other optional data handling settings.
|
||||
self.handle_error = restart_on_error
|
||||
|
||||
# Enable websocket-client's trace logging for extra debug information
|
||||
# on the websocket connection, including the raw sent & recv messages
|
||||
websocket.enableTrace(trace_logging)
|
||||
|
||||
# Set initial state, initialize dictionary and connect.
|
||||
self._reset()
|
||||
self.attempting_connection = False
|
||||
|
||||
def _on_open(self):
|
||||
"""
|
||||
Log WS open.
|
||||
"""
|
||||
logger.debug(f"WebSocket {self.ws_name} opened.")
|
||||
|
||||
def _on_message(self, message):
|
||||
"""
|
||||
Parse incoming messages.
|
||||
"""
|
||||
self.callback(json.loads(message))
|
||||
|
||||
def is_connected(self):
|
||||
try:
|
||||
if self.ws.sock or not self.ws.sock.is_connected:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _are_connections_connected(active_connections):
|
||||
for connection in active_connections:
|
||||
if not connection.is_connected():
|
||||
return False
|
||||
return True
|
||||
|
||||
def _ping_loop(self, ping_payload: str, ping_interval: int, ping_timeout: int):
|
||||
"""
|
||||
Ping the websocket.
|
||||
"""
|
||||
time.sleep(ping_timeout)
|
||||
while True:
|
||||
logger.info(f"WebSocket {self.ws_name} send ping...")
|
||||
self.ws.send(ping_payload)
|
||||
time.sleep(ping_interval)
|
||||
|
||||
def _connect(self, url):
|
||||
"""
|
||||
Open websocket in a thread.
|
||||
"""
|
||||
def resubscribe_to_topics():
|
||||
if not self.subscriptions:
|
||||
# There are no subscriptions to resubscribe to, probably
|
||||
# because this is a brand new WSS initialisation so there was
|
||||
# no previous WSS connection.
|
||||
return
|
||||
for subscription_message in self.subscriptions:
|
||||
self.ws.send(subscription_message)
|
||||
|
||||
self.attempting_connection = True
|
||||
|
||||
# Set endpoint.
|
||||
self.endpoint = url
|
||||
|
||||
|
||||
# Attempt to connect for X seconds.
|
||||
retries = self.retries
|
||||
if retries == 0:
|
||||
infinitely_reconnect = True
|
||||
else:
|
||||
infinitely_reconnect = False
|
||||
while (infinitely_reconnect or retries > 0) and not self.is_connected():
|
||||
logger.info(f"WebSocket {self.ws_name} attempting connection...")
|
||||
self.ws = websocket.WebSocketApp(
|
||||
url=url,
|
||||
on_message=lambda ws, msg: self._on_message(msg),
|
||||
on_close=self._on_close(),
|
||||
on_open=self._on_open(),
|
||||
on_error=lambda ws, err: self._on_error(err)
|
||||
)
|
||||
|
||||
# Setup the thread running WebSocketApp.
|
||||
self.wst = threading.Thread(target=lambda: self.ws.run_forever(
|
||||
ping_interval=self.ping_interval,
|
||||
ping_timeout=self.ping_timeout,
|
||||
**self.proxy_settings
|
||||
))
|
||||
|
||||
# Configure as daemon; start.
|
||||
self.wst.daemon = True
|
||||
self.wst.start()
|
||||
|
||||
# setup ping loop
|
||||
self.wsl = threading.Thread(target=lambda: self._ping_loop(
|
||||
ping_payload='{"method":"ping"}',
|
||||
ping_interval=self.ping_interval,
|
||||
ping_timeout=self.ping_timeout
|
||||
))
|
||||
|
||||
self.wsl.daemon = True
|
||||
self.wsl.start()
|
||||
|
||||
retries -= 1
|
||||
time.sleep(1)
|
||||
|
||||
# If connection was not successful, raise error.
|
||||
if not infinitely_reconnect and retries <= 0:
|
||||
self.exit()
|
||||
raise websocket.WebSocketTimeoutException(
|
||||
f"WebSocket {self.ws_name} connection failed. Too many "
|
||||
f"connection attempts. pybit will "
|
||||
f"no longer try to reconnect.")
|
||||
|
||||
logger.info(f"WebSocket {self.ws_name} connected")
|
||||
|
||||
# If given an api_key, authenticate.
|
||||
if self.api_key and self.api_secret:
|
||||
self._auth()
|
||||
|
||||
resubscribe_to_topics()
|
||||
|
||||
self.attempting_connection = False
|
||||
|
||||
def _auth(self):
|
||||
# Generate signature
|
||||
|
||||
# make auth if futures. spot has a different auth system.
|
||||
|
||||
isspot = self.endpoint.startswith(SPOT)
|
||||
if isspot:
|
||||
return
|
||||
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
_val = self.api_key + timestamp
|
||||
signature = str(hmac.new(
|
||||
bytes(self.api_secret, "utf-8"),
|
||||
bytes(_val, "utf-8"), digestmod="sha256"
|
||||
).hexdigest())
|
||||
|
||||
# Authenticate with API.
|
||||
self.ws.send(
|
||||
json.dumps({
|
||||
"subscribe": False,
|
||||
"method": "login",
|
||||
"param": {
|
||||
"apiKey": self.api_key,
|
||||
"reqTime": timestamp,
|
||||
"signature": signature
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
def _on_error(self, error):
|
||||
"""
|
||||
Exit on errors and raise exception, or attempt reconnect.
|
||||
"""
|
||||
if type(error).__name__ not in ["WebSocketConnectionClosedException",
|
||||
"ConnectionResetError",
|
||||
"WebSocketTimeoutException"]:
|
||||
# Raises errors not related to websocket disconnection.
|
||||
self.exit()
|
||||
raise error
|
||||
|
||||
if not self.exited:
|
||||
logger.error(f"WebSocket {self.ws_name} encountered error: {error}.")
|
||||
self.exit()
|
||||
|
||||
# Reconnect.
|
||||
if self.handle_error and not self.attempting_connection:
|
||||
self._reset()
|
||||
self._connect(self.endpoint)
|
||||
|
||||
def _on_close(self):
|
||||
"""
|
||||
Log WS close.
|
||||
"""
|
||||
logger.debug(f"WebSocket {self.ws_name} closed.")
|
||||
|
||||
def _reset(self):
|
||||
"""
|
||||
Set state booleans and initialize dictionary.
|
||||
"""
|
||||
self.exited = False
|
||||
self.auth = False
|
||||
self.data = {}
|
||||
|
||||
def exit(self):
|
||||
"""
|
||||
Closes the websocket connection.
|
||||
"""
|
||||
|
||||
self.ws.close()
|
||||
while self.ws.sock:
|
||||
continue
|
||||
self.exited = True
|
||||
|
||||
class _FuturesWebSocketManager(_WebSocketManager):
|
||||
def __init__(self, ws_name, **kwargs):
|
||||
callback_function = kwargs.pop("callback_function") if \
|
||||
kwargs.get("callback_function") else self._handle_incoming_message
|
||||
|
||||
super().__init__(callback_function, ws_name, **kwargs)
|
||||
|
||||
self.private_topics = ["personal.order", "personal.asset",
|
||||
"personal.position", "personal.risk.limit",
|
||||
"personal.adl.level", "personal.position.mode"]
|
||||
|
||||
self.symbol_wildcard = "*"
|
||||
self.symbol_separator = "|"
|
||||
self.last_subsctiption = None
|
||||
|
||||
def subscribe(self, topic, callback, params: dict = {}):
|
||||
subscription_args = {
|
||||
"method": topic,
|
||||
"param": params
|
||||
}
|
||||
self._check_callback_directory(subscription_args)
|
||||
|
||||
while not self.is_connected():
|
||||
# Wait until the connection is open before subscribing.
|
||||
time.sleep(0.1)
|
||||
|
||||
subscription_message = json.dumps(subscription_args)
|
||||
self.ws.send(subscription_message)
|
||||
self.subscriptions.append(subscription_message)
|
||||
self._set_callback(topic.replace("sub.", ""), callback)
|
||||
self.last_subsctiption = topic.replace("sub.", "")
|
||||
|
||||
def _initialise_local_data(self, topic):
|
||||
# Create self.data
|
||||
try:
|
||||
self.data[topic]
|
||||
except KeyError:
|
||||
self.data[topic] = []
|
||||
|
||||
def _process_auth_message(self, message):
|
||||
# If we get successful futures auth, notify user
|
||||
if message.get("data") == "success":
|
||||
logger.debug(f"Authorization for {self.ws_name} successful.")
|
||||
self.auth = True
|
||||
# If we get unsuccessful auth, notify user.
|
||||
elif message.get("data") != "success": # !!!!
|
||||
logger.debug(f"Authorization for {self.ws_name} failed. Please "
|
||||
f"check your API keys and restart.")
|
||||
|
||||
def _process_subscription_message(self, message):
|
||||
#try:
|
||||
sub = message["channel"]
|
||||
#except KeyError:
|
||||
#sub = message["c"] # SPOT PUBLIC & PRIVATE format
|
||||
|
||||
# If we get successful futures subscription, notify user
|
||||
if (
|
||||
message.get("channel", "").startswith("rs.") or
|
||||
message.get("channel", "").startswith("push.")
|
||||
) and message.get("channel", "") != "rs.error":
|
||||
|
||||
logger.debug(f"Subscription to {sub} successful.")
|
||||
# Futures subscription fail
|
||||
else:
|
||||
response = message["data"]
|
||||
logger.error("Couldn't subscribe to topic. "
|
||||
f"Error: {response}.")
|
||||
if self.last_subsctiption:
|
||||
self._pop_callback(self.last_subsctiption)
|
||||
|
||||
def _process_normal_message(self, message):
|
||||
topic = message["channel"].replace("push.", "").replace("rs.sub.", "")
|
||||
callback_data = message
|
||||
callback_function = self._get_callback(topic)
|
||||
callback_function(callback_data)
|
||||
|
||||
def _handle_incoming_message(self, message):
|
||||
def is_auth_message():
|
||||
if message.get("channel", "") == "rs.login":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_subscription_message():
|
||||
if str(message).startswith("{'channel': 'push."):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_pong_message():
|
||||
if message.get("channel", "") in ("pong", "clientId"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if is_auth_message():
|
||||
self._process_auth_message(message)
|
||||
elif is_subscription_message():
|
||||
self._process_subscription_message(message)
|
||||
elif is_pong_message():
|
||||
pass
|
||||
else:
|
||||
self._process_normal_message(message)
|
||||
|
||||
def custom_topic_stream(self, topic, callback):
|
||||
return self.subscribe(topic=topic, callback=callback)
|
||||
|
||||
def _check_callback_directory(self, topics):
|
||||
for topic in topics:
|
||||
if topic in self.callback_directory:
|
||||
raise Exception(f"You have already subscribed to this topic: "
|
||||
f"{topic}")
|
||||
|
||||
def _set_callback(self, topic, callback_function):
|
||||
self.callback_directory[topic] = callback_function
|
||||
|
||||
def _get_callback(self, topic):
|
||||
return self.callback_directory[topic]
|
||||
|
||||
def _pop_callback(self, topic):
|
||||
self.callback_directory.pop(topic)
|
||||
|
||||
class _FuturesWebSocket(_FuturesWebSocketManager):
|
||||
def __init__(self, **kwargs):
|
||||
self.ws_name = "FuturesV1"
|
||||
self.endpoint = "wss://contract.mexc.com/ws"
|
||||
|
||||
super().__init__(self.ws_name, **kwargs)
|
||||
self.ws = None
|
||||
|
||||
self.active_connections = []
|
||||
self.kwargs = kwargs
|
||||
|
||||
def is_connected(self):
|
||||
return self._are_connections_connected(self.active_connections)
|
||||
|
||||
def _ws_subscribe(self, topic, callback, params: list = []):
|
||||
if not self.ws:
|
||||
self.ws = _FuturesWebSocketManager(
|
||||
self.ws_name, **self.kwargs)
|
||||
self.ws._connect(self.endpoint)
|
||||
self.active_connections.append(self.ws)
|
||||
self.ws.subscribe(topic, callback, params)
|
||||
|
||||
class _SpotWebSocketManager(_WebSocketManager):
|
||||
def __init__(self, ws_name, **kwargs):
|
||||
callback_function = kwargs.pop("callback_function") if \
|
||||
kwargs.get("callback_function") else self._handle_incoming_message
|
||||
super().__init__(callback_function, ws_name, **kwargs)
|
||||
|
||||
self.private_topics = ["account", "deals", "orders"]
|
||||
|
||||
self.last_subsctiption = None
|
||||
|
||||
def subscribe(self, topic: str, callback, params_list: list):
|
||||
subscription_args = {
|
||||
"method": "SUBSCRIPTION",
|
||||
"params": [
|
||||
'@'.join([f"spot@{topic}.v3.api"] + list(map(str, params.values())))
|
||||
for params in params_list
|
||||
]
|
||||
}
|
||||
self._check_callback_directory(subscription_args)
|
||||
|
||||
while not self.is_connected():
|
||||
# Wait until the connection is open before subscribing.
|
||||
time.sleep(0.1)
|
||||
|
||||
subscription_message = json.dumps(subscription_args)
|
||||
self.ws.send(subscription_message)
|
||||
self.subscriptions.append(subscription_message)
|
||||
self._set_callback(topic, callback)
|
||||
self.last_subsctiption = topic
|
||||
|
||||
def _initialise_local_data(self, topic):
|
||||
# Create self.data
|
||||
try:
|
||||
self.data[topic]
|
||||
except KeyError:
|
||||
self.data[topic] = []
|
||||
|
||||
|
||||
def _process_subscription_message(self, message):
|
||||
sub = message["msg"].replace("spot@", "").split(".v3.api")[0]
|
||||
|
||||
# If we get successful futures subscription, notify user
|
||||
if message.get("id") == 0 and message.get("code") == 0:
|
||||
logger.debug(f"Subscription to {sub} successful.")
|
||||
# Futures subscription fail
|
||||
else:
|
||||
response = message["msg"]
|
||||
logger.error("Couldn't subscribe to topic. "
|
||||
f"Error: {response}.")
|
||||
if self.last_subsctiption:
|
||||
self._pop_callback(self.last_subsctiption)
|
||||
|
||||
def _process_normal_message(self, message):
|
||||
topic = message["c"].replace("spot@", "").split(".v3.api")[0]
|
||||
callback_data = message
|
||||
callback_function = self._get_callback(topic)
|
||||
callback_function(callback_data)
|
||||
|
||||
def _handle_incoming_message(self, message):
|
||||
def is_subscription_message():
|
||||
if (message.get("id") == 0 and
|
||||
message.get("code") == 0 and
|
||||
message.get("msg")):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if is_subscription_message():
|
||||
self._process_subscription_message(message)
|
||||
else:
|
||||
self._process_normal_message(message)
|
||||
|
||||
def custom_topic_stream(self, topic, callback):
|
||||
return self.subscribe(topic=topic, callback=callback)
|
||||
|
||||
def _check_callback_directory(self, topics):
|
||||
for topic in topics:
|
||||
if topic in self.callback_directory:
|
||||
raise Exception(f"You have already subscribed to this topic: "
|
||||
f"{topic}")
|
||||
|
||||
def _set_callback(self, topic, callback_function):
|
||||
self.callback_directory[topic] = callback_function
|
||||
|
||||
def _get_callback(self, topic):
|
||||
return self.callback_directory[topic]
|
||||
|
||||
def _pop_callback(self, topic):
|
||||
self.callback_directory.pop(topic)
|
||||
|
||||
class _SpotWebSocket(_SpotWebSocketManager):
|
||||
def __init__(self, endpoint: str = "wss://wbs.mexc.com/ws", **kwargs):
|
||||
self.ws_name = "SpotV3"
|
||||
self.endpoint = endpoint
|
||||
|
||||
|
||||
super().__init__(self.ws_name, **kwargs)
|
||||
self.ws = None
|
||||
|
||||
self.active_connections = []
|
||||
self.kwargs = kwargs
|
||||
|
||||
def is_connected(self):
|
||||
return self._are_connections_connected(self.active_connections)
|
||||
|
||||
def _ws_subscribe(self, topic, callback, params: list = []):
|
||||
if not self.ws:
|
||||
self.ws = _SpotWebSocketManager(
|
||||
self.ws_name, **self.kwargs)
|
||||
self.ws._connect(self.endpoint)
|
||||
self.active_connections.append(self.ws)
|
||||
self.ws.subscribe(topic, callback, params)
|
1694
pymexc/futures.py
Normal file
1694
pymexc/futures.py
Normal file
File diff suppressed because it is too large
Load Diff
1967
pymexc/spot.py
Normal file
1967
pymexc/spot.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user