From 9e9cbf3547539d081939c551f56f9403dbe2c549 Mon Sep 17 00:00:00 2001 From: dongho Date: Fri, 20 Dec 2024 20:48:02 +0900 Subject: [PATCH] mexc-websocket added --- mexc-socket.py | 25 + pymexc/__init__.py | 60 ++ pymexc/base.py | 164 ++++ pymexc/base_websocket.py | 509 ++++++++++ pymexc/futures.py | 1694 ++++++++++++++++++++++++++++++++ pymexc/spot.py | 1967 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 4419 insertions(+) create mode 100644 mexc-socket.py create mode 100644 pymexc/__init__.py create mode 100644 pymexc/base.py create mode 100644 pymexc/base_websocket.py create mode 100644 pymexc/futures.py create mode 100644 pymexc/spot.py diff --git a/mexc-socket.py b/mexc-socket.py new file mode 100644 index 0000000..eba4857 --- /dev/null +++ b/mexc-socket.py @@ -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: + ... diff --git a/pymexc/__init__.py b/pymexc/__init__.py new file mode 100644 index 0000000..5016deb --- /dev/null +++ b/pymexc/__init__.py @@ -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" +] \ No newline at end of file diff --git a/pymexc/base.py b/pymexc/base.py new file mode 100644 index 0000000..4c3f954 --- /dev/null +++ b/pymexc/base.py @@ -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() \ No newline at end of file diff --git a/pymexc/base_websocket.py b/pymexc/base_websocket.py new file mode 100644 index 0000000..66d7323 --- /dev/null +++ b/pymexc/base_websocket.py @@ -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) diff --git a/pymexc/futures.py b/pymexc/futures.py new file mode 100644 index 0000000..52ff4e8 --- /dev/null +++ b/pymexc/futures.py @@ -0,0 +1,1694 @@ +""" +### Futures API +Documentation: https://mexcdevelop.github.io/apidocs/contract_v1_en/#update-log + +### Usage + +```python +from pymexc import futures + +api_key = "YOUR API KEY" +api_secret = "YOUR API SECRET KEY" + +def handle_message(message): + # handle websocket message + print(message) + +# 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: + ... + +""" +from typing import Callable, Literal, List, Optional, Union +import logging + +logger = logging.getLogger(__name__) + +try: + from base import _FuturesHTTP + from base_websocket import _FuturesWebSocket +except ImportError: + from .base import _FuturesHTTP + from .base_websocket import _FuturesWebSocket + +class HTTP(_FuturesHTTP): + + # <=================================================================> + # + # Market Endpoints + # + # <=================================================================> + + def ping(self) -> dict: + """ + ### Get the server time + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-server-time + """ + return self.call("GET", "api/v1/contract/ping") + + def detail(self, symbol: Optional[str] = None) -> dict: + """ + ### Get the contract information + + Rate limit: 1 times / 5 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-contract-information + + :param symbol: (optional) the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/contract/detail", + params = dict( + symbol = symbol + )) + + def support_currencies(self) -> dict: + """ + ### Get the transferable currencies + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-transferable-currencies + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/contract/support_currencies") + + def get_depth(self, + symbol: str, + limit: Optional[int] = None) -> dict: + """ + ### Get the contract's depth information + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-contract-s-depth-information + + :param symbol: the name of the contract + :type symbol: str + :param limit: (optional) the limit of the depth + :type limit: int + + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/depth/{symbol}", + params = dict( + limit = limit + )) + + def depth_commits(self, + symbol: str, + limit: int) -> dict: + """ + ### Get a snapshot of the latest N depth information of the contract + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-a-snapshot-of-the-latest-n-depth-information-of-the-contract + + :param symbol: the name of the contract + :type symbol: str + :param limit: count + :type limit: int + + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/depth_commits/{symbol}/{limit}") + + def index_price(self, symbol: str) -> dict: + """ + ### Get contract index price + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-index-price + + :param symbol: the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/index_price/{symbol}") + + def fair_price(self, symbol: str) -> dict: + """ + ### Get contract fair price + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-fair-price + + :param symbol: the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/fair_price/{symbol}") + + def funding_rate(self, symbol: str) -> dict: + """ + ### Get contract funding rate + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-funding-rate + + :param symbol: the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/funding_rate/{symbol}") + + def kline(self, + symbol: str, + interval: Optional[Literal["Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1"]] = None, + start: Optional[int] = None, + end: Optional[int] = None) -> dict: + """ + ### K-line data + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#k-line-data + + :param symbol: the name of the contract + :type symbol: str + :param interval: The time interval for the Kline data. Must be one of "Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1". Defaults to "Min1". + :type interval: Optional[Literal["Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1"]] + :param start: (optional) The start time of the Kline data in Unix timestamp format. + :type start: Optional[int] + :param end: (optional) The end time of the Kline data in Unix timestamp format. + :type end: Optional[int] + + :return: A dictionary containing the Kline data for the specified symbol and interval within the specified time range. + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/kline/{symbol}", + params = dict( + symbol = symbol, + interval = interval, + start = start, + end = end + )) + + + def kline_index_price(self, + symbol: str, + interval: Optional[Literal["Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1"]] = "Min1", + start: Optional[int] = None, + end: Optional[int] = None) -> dict: + """ + ### Get K-line data of the index price + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-k-line-data-of-the-index-price + + :param symbol: the name of the contract + :type symbol: str + :param interval: The time interval for the Kline data. Must be one of "Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1". Defaults to "Min1". + :type interval: Optional[Literal["Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1"]] + :param start: (optional) The start time of the Kline data in Unix timestamp format. + :type start: Optional[int] + :param end: (optional) The end time of the Kline data in Unix timestamp format. + :type end: Optional[int] + + :return: A dictionary containing the Kline data for the specified symbol and interval within the specified time range. + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/kline/index_price/{symbol}", + params = dict( + symbol = symbol, + interval = interval, + start = start, + end = end + )) + + def kline_fair_price(self, + symbol: str, + interval: Optional[Literal["Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1"]] = "Min1", + start: Optional[int] = None, + end: Optional[int] = None) -> dict: + """ + ### Get K-line data of the index price + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-k-line-data-of-the-index-price + + :param symbol: the name of the contract + :type symbol: str + :param interval: The time interval for the Kline data. Must be one of "Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1". Defaults to "Min1". + :type interval: Optional[Literal["Min1", "Min5", "Min15", "Min30", "Min60", "Hour4", "Hour8", "Day1", "Week1", "Month1"]] + :param start: (optional) The start time of the Kline data in Unix timestamp format. + :type start: Optional[int] + :param end: (optional) The end time of the Kline data in Unix timestamp format. + :type end: Optional[int] + + :return: A dictionary containing the Kline data for the specified symbol and interval within the specified time range. + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/kline/fair_price/{symbol}", + params = dict( + symbol = symbol, + interval = interval, + start = start, + end = end + )) + + def deals(self, + symbol: str, + limit: Optional[int] = 100) -> dict: + """ + ### Get contract transaction data + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-transaction-data + + :param symbol: the name of the contract + :type symbol: str + :param limit: (optional) consequence set quantity, maximum is 100, default 100 without setting + :type limit: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/contract/deals/{symbol}", + params = dict( + symbol = symbol, + limit = limit + )) + + def ticker(self, symbol: Optional[str] = None) -> dict: + """ + ### Get contract trend data + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-trend-data + + :param symbol: (optional)the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/contract/ticker", + params = dict( + symbol = symbol + )) + + def risk_reverse(self) -> dict: + """ + ### Get all contract risk fund balance + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-all-contract-risk-fund-balance + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/contract/risk_reverse") + + def risk_reverse_history(self, + symbol: str, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get contract risk fund balance history + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-risk-fund-balance-history + + :param symbol: the name of the contract + :type symbol: str + :param page_num: current page number, default is 1 + :type page_num: int + :param page_size: the page size, default 20, maximum 100 + :type page_size: int + + :return: A dictionary containing the risk reverse history. + """ + return self.call("GET", "api/v1/contract/risk_reverse/history", + params = dict( + symbol = symbol, + page_num = page_num, + page_size = page_size + )) + + def funding_rate_history(self, + symbol: str, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get contract funding rate history + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-contract-funding-rate-history + + :param symbol: the name of the contract + :type symbol: str + :param page_num: current page number, default is 1 + :type page_num: int + :param page_size: the page size, default 20, maximum 100 + :type page_size: int + + :return: A dictionary containing the risk reverse history. + """ + return self.call("GET", "api/v1/contract/funding_rate/history", + params = dict( + symbol = symbol, + page_num = page_num, + page_size = page_size + )) + + # <=================================================================> + # + # Account and trading endpoints + # + # <=================================================================> + + def assets(self) -> dict: + """ + ### Get all informations of user's asset + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-all-informations-of-user-39-s-asset + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/account/assets") + + def asset(self, currency: str) -> dict: + """ + ### Get the user's single currency asset information + #### Required permissions: Account reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-user-39-s-single-currency-asset-information + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", f"api/v1/private/account/asset/{currency}") + + def transfer_record(self, + currency: Optional[str] = None, + state: Literal["WAIT", "SUCCESS", "FAILED"] = None, + type: Literal["IN", "OUT"] = None, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get the user's asset transfer records + #### Required permissions: Account reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-user-39-s-asset-transfer-records + + :param currency: (optional) The currency. + :type currency: str + :param state: (optional) state:WAIT 、SUCCESS 、FAILED + :type state: str + :param type: (optional) type:IN 、OUT + :type type: str + :param page_num: (optional) current page number, default is 1 + :type page_num: int + :param page_size: (optional) page size, default 20, maximum 100 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/account/transfer_record", + params = dict( + currency = currency, + state = state, + type = type, + page_num = page_num, + page_size = page_size + )) + + def history_positions(self, + symbol: Optional[str] = None, + type: Optional[int] = None, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get the user's history position information + #### Required permissions: Trade reading permissions + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-user-s-history-position-information + + :param symbol: (optional) the name of the contract + :type symbol: str + :param type: (optional) position type: 1 - long, 2 -short + :type type: int + :param page_num: (optional) current page number , default is 1 + :type page_num: int + :param page_size: (optional) page size , default 20, maximum 100 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/position/list/history_positions", + params = dict( + symbol = symbol, + type = type, + page_num = page_num, + page_size = page_size + )) + + def open_positions(self, symbol: Optional[str] = None) -> dict: + """ + ### Get the user's current holding position + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-user-39-s-current-holding-position + + :param symbol: (optional) the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/position/open_positions", + params = dict( + symbol = symbol + )) + + def funding_records(self, + symbol: Optional[str] = None, + position_id: Optional[int] = None, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get details of user's funding rate + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-details-of-user-s-funding-rate + + :param symbol: the name of the contract + :type symbol: str + :param position_id: position id + :type position_id: int + :param page_num: current page number, default is 1 + :type page_num: int + :param page_size: page size, default 20, maximum 100 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + + return self.call("GET", "api/v1/private/position/funding_records", + params = dict( + symbol = symbol, + position_id = position_id, + page_num = page_num, + page_size = page_size + )) + + def open_orders(self, + symbol: Optional[str] = None, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get the user's current pending order + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-user-39-s-current-pending-order + + :param symbol: The name of the contract. Returns all contract parameters if not specified. + :type symbol: str + :param page_num: The current page number. Defaults to 1. + :type page_num: int + :param page_size: The page size. Defaults to 20. Maximum of 100. + :type page_size: int + + :return: A dictionary containing the user's current pending order. + :rtype: dict + """ + return self.call("GET", f"api/v1/private/order/list/open_orders/{symbol}", + params = dict( + symbol = symbol, + page_num = page_num, + page_size = page_size + )) + + def history_orders(self, + symbol: Optional[str] = None, + states: Optional[str] = None, + category: Optional[str] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + side: Optional[int] = None, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get all of the user's historical orders + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-all-of-the-user-39-s-historical-orders + + :param symbol: The name of the contract. Returns all contract parameters if not specified. + :type symbol: str + :param states: The order state(s) to filter by. Multiple states can be separated by ','. Defaults to None. + :type states: str + :param category: The order category to filter by. Defaults to None. + :type category: int + :param start_time: The start time of the order history to retrieve. Defaults to None. + :type start_time: int + :param end_time: The end time of the order history to retrieve. Defaults to None. + :type end_time: int + :param side: The order direction to filter by. Defaults to None. + :type side: int + :param page_num: The current page number. Defaults to 1. + :type page_num: int + :param page_size: The page size. Defaults to 20. Maximum of 100. + :type page_size: int + + :return: A dictionary containing all of the user's historical orders. + :rtype: dict + """ + return self.call("GET", "api/v1/private/order/history_orders", + params = dict( + symbol = symbol, + states = states, + category = category, + start_time = start_time, + end_time = end_time, + side = side, + page_num = page_num, + page_size = page_size + )) + + def get_order_external(self, symbol: str, external_oid: int) -> dict: + """ + ### Query the order based on the external number + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#query-the-order-based-on-the-external-number + + :param symbol: The name of the contract. + :type symbol: str + :param external_oid: The external order ID. + :type external_oid: str + + :return: A dictionary containing the queried order based on the external number. + :rtype: dict + """ + + return self.call("GET", f"api/v1/private/order/external/{symbol}/{external_oid}") + + def get_order(self, order_id: int) -> dict: + """ + ### Query the order based on the order number + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#query-the-order-based-on-the-order-number + + :param order_id: The ID of the order to query. + :type order_id: int + + :return: A dictionary containing the queried order based on the order number. + :rtype: dict + """ + return self.call("GET", f"api/v1/private/order/{order_id}") + + def batch_query(self, order_ids: List[int]) -> dict: + """ + ### Query the order in bulk based on the order number + #### Required permissions: Trade reading permission + + Rate limit: 5 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#query-the-order-in-bulk-based-on-the-order-number + + :param order_ids: An array of order IDs, separated by ",". Maximum of 50 orders. + :type order_ids: str + + :return: A dictionary containing the queried orders in bulk based on the order number. + :rtype: dict + """ + return self.call("GET", "api/v1/private/order/batch_query", + params = dict( + order_ids = ','.join(order_ids) if isinstance(order_ids, list) else order_ids + )) + + def deal_details(self, order_id: int) -> dict: + """ + ### Get order transaction details based on the order ID + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-order-transaction-details-based-on-the-order-id + + :param order_id: The ID of the order to retrieve transaction details for. + :type order_id: int + + :return: A dictionary containing the transaction details for the given order ID. + :rtype: dict + """ + return self.call("GET", f"api/v1/private/order/deal_details/{order_id}") + + def order_deals(self, + symbol: str, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page_num: Optional[int] = 1, + page_size: Optional[int] = 20) -> dict: + """ + ### Get all transaction details of the user's order + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-all-transaction-details-of-the-user-s-order + + :param symbol: the name of the contact + :type symbol: str + :param start_time: (optional) the starting time, the default is to push forward 7 days, and the maximum span is 90 days + :type start_time: int + :param end_time: (optional) the end time, start and end time span is 90 days + :type end_time: int + :param page_num: current page number, default is 1 + :type page_num: int + :param page_size: page size , default 20, maximum 100 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/order/list/order_deals", + params = dict( + symbol = symbol, + start_time = start_time, + end_time = end_time, + page_num = page_num, + page_size = page_size + )) + + def get_trigger_orders(self, + symbol: Optional[str] = None, + states: Optional[str] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page_num: int = 1, + page_size: int = 20) -> dict: + """ + ### Gets the trigger order list + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#gets-the-trigger-order-list + + :param symbol: (optional) the name of the contract + :type symbol: str + :param states: (optional) order state, 1 uninformed, 2 uncompleted, 3 completed, 4 cancelled, 5 invalid; Multiple separate by ',' + :type states: str + :param start_time: (optional) start time, start time and end time span can only check 90 days at a time, default return the last 7 days of data without fill in + :type start_time: int + :param end_time: (optional) end time, start time, and end time spans can only be checked for 90 days at a time + :type end_time: int + :param page_num: current page number, default is 1 + :type page_num: int + :param page_size: page size, default 20, maximum 100 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/planorder/list/orders", + params = dict( + symbol = symbol, + states = states, + start_time = start_time, + end_time = end_time, + page_num = page_num, + page_size = page_size + )) + + def get_stop_limit_orders(self, + symbol: Optional[str] = None, + is_finished: Optional[int] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page_num: int = 1, + page_size: int = 20) -> dict: + """ + ### Get the Stop-Limit order list + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-the-stop-limit-order-list + + :param symbol: (optional) the name of the contact + :type symbol: str + :param is_finished: (optional) final state indicator :0: uncompleted, 1: completed + :type is_finished: int + :param start_time: (optional) start time, start time and end time span can only check 90 days at a time, default return the last 7 days of data without fill in + :type start_time: long + :param end_time: (optional) end time, start time, and end time spans can only be checked for 90 days at a time + :type end_time: long + :param page_num: current page number, default is 1 + :type page_num: int + :param page_size: page size, default 20, maximum 100 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + + return self.call("GET", "api/v1/private/stoporder/list/orders", + params = dict( + symbol = symbol, + is_finished = is_finished, + start_time = start_time, + end_time = end_time, + page_num = page_num, + page_size = page_size + )) + + def risk_limit(self, symbol: Optional[str] = None) -> dict: + """ + ### Get risk limits + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-risk-limits + + :param symbol: (optional) the name of the contract , not uploaded will return all + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/account/risk_limit", + params = dict( + symbol = symbol + )) + + def tiered_fee_rate(self, symbol: Optional[str] = None) -> dict: + """ + ### Gets the user's current trading fee rate + #### Required permissions: Trade reading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#gets-the-user-39-s-current-trading-fee-rate + + :param symbol: the name of the contract + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("GET", "api/v1/private/account/tiered_fee_rate", + params = dict( + symbol = symbol + )) + + def change_margin(self, + position_id: int, + amount: int, + type: str) -> dict: + """ + ### Increase or decrease margin + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#increase-or-decrease-margin + + :param positionId: position id + :type positionId: int + :param amount: amount + :type amount: float + :param type: type, ADD: increase, SUB: decrease + :type type: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v1/private/position/change_margin", + params = dict( + positionId = position_id, + amount = amount, + type = type + )) + + def get_leverage(self, symbol: str) -> dict: + """ + ### Get leverage + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-leverage + + :param symbol: symbol + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("GET", "api/v1/private/position/leverage", + params = dict( + symbol = symbol + )) + + def change_leverage(self, + position_id: int, + leverage: int, + open_type: Optional[int] = None, + symbol: Optional[str] = None, + position_type: Optional[int] = None) -> dict: + """ + ### Switch leverage + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#switch-leverage + + :param positionId: position id + :type positionId: int + :param leverage: leverage + :type leverage: int + :param openType: (optional) Required when there is no position, openType, 1: isolated position, 2: full position + :type openType: int + :param symbol: (optional) Required when there is no position, symbol + :type symbol: str + :param positionType: (optional) Required when there is no position, positionType: 1 Long 2:short + :type positionType: int + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/position/change_leverage", + params = dict( + positionId = position_id, + leverage = leverage, + openType = open_type, + symbol = symbol, + positionType = position_type + )) + + def get_position_mode(self) -> dict: + """ + ### Get position mode + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#get-position-mode + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v1/private/position/position_mode") + + def change_position_mode(self, position_mode: int) -> dict: + """ + ### Change position mode + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#change-position-mode + + :param positionMode: 1: Hedge, 2: One-way, the modification of the position mode must ensure that there are no active orders, planned orders, or unfinished positions, otherwise it cannot be modified. When switching the one-way mode in both directions, the risk limit level will be reset to level 1. If you need to change the call interface, modify + :type positionMode: int + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v1/private/position/change_position_mode", + params = dict( + positionMode = position_mode + )) + + def order(self, + symbol: str, + price: float, + vol: float, + side: int, + type: int, + open_type: int, + position_id: Optional[int] = None, + leverage: Optional[int] = None, + external_oid: Optional[str] = None, + stop_loss_price: Optional[float] = None, + take_profit_price: Optional[float] = None, + position_mode: Optional[int] = None, + reduce_only: Optional[bool] = False) -> dict: + """ + ### Order (Under maintenance) + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#order-under-maintenance + + :param symbol: the name of the contract + :type symbol: str + :param price: price + :type price: decimal + :param vol: volume + :type vol: decimal + :param leverage: (optional) leverage, Leverage is necessary on Isolated Margin + :type leverage: int + :param side: order direction 1 open long ,2close short,3open short ,4 close l + :type side: int + :param type: orderType,1:price limited order,2:Post Only Maker,3:transact or cancel instantly ,4 : transact completely or cancel completely,5:market orders,6 convert market price to current price + :type type: int + :param openType: open type,1:isolated,2:cross + :type openType: int + :param positionId: (optional) position Id, It is recommended to fill in this parameter when closing a position + :type positionId: long + :param externalOid: (optional) external order ID + :type externalOid: str + :param stopLossPrice: (optional) stop-loss price + :type stopLossPrice: decimal + :param takeProfitPrice: (optional) take-profit price + :type takeProfitPrice: decimal + :param positionMode: (optional) position mode,1:hedge,2:one-way,default: the user's current config + :type positionMode: int + :param reduceOnly: (optional) Default false,For one-way positions, if you need to only reduce positions, pass in true, and two-way positions will not accept this parameter. + :type reduceOnly: bool + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v1/private/order/submit", + params = dict( + symbol = symbol, + price = price, + vol = vol, + side = side, + type = type, + openType = open_type, + positionId = position_id, + leverage = leverage, + externalOid = external_oid, + stopLossPrice = stop_loss_price, + takeProfitPrice = take_profit_price, + positionMode = position_mode, + reduceOnly = reduce_only + )) + + def bulk_order(self, + symbol: str, + price: float, + vol: float, + side: int, + type: int, + open_type: int, + position_id: Optional[int] = None, + external_oid: Optional[str] = None, + stop_loss_price: Optional[float] = None, + take_profit_price: Optional[float] = None) -> dict: + """ + ### Bulk order (Under maintenance) + #### Required permissions: Trading permission + + Rate limit: 1/2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#bulk-order-under-maintenance + + :param symbol: the name of the contract + :type symbol: str + :param price: price + :type price: decimal + :param vol: volume + :type vol: decimal + :param leverage: (optional) leverage, Leverage is necessary on Isolated Margin + :type leverage: int + :param side: order side 1open long,2close short,3open short, 4 close long + :type side: int + :param type: order type :1 price limited order,2:Post Only Maker,3:transact or cancel instantly ,4 : transact completely or cancel completely,5:market orders,6 convert market price to current price + :type type: int + :param openType: open type,1:isolated,2:cross + :type openType: int + :param positionId: (optional) position Id, It is recommended to fill in this parameter when closing a position + :type positionId: int + :param externalOid: (optional) external order ID, return the existing order ID if it already exists + :type externalOid: str + :param stopLossPrice: (optional) stop-loss price + :type stopLossPrice: decimal + :param takeProfitPrice: (optional) take-profit price + :type takeProfitPrice: decimal + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v1/private/order/submit_batch", + params = dict( + symbol = symbol, + price = price, + vol = vol, + side = side, + type = type, + openType = open_type, + positionId = position_id, + externalOid = external_oid, + stopLossPrice = stop_loss_price, + takeProfitPrice = take_profit_price + )) + + def cancel_order(self, order_id: Union[List[int], int]) -> dict: + """ + ### Cancel the order (Under maintenance) + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-the-order-under-maintenance + + :param order_id_list: list of order ids to cancel, maximum 50 + :type order_id_list: List[int] + + :return: dictionary containing the order ID and error message, if any + :rtype: dict + """ + + return self.call("POST", "api/v1/private/order/cancel", + params = dict( + order_ids = ','.join(order_id) if isinstance(order_id, list) else order_id + )) + + def cancel_order_with_external(self, symbol: str, external_oid: str) -> dict: + """ + ### Cancel the order according to the external order ID (Under maintenance) + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-the-order-according-to-the-external-order-id-under-maintenance + + :param symbol: the name of the contract + :type symbol: str + :param external_oid: external order ID of the order to be cancelled + :type external_oid: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/order/cancel_with_external", + params = dict( + symbol = symbol, + externalOid = external_oid + )) + + def cancel_all(self, symbol: Optional[str] = None) -> dict: + """ + ### Cancel all orders under a contract (Under maintenance) + + #### Required permissions: Trading permission + + Rate limit: 20 times / 2 seconds + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-all-orders-under-a-contract-under-maintenance + + :param symbol: (optional) the name of the contract, cancel specific orders placed under this contract when fill the symbol , otherwise , cancel all orders without filling + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/order/cancel_all", + params = dict( + symbol = symbol + )) + + def change_risk_level(self) -> dict: + """ + ### Switch the risk level + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#switch-the-risk-level + + :return: None + :rtype: None + """ + + return self.call("POST", "api/v1/private/account/change_risk_level") + + def trigger_order(self, + symbol: str, + vol: float, + side: int, + open_type: int, + trigger_price: float, + trigger_type: int, + execute_cycle: int, + order_type: int, + trend: int, + price: Optional[float] = None, + leverage: Optional[int] = None) -> dict: + """ + ### Trigger order (Under maintenance) + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#trigger-order-under-maintenance + + :param symbol: the name of the contract + :type symbol: str + :param price: (optional) execute price, market price may not fill in + :type price: int + :param vol: volume + :type vol: int + :param leverage: (optional) leverage , Leverage is necessary on Isolated Margin + :type leverage: int + :param side: 1 for open long, 2 for close short, 3 for open short, and 4 for close long + :type side: int + :param openType: open type, 1: isolated, 2: cross + :type openType: int + :param triggerPrice: trigger price + :type triggerPrice: int + :param triggerType: trigger type, 1: more than or equal, 2: less than or equal + :type triggerType: int + :param executeCycle: execution cycle, 1: 24 hours, 2: 7 days + :type executeCycle: int + :param orderType: order type, 1: limit order, 2: Post Only Maker, 3: close or cancel instantly, 4: close or cancel completely, 5: Market order + :type orderType: int + :param trend: trigger price type, 1: latest price, 2: fair price, 3: index price + :type trend: int + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/planorder/place", + params = dict( + symbol = symbol, + price = price, + vol = vol, + leverage = leverage, + side = side, + openType = open_type, + triggerPrice = trigger_price, + triggerType = trigger_type, + executeCycle = execute_cycle, + orderType = order_type, + trend = trend + )) + + def cancel_trigger_order(self, order_id: int) -> dict: + """ + ### Cancel the trigger order (Under maintenance) + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-the-trigger-order-under-maintenance + + :param orderList: list of orders to be cancelled (maximum of 50) + :type orderList: list[dict] + :param orderList.symbol: the name of the contract + :type orderList.symbol: str + :param orderList.orderId: orderId + :type orderList.orderId: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/planorder/cancel", + params = dict( + order_id = order_id + )) + + def cancel_all_trigger_orders(self, symbol: Optional[str] = None) -> dict: + """ + ### Cancel all trigger orders (Under maintenance) + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-all-trigger-orders-under-maintenance + + :param symbol: (optional) the name of the contract, cancel specific orders placed under this contract when filled, otherwise, cancel all orders without filling + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/planorder/cancel_all", + params = dict( + symbol = symbol + )) + + def cancel_stop_order(self, order_id: int) -> dict: + """ + ### Cancel the Stop-Limit trigger order (Under maintenance) + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-the-stop-limit-trigger-order-under-maintenance + + :param orderList: list of orders to be cancelled (maximum of 50) + :type orderList: list[str] + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/stoporder/cancel", + params = dict( + order_id = order_id + )) + + def cancel_all_stop_order(self, + position_id: Optional[int] = None, + symbol: Optional[str] = None) -> dict: + """ + ### Cancel all Stop-Limit price trigger orders (Under maintenance) + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#cancel-all-stop-limit-price-trigger-orders-under-maintenance + + :param positionId: (optional) position id, fill in positionId to only cancel the trigger order of the corresponding position, otherwise check the symbol without filling + :type positionId: int + :param symbol: (optional) the name of the contract, only cancels the delegate order under this contract based on the symbol, cancel all orders without filling the symbol + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/stoporder/cancel_all", + params = dict( + positionId = position_id, + symbol = symbol + )) + + def stop_limit_change_price(self, + order_id: int, + stop_loss_price: Optional[float] = None, + take_profit_price: Optional[float] = None) -> dict: + """ + ### Switch Stop-Limit limited order price + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#switch-stop-limit-limited-order-price + + :param orderId: the limit order ID + :type orderId: int + :param stopLossPrice: (optional) stop-loss price, if take-profit and stop-loss price are empty or 0 at the same time, it indicates to cancel and take profit + :type stopLossPrice: int + :param takeProfitPrice: (optional) take-profit price, if take-profit and stop-loss price are empty or 0 at the same time, it indicates to cancel stop-loss and take profit + :type takeProfitPrice: int + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/stoporder/change_price", + params = dict( + orderId = order_id, + stopLossPrice = stop_loss_price, + takeProfitPrice = take_profit_price + )) + + def stop_limit_change_plan_price(self, + stop_plan_order_id: int, + stop_loss_price: Optional[float] = None, + take_profit_price: Optional[float] = None) -> dict: + """ + ### Switch the Stop-Limit price of trigger orders + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#switch-the-stop-limit-price-of-trigger-orders + + :param stopPlanOrderId: the Stop-Limit price of trigger order ID + :type stopPlanOrderId: int + :param stopLossPrice: (optional) stop-loss price. At least one stop-loss price and one take-profit price must not be empty and must be more than 0. + :type stopLossPrice: int + :param takeProfitPrice: (optional) take-profit price. At least one take-profit price and stop-loss price must not be empty and must be more than 0. + :type takeProfitPrice: int + + :return: response dictionary + :rtype: dict + """ + + return self.call("POST", "api/v1/private/stoporder/change_plan_price", + params = dict( + stopPlanOrderId = stop_plan_order_id, + stopLossPrice = stop_loss_price, + takeProfitPrice = take_profit_price + )) + +class WebSocket(_FuturesWebSocket): + def __init__(self, + api_key: Optional[str] = None, + api_secret: Optional[str] = None, + ping_interval: Optional[int] = 20, + ping_timeout: Optional[int] = 10, + retries: Optional[int] = 10, + restart_on_error: Optional[bool] = True, + trace_logging: Optional[bool] = False, + http_proxy_host: Optional[str] = None, + http_proxy_port: Optional[int] = None, + http_no_proxy: Optional[list] = None, + http_proxy_auth: Optional[tuple] = None, + http_proxy_timeout: Optional[int] = None): + + kwargs = dict( + api_key = api_key, + api_secret = api_secret, + ping_interval = ping_interval, + ping_timeout = ping_timeout, + retries = retries, + restart_on_error = restart_on_error, + trace_logging = trace_logging, + 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 + ) + + super().__init__(**kwargs) + + + def tickers_stream(self, callback: Callable[..., None]): + """ + ### Tickers + Get the latest transaction price, buy-price, sell-price and 24 transaction volume of all the perpetual contracts on the platform without login. + Send once a second after subscribing. + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + + :return: None + """ + params = {} + topic = "sub.tickers" + self._ws_subscribe(topic, callback, params) + + def ticker_stream(self, callback: Callable[..., None], symbol: str): + """ + ### Ticker + Get the latest transaction price, buy price, sell price and 24 transaction volume of a contract, + send the transaction data without users' login, and send once a second after subscription. + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = dict( + symbol = symbol + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.ticker" + self._ws_subscribe(topic, callback, params) + + def deal_stream(self, callback: Callable[..., None], symbol: str): + """ + ### Transaction + Access to the latest data without login, and keep updating. + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = dict( + symbol = symbol + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.deal" + self._ws_subscribe(topic, callback, params) + + def depth_stream(self, callback: Callable[..., None], symbol: str): + """ + ### Depth + + Tip: [411.8, 10, 1] 411.8 is price, 10 is the order numbers of the contract ,1 is the order quantity + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = dict( + symbol = symbol + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.depth" + self._ws_subscribe(topic, callback, params) + + def depth_full_stream(self, callback: Callable[..., None], symbol: str, limit: int = 20): + """ + ### Depth full + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + :param limit: Limit could be 5, 10 or 20, default 20 without define., only subscribe to the full amount of one gear + :type limit: int + + :return: None + """ + params = dict( + symbol = symbol, + limit = limit + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.depth.full" + self._ws_subscribe(topic, callback, params) + + def kline_stream(self, + callback: Callable[..., None], + symbol: str, + interval: Literal["Min1", "Min5", "Min15", + "Min60", "Hour1", "Hour4", + "Day1", "Week1"] = "Min1"): + """ + ### K-line + Get the k-line data of the contract and keep updating. + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + :param interval: Min1, Min5, Min15, Min30, Min60, Hour4, Hour8, Day1, Week1, Month1 + :type interval: str + + :return: None + """ + params = dict( + symbol = symbol, + interval = interval + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.kline" + self._ws_subscribe(topic, callback, params) + + def funding_rate_stream(self, + callback: Callable[..., None], + symbol: str): + """ + ### Funding rate + Get the contract funding rate, and keep updating. + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = dict( + symbol = symbol + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.funding.rate" + self._ws_subscribe(topic, callback, params) + + def index_price_stream(self, + callback: Callable[..., None], + symbol: str): + """ + ### Index price + Get the index price, and will keep updating if there is any changes. + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = dict( + symbol = symbol + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.index.price" + self._ws_subscribe(topic, callback, params) + + def fair_price_stream(self, + callback: Callable[..., None], + symbol: str): + """ + ### Fair price + + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = dict( + symbol = symbol + ) + + # clear none values + params = {k: v for k, v in params.items() if v is not None} + + topic = "sub.fair.price" + self._ws_subscribe(topic, callback, params) + + # <=================================================================> + # + # PRIVATE + # + # <=================================================================> + + def order_stream(self, callback, params: dict = {}): + """ + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + """ + topic = "sub.personal.order" + self._ws_subscribe(topic, callback, params) + + def asset_stream(self, callback, params: dict = {}): + """ + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + """ + topic = "sub.personal.asset" + self._ws_subscribe(topic, callback, params) + + def position_stream(self, callback, params: dict = {}): + """ + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + """ + topic = "sub.personal.position" + self._ws_subscribe(topic, callback, params) + + def risk_limit_stream(self, callback, params: dict = {}): + """ + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + """ + topic = "sub.personal.risk.limit" + self._ws_subscribe(topic, callback, params) + + def adl_level_stream(self, callback, params: dict = {}): + """ + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + """ + topic = "sub.personal.adl.level" + self._ws_subscribe(topic, callback, params) + + def position_mode_stream(self, callback, params: dict = {}): + """ + https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels + """ + topic = "sub.personal.position.mode" + self._ws_subscribe(topic, callback, params) + + diff --git a/pymexc/spot.py b/pymexc/spot.py new file mode 100644 index 0000000..5e79d90 --- /dev/null +++ b/pymexc/spot.py @@ -0,0 +1,1967 @@ +""" +### Spot API +Documentation: https://mexcdevelop.github.io/apidocs/spot_v3_en/#introduction + +### Usage + +```python +from pymexc import spot + +api_key = "YOUR API KEY" +api_secret = "YOUR API SECRET KEY" + +def handle_message(message): + # handle websocket message + print(message) + +# 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") + +# loop forever for save websocket connection +while True: + ... + +""" +from typing import Callable, Literal, List, Optional, Union +import threading +import time +import logging + +logger = logging.getLogger(__name__) + +try: + from base import _SpotHTTP + from base_websocket import _SpotWebSocket +except ImportError: + from .base import _SpotHTTP + from .base_websocket import _SpotWebSocket + +class HTTP(_SpotHTTP): + # <=================================================================> + # + # Market Data Endpoints + # + # <=================================================================> + + def ping(self) -> dict: + """ + ### Test connectivity to the Rest API. + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#test-connectivity + """ + return self.call("GET", "/api/v3/ping", auth = False) + + def time(self) -> dict: + """ + ### Check Server Time + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#check-server-time + """ + return self.call("GET", "/api/v3/time", auth = False) + + def default_symbols(self) -> dict: + """ + ### API default symbol + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#api-default-symbol + """ + return self.call("GET", "/api/v3/defaultSymbols", auth = False) + + def exchange_info(self, + symbol: Optional[str] = None, + symbols: Optional[List[str]] = None) -> dict: + """ + ### Exchange Information + + Current exchange trading rules and symbol information + + Weight(IP): 10 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#exchange-information + + :param symbol: (optional) The symbol for a specific trading pair. + :type symbol: str + :param symbols: (optional) List of symbols to get information for. + :type symbols: List[str] + :return: The response from the API containing trading pair information. + :rtype: dict + """ + + return self.call("GET", "/api/v3/exchangeInfo", + params = dict( + symbol = symbol, + symbols = ','.join(symbols) if symbols else None + ), + auth = False) + + def order_book(self, + symbol: str, + limit: Optional[int] = 100) -> dict: + """ + ### Order Book + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#order-book + + :param symbol: A string representing the trading pair symbol, e.g. "BTCUSDT". + :type symbol: str + :param limit: An integer representing the number of order book levels to retrieve. Defaults to 100. Max is 5000. + :type limit: int + + :return: The order book data in JSON format. + :rtype: dict + """ + return self.call("GET", "/api/v3/depth", + params = dict( + symbol = symbol, + limit = limit + ), + auth = False) + + def trades(self, + symbol: str, + limit: Optional[int] = 500) -> dict: + """ + ### Recent Trades List + + Weight(IP): 5 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#recent-trades-list + + :param symbol: A string representing the trading pair symbol. + :type symbol: str + :param limit: An optional integer representing the maximum number of trades to retrieve. Defaults to 500. Max is 5000. + :type limit: int + + :return: A dictionary containing information about the trades. + :rtype: dict + """ + + return self.call("GET", "/api/v3/trades", + params = dict( + symbol = symbol, + limit = limit + ), + auth = False) + + def agg_trades(self, + symbol: str, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500) -> dict: + """ + ### Compressed/Aggregate Trades List + + Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same price will have the quantity aggregated. + + startTime and endTime must be used at the same time. + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#compressed-aggregate-trades-list + + :param symbol: The symbol to retrieve trades for. + :type symbol: str + :param start_time: (optional) Timestamp in ms to get aggregate trades from INCLUSIVE. + :type start_time: int + :param end_time: (optional) Timestamp in ms to get aggregate trades until INCLUSIVE. + :type end_time: int + :param limit: (optional) The maximum number of trades to retrieve. Default is 500. Max is 5000. + :type limit: int + + :return: A dictionary containing the retrieved trades. + :rtype: dict + """ + return self.call("GET", "/api/v3/aggTrades", + params = dict( + symbol = symbol, + startTime = start_time, + endTime = end_time, + limit = limit + ), + auth = False) + + def klines(self, + symbol: str, + interval: Literal["1m", "5m", "15m", + "30m", "60m", "4h", + "1d", "1M"] = "1m", + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500) -> dict: + """ + ### Kline/Candlestick Data + + Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#kline-candlestick-data + + :param symbol: The symbol to retrieve trades for. + :type symbol: str + :param interval: The interval for the kline. + :type interval: ENUM_Kline + :param start_time: (optional) Timestamp in ms to get aggregate trades from INCLUSIVE. + :type start_time: int + :param end_time: (optional) Timestamp in ms to get aggregate trades until INCLUSIVE. + :type end_time: int + :param limit: (optional) The maximum number of trades to retrieve. Default is 500. Max is 5000. + :type limit: int + + :return: A dictionary containing the klines. + :rtype: dict + """ + return self.call("GET", "/api/v3/klines", + params = dict( + symbol = symbol, + interval = interval, + startTime = start_time, + endTime = end_time, + limit = limit + ), + auth = False) + + def avg_price(self, symbol: str): + """ + ### Current Average Price + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#current-average-price + + :param symbol: The symbol. + :type symbol: str + + :return: A dictionary containing average price. + :rtype: dict + """ + return self.call("GET", "/api/v3/avgPrice", + params = dict( + symbol = symbol + ), auth = False) + + def ticker_24h(self, symbol: Optional[str] = None): + """ + ### 24hr Ticker Price Change Statistics + + Weight(IP): 1 - 1 symbol; + Weight(IP): 40 - all symbols; + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#24hr-ticker-price-change-statistics + + :param symbol: (optional) If the symbol is not sent, tickers for all symbols will be returned in an array. + :type symbol: str + + :return: A dictionary. + :rtype: dict + """ + return self.call("GET", "/api/v3/ticker/24hr", + params = dict( + symbol = symbol + ), + auth = False) + + def ticker_price(self, symbol: Optional[str] = None): + """ + ### Symbol Price Ticker + + Weight(IP): 1 - 1 symbol; + Weight(IP): 2 - all symbols; + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#symbol-price-ticker + + :param symbol: (optional) If the symbol is not sent, all symbols will be returned in an array. + :type symbol: str + + :return: A dictionary. + :rtype: dict + """ + return self.call("GET", "/api/v3/ticker/price", + params = dict( + symbol = symbol + ), + auth = False) + + def ticker_book_price(self, symbol: Optional[str] = None): + """ + ### Symbol Price Ticker + + Best price/qty on the order book for a symbol or symbols. + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#symbol-order-book-ticker + + :param symbol: (optional) If the symbol is not sent, all symbols will be returned in an array. + :type symbol: str + + :return: A dictionary. + :rtype: dict + """ + return self.call("GET", "/api/v3/ticker/bookTicker", + params = dict( + symbol = symbol + ), + auth = False) + + # <=================================================================> + # + # Sub-Account Endpoints + # + # <=================================================================> + + def create_sub_account(self, sub_account: str, note: str) -> dict: + """ + ### Create a sub-account from the master account. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#create-a-sub-account-for-master-account + + :param sub_account: Sub-account Name + :type sub_account: str + :param note: Sub-account notes + :type note: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/sub-account/virtualSubAccount", + params = dict( + subAccount = sub_account, + note = note + )) + + + def sub_account_list(self, + sub_account: Optional[str] = None, + is_freeze: Optional[bool] = None, + page: Optional[int] = 1, + limit: Optional[int] = 10) -> dict: + """ + ### Get details of the sub-account list. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-sub-account-list-for-master-account + + :param sub_account: (optional) Sub-account Name + :type sub_account: str + :param is_freeze: (optional) true or false + :type is_freeze: bool + :param page: (optional) Default value: 1 + :type page: int + :param limit: (optional) Default value: 10, Max value: 200 + :type limit: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/sub-account/list", + params = dict( + subAccount = sub_account, + isFreeze = is_freeze, + page = page, + limit = limit + )) + + def create_sub_account_api_key(self, + sub_account: str, + note: str, + permissions: Union[str, List[Literal["SPOT_ACCOUNT_READ", "SPOT_ACCOUNT_WRITE", + "SPOT_DEAL_READ", "SPOT_DEAL_WRITE", + "CONTRACT_ACCOUNT_READ", "CONTRACT_ACCOUNT_WRITE", + "CONTRACT_DEAL_READ", "CONTRACT_DEAL_WRITE", + "SPOT_TRANSFER_READ", "SPOT_TRANSFER_WRITE"]]], + ip: Optional[str] = None) -> dict: + """ + ### Create an APIKey for a sub-account. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#create-an-apikey-for-a-sub-account-for-master-account + + :param sub_account: Sub-account Name + :type sub_account: str + :param note: APIKey note + :type note: str + :param permissions: Permission of APIKey: SPOT_ACCOUNT_READ, SPOT_ACCOUNT_WRITE, SPOT_DEAL_READ, SPOT_DEAL_WRITE, CONTRACT_ACCOUNT_READ, CONTRACT_ACCOUNT_WRITE, CONTRACT_DEAL_READ, CONTRACT_DEAL_WRITE, SPOT_TRANSFER_READ, SPOT_TRANSFER_WRITE + :type permissions: list + :param ip: (optional) Link IP addresses, separate with commas if more than one. Support up to 20 addresses. + :type ip: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/sub-account/apiKey", + params = dict( + subAccount = sub_account, + note = note, + permissions = ','.join(permissions) if isinstance(permissions, list) else permissions, + ip = ip + )) + + def query_sub_account_api_key(self, sub_account: str) -> dict: + """ + ### Query the APIKey of a sub-account. + #### Applies to master accounts only + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-the-apikey-of-a-sub-account-for-master-account + + :param sub_account: Sub-account Name + :type sub_account: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/sub-account/apiKey", + params = dict( + subAccount = sub_account + )) + + def delete_sub_account_api_key(self, sub_account: str, api_key: str) -> dict: + """ + ### Delete the APIKey of a sub-account. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#delete-the-apikey-of-a-sub-account-for-master-account + + :param sub_account: Sub-account Name + :type sub_account: str + :param api_key: API public key + :type api_key: str + + :return: response dictionary + :rtype: dict + """ + return self.call("DELETE", "api/v3/sub-account/apiKey", + params = dict( + subAccount = sub_account, + apiKey = api_key + )) + + def universal_transfer(self, + from_account_type: Literal["SPOT", "FUTURES"], + to_account_type: Literal["SPOT", "FUTURES"], + asset: str, + amount: float, + from_account: Optional[str] = None, + to_account: Optional[str] = None) -> dict: + """ + ### Universal Transfer (For Master Account) + #### Required permission: SPOT_TRANSFER_WRITE + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-universal-transfer-history-for-master-account + + :param from_account: (optional) Transfer from master account by default if fromAccount is not sent + :type from_account: str + :param to_account: (optional) Transfer to master account by default if toAccount is not sent + :type to_account: str + :param from_account_type: fromAccountType:"SPOT","FUTURES" + :type from_account_type: str + :param to_account_type: toAccountType:"SPOT","FUTURES" + :type to_account_type: str + :param asset: asset,eg:USDT + :type asset: str + :param amount: amount,eg:1.82938475 + :type amount: float + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/capital/sub-account/universalTransfer", + params = dict( + fromAccount = from_account, + toAccount = to_account, + fromAccountType = from_account_type, + toAccountType = to_account_type, + asset = asset, + amount = amount + )) + + def query_universal_transfer_history(self, + from_account_type: Literal["SPOT", "FUTURES"], + to_account_type: Literal["SPOT", "FUTURES"], + from_account: Optional[str] = None, + to_account: Optional[str] = None, + start_time: Optional[str] = None, + end_time: Optional[str] = None, + page: Optional[int] = 1, + limit: Optional[int] = 500) -> dict: + """ + ### Query Universal Transfer History. + #### Required permission: SPOT_TRANSFER_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-universal-transfer-history-for-master-account + + :param from_account: (optional) Transfer from master account by default if fromAccount is not sent + :type from_account: str + :param to_account: (optional) Transfer to master account by default if toAccount is not sent + :type to_account: str + :param from_account_type: fromAccountType:"SPOT","FUTURES" + :type from_account_type: str + :param to_account_type: toAccountType:"SPOT","FUTURES" + :type to_account_type: str + :param start_time: (optional) startTime + :type start_time: str + :param end_time: (optional) endTime + :type end_time: str + :param page: (optional) default 1 + :type page: int + :param limit: (optional) default 500, max 500 + :type limit: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/sub-account/universalTransfer", + params = dict( + fromAccount = from_account, + toAccount = to_account, + fromAccountType = from_account_type, + toAccountType = to_account_type, + startTime = start_time, + endTime = end_time, + page = page, + limit = limit + )) + + # <=================================================================> + # + # Spot Account/Trade + # + # <=================================================================> + + def get_default_symbols(self) -> dict: + """ + ### User API default symbol. + #### Required permission: SPOT_ACCOUNT_R + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#user-api-default-symbol + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/selfSymbols") + + def test_new_order(self, + symbol: str, + side: str, + order_type: str, + quantity: Optional[int] = None, + quote_order_qty: Optional[int] = None, + price: Optional[int] = None, + new_client_order_id: Optional[str] = None, + stop_price: Optional[int] = None, + iceberg_qty: Optional[int] = None, + time_in_force: Optional[str] = None) -> dict: + """ + ### New Order. + #### Required permission: SPOT_DEAL_WRITE + + Weight(IP): 1, Weight(UID): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#new-order + + :param symbol: + :type symbol: str + :param side: ENUM:Order Side + :type side: str + :param order_type: ENUM:Order Type + :type order_type: str + :param quantity: (optional) Quantity + :type quantity: int + :param quote_order_qty: (optional) Quote order quantity + :type quote_order_qty: int + :param price: (optional) Price + :type price: int + :param new_client_order_id: (optional) Unique order id + :type new_client_order_id: str + :param stop_price: (optional) Stop price + :type stop_price: int + :param iceberg_qty: (optional) Iceberg quantity + :type iceberg_qty: int + :param time_in_force: (optional) ENUM:Time In Force + :type time_in_force: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "/api/v3/order/test", + params = dict( + symbol = symbol, + side = side, + type = order_type, + quantity = quantity, + quoteOrderQty = quote_order_qty, + price = price, + newClientOrderId = new_client_order_id, + stopPrice = stop_price, + icebergQty = iceberg_qty, + timeInForce = time_in_force + )) + + def new_order(self, + symbol: str, + side: str, + order_type: str, + quantity: Optional[int] = None, + quote_order_qty: Optional[int] = None, + price: Optional[int] = None, + new_client_order_id: Optional[str] = None, + stop_price: Optional[int] = None, + iceberg_qty: Optional[int] = None, + time_in_force: Optional[str] = None) -> dict: + """ + ### New Order. + #### Required permission: SPOT_DEAL_WRITE + + Weight(IP): 1, Weight(UID): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#new-order + + :param symbol: + :type symbol: str + :param side: ENUM:Order Side + :type side: str + :param order_type: ENUM:Order Type + :type order_type: str + :param quantity: (optional) Quantity + :type quantity: int + :param quote_order_qty: (optional) Quote order quantity + :type quote_order_qty: int + :param price: (optional) Price + :type price: int + :param new_client_order_id: (optional) Unique order id + :type new_client_order_id: str + :param stop_price: (optional) Stop price + :type stop_price: int + :param iceberg_qty: (optional) Iceberg quantity + :type iceberg_qty: int + :param time_in_force: (optional) ENUM:Time In Force + :type time_in_force: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/order", + params = dict( + symbol = symbol, + side = side, + type = order_type, + quantity = quantity, + quoteOrderQty = quote_order_qty, + price = price, + newClientOrderId = new_client_order_id, + stopPrice = stop_price, + icebergQty = iceberg_qty, + timeInForce = time_in_force + )) + + def batch_orders(self, + batch_orders: List[str], + symbol: str, + side: Literal["BUY", "SELL"], + order_type: Literal["LIMIT", "MARKET", "LIMIT_MARKET", "IMMEDIATE_OR_CANCEL", "FILL_OR_KILL"], + quantity: Optional[int] = None, + quote_order_qty: Optional[int] = None, + price: Optional[int] = None, + new_client_order_id: Optional[str] = None) -> dict: + + """ + ### Batch Orders + #### Supports 20 orders with a same symbol in a batch,rate limit:2 times/s. + #### Required permission: SPOT_DEAL_WRITE + + Weight(IP): 1,Weight(UID): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#batch-orders + + + :param batch_orders: list of batchOrders,supports max 20 orders + :type batch_orders: List[dict] + :param symbol: symbol + :type symbol: str + :param side: order side + :type side: str + :param order_type: order type + :type order_type: str + :param quantity: (optional) quantity + :type quantity: int + :param quote_order_qty: (optional) quoteOrderQty + :type quote_order_qty: int + :param price: (optional) order price + :type price: int + :param new_client_order_id: (optional) ClientOrderId + :type new_client_order_id: str + + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/batchOrders", + params = dict( + batchOrders = batch_orders, + symbol = symbol, + side = side, + type = order_type, + quantity = quantity, + quoteOrderQty = quote_order_qty, + price = price, + newClientOrderId = new_client_order_id + )) + + def cancel_order(self, + symbol: str, + order_id: Optional[str] = None, + orig_client_order_id: Optional[str] = None, + new_client_order_id: Optional[str] = None) -> dict: + """ + ### Cancel Order. + #### Required permission: SPOT_DEAL_WRITE + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#cancel-order + + :param symbol: + :type symbol: str + :param order_id: (optional) Order id + :type order_id: str + :param orig_client_order_id: (optional) + :type orig_client_order_id: str + :param new_client_order_id: (optional) Unique order id + :type new_client_order_id: str + + :return: response dictionary + :rtype: dict + """ + return self.call("DELETE", "api/v3/order", + params = dict( + symbol = symbol, + orderId = order_id, + origClientOrderId = orig_client_order_id, + newClientOrderId = new_client_order_id + )) + + def cancel_all_open_orders(self, symbol: str) -> dict: + + """ + ### Cancel all Open Orders on a Symbol. + #### Required permission: SPOT_DEAL_WRITE + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#cancel-all-open-orders-on-a-symbol + + :param symbol: maximum input 5 symbols,separated by ",". e.g. "BTCUSDT,MXUSDT,ADAUSDT" + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("DELETE", "api/v3/openOrders", + params = dict( + symbol = symbol + )) + + def query_order(self, + symbol: str, + orig_client_order_id: Optional[str] = None, + order_id: Optional[str] = None) -> dict: + + """ + ### Query Order. + #### Required permission: SPOT_DEAL_READ + Check an order's status. + + Weight(IP): 2 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-order + + :param symbol: + :type symbol: str + :param orig_client_order_id: (optional) Unique order id + :type orig_client_order_id: str + :param order_id: (optional) Order id + :type order_id: str + :param recv_window: (optional) Request timeout + :type recv_window: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/order", + params = dict( + symbol = symbol, + origClientOrderId = orig_client_order_id, + orderId = order_id + )) + + def current_open_orders(self, symbol: str) -> dict: + + """ + ### Current Open Orders. + #### Required permission: SPOT_DEAL_READ + + Weight(IP): 3 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#current-open-orders + + :param symbol: + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/openOrders", params = dict(symbol = symbol)) + + def all_orders(self, + symbol: str, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = None) -> dict: + + """ + ### All Orders. + #### Required permission: SPOT_DEAL_READ + + Weight(IP): 10 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#all-orders + + Get all account orders including active, cancelled or completed orders(the query period is the latest 24 hours by default). You can query a maximum of the latest 7 days. + + :param symbol: Symbol + :type symbol: str + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param limit: (optional) Default 500; max 1000; + :type limit: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/allOrders", + params=dict( + symbol=symbol, + startTime=start_time, + endTime=end_time, + limit=limit + )) + + def account_information(self) -> dict: + + """ + ### Account Information. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 10 + + Get current account information,rate limit:2 times/s. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#account-information + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/account") + + def account_trade_list(self, + symbol: str, + order_id: Optional[str] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = None) -> dict: + + """ + ### Account Trade List. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 10 + + Get trades for a specific account and symbol, + Only the transaction records in the past 1 month can be queried. + If you want to view more transaction records, please use the export function on the web side, + which supports exporting transaction records of the past 3 years at most. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#account-trade-list + + :param symbol: + :type symbol: str + :param order_id: (optional) order Id + :type order_id: str + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param limit: (optional) Default 500; max 1000; + :type limit: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/myTrades", + params = dict( + symbol = symbol, + orderId = order_id, + startTime = start_time, + endTime = end_time, + limit = limit + )) + + def enable_mx_deduct(self, mx_deduct_enable: bool) -> dict: + """ + ### Enable MX Deduct. + #### Required permission: SPOT_DEAL_WRITE + Enable or disable MX deduct for spot commission fee + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#enable-mx-deduct + + :param mx_deduct_enable: true:enable,false:disable + :type mx_deduct_enable: bool + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/mxDeduct/enable", + params = dict( + mxDeductEnable = mx_deduct_enable + )) + + def query_mx_deduct_status(self) -> dict: + """ + ### Query MX Deduct Status. + #### Required permission: SPOT_DEAL_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-mx-deduct-status + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/mxDeduct/enable") + + + # <=================================================================> + # + # Wallet Endpoints + # + # <=================================================================> + + def get_currency_info(self) -> dict: + """ + ### Query the currency information. + #### Required permission: SPOT_WITHDRAW_READ + + Weight(IP): 10 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-the-currency-information + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/config/getall") + + def withdraw(self, + coin: str, + address: str, + amount: int, + withdraw_order_id: Optional[str] = None, + network: Optional[str] = None, + memo: Optional[str] = None, + remark: Optional[str] = None) -> dict: + """ + ### Withdraw. + #### Required permission: SPOT_WITHDRAW_WRITE + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#withdraw + + :param coin: coin + :type coin: str + :param withdraw_order_id: (optional) withdrawOrderId + :type withdraw_order_id: str + :param network: (optional) withdraw network + :type network: str + :param address: withdraw address + :type address: str + :param memo: (optional) memo(If memo is required in the address, it must be passed in) + :type memo: str + :param amount: withdraw amount + :type amount: int + :param remark: (optional) remark + :type remark: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/capital/withdraw/apply", + params = dict( + coin = coin, + withdrawOrderId = withdraw_order_id, + network = network, + address = address, + memo = memo, + amount = amount, + remark = remark + )) + + def cancel_withdraw(self, id: str) -> dict: + """ + ### Cancel withdraw. + #### Required permission: SPOT_WITHDRAW_W + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#cancel-withdraw + + :param id: withdraw id + :type id: str + + :return: response dictionary + :rtype: dict + """ + return self.call("DELETE", "api/v3/capital/withdraw", params=dict(id=id)) + + def deposit_history(self, + coin: Optional[str] = None, + status: Optional[str] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = None) -> dict: + + """ + ### Deposit History(supporting network). + #### Required permission: SPOT_WITHDRAW_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#deposit-history-supporting-network + + Ensure that the default timestamp of 'startTime' and 'endTime' does not exceed 90 days. + + :param coin: (optional) coin + :type coin: str + :param status: (optional) status + :type status: str + :param start_time: (optional) default: 90 days ago from current time + :type start_time: int + :param end_time: (optional) default:current time + :type end_time: int + :param limit: (optional) default:1000,max:1000 + :type limit: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/deposit/hisrec", + params = dict( + coin = coin, + status = status, + startTime = start_time, + endTime = end_time, + limit = limit + )) + + def withdraw_history(self, + coin: Optional[str] = None, + status: Optional[str] = None, + limit: Optional[int] = None, + start_time: Optional[str] = None, + end_time: Optional[str] = None) -> dict: + """ + ### Withdraw History (supporting network). + #### Required permission: SPOT_WITHDRAW_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#withdraw-history-supporting-network + + :param coin: (optional) coin + :type coin: str + :param status: (optional) withdraw status + :type status: str + :param limit: (optional) default:1000, max:1000 + :type limit: int + :param start_time: (optional) default: 90 days ago from current time + :type start_time: str + :param end_time: (optional) default:current time + :type end_time: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/withdraw/history", + params = dict( + coin = coin, + status = status, + limit = limit, + startTime = start_time, + endTime = end_time + )) + + def generate_deposit_address(self, + coin: str, + network: str) -> dict: + """ + ### Generate deposit address (supporting network). + #### Required permission: SPOT_WITHDRAW_WRITE + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#generate-deposit-address-supporting-network + + :param coin: coin + :type coin: str + :param network: deposit network + :type network: str + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/capital/deposit/address", + params = dict( + coin = coin, + network = network + )) + + def deposit_address(self, + coin: str, + network: Optional[str] = None) -> dict: + """ + ### Deposit Address (supporting network). + #### Required permission: SPOT_WITHDRAW_READ + + Weight(IP): 10 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#deposit-address-supporting-network + + :param coin: coin + :type coin: str + :param network: (optional) deposit network + :type network: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/deposit/address", + params = dict( + coin = coin, + network = network, + )) + + def withdraw_address(self, + coin: Optional[str] = None, + page: Optional[int] = None, + limit: Optional[int] = None) -> dict: + """ + ### Withdraw Address (supporting network). + #### Required permission: SPOT_WITHDRAW_R + + Weight(IP): 10 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#withdraw-address-supporting-network + + + :param coin: (optional) coin + :type coin: str + :param page: (optional) page,default 1 + :type page: int + :param limit: (optional) limit for per page,default 20 + :type limit: int + + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/withdraw/address", + params = dict( + coin = coin, + page = page, + limit = limit + )) + + def user_universal_transfer(self, + from_account_type: str, + to_account_type: str, + asset: str, + amount: int) -> dict: + """ + ### User Universal Transfer. + #### Required permission: SPOT_TRANSFER_WRITE + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#user-universal-transfer + + :param from_account_type: fromAccountType:"SPOT","FUTURES" + :type from_account_type: str + :param to_account_type: toAccountType:"SPOT","FUTURES" + :type to_account_type: str + :param asset: asset + :type asset: str + :param amount: amount + :type amount: int + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/capital/transfer", + params = dict( + fromAccountType = from_account_type, + toAccountType = to_account_type, + asset = asset, + amount = amount + )) + + def user_universal_transfer_history(self, + from_account_type: Literal["SPOT", "FUTURES"], + to_account_type: Literal["SPOT", "FUTURES"], + start_time: Optional[str] = None, + end_time: Optional[str] = None, + page: Optional[int] = 1, + size: Optional[int] = 10) -> dict: + """ + ### Query User Universal Transfer History. + #### Required permission: SPOT_TRANSFER_READ + Only can quary the data for the last six months + If 'startTime' and 'endTime' are not send, will return the last seven days' data by default + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-user-universal-transfer-history + + :param from_account_type: fromAccountType:"SPOT","FUTURES" + :type from_account_type: str + :param to_account_type: toAccountType:"SPOT","FUTURES" + :type to_account_type: str + :param start_time: (optional) startTime + :type start_time: str + :param end_time: (optional) endTime + :type end_time: str + :param page: (optional) default:1 + :type page: int + :param size: (optional) default:10, max:100 + :type size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/transfer", + params = dict( + fromAccountType = from_account_type, + toAccountType = to_account_type, + startTime = start_time, + endTime = end_time, + page = page, + size = size + )) + + def user_universal_transfer_history_by_tranid(self, tran_id: str) -> dict: + """ + ### Query User Universal Transfer History (by tranId). + #### Required permission: SPOT_TRANSFER_R + Only can quary the data for the last six months + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-user-universal-transfer-history-by-tranid + + :param tran_id: tranId + :type tran_id: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/transfer/tranId", params=dict(tranId=tran_id)) + + def get_assets_convert_into_mx(self) -> dict: + """ + ### Get Assets That Can Be Converted Into MX. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-assets-that-can-be-converted-into-mx + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/convert/list") + + def dust_transfer(self, asset: Union[str, List[str]]) -> dict: + """ + ### Dust Transfer. + #### Required permission: SPOT_ACCOUNT_W + + Weight(IP): 10 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#dust-transfer + + :param asset: The asset being converted.(max 15 assert)eg:asset=BTC,FIL,ETH + :type asset: Union[str, List[str]] + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/capital/convert", + params = dict( + asset = ','.join(asset) if isinstance(asset, list) else asset + )) + + def dustlog(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page: Optional[int] = None, + limit: Optional[int] = None) -> dict: + """ + ### DustLog. + #### Required permission: SPOT_DEAL_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#dustlog + + + :param start_time: (optional) startTime + :type start_time: int + :param end_time: (optional) endTime + :type end_time: int + :param page: (optional) page,default 1 + :type page: int + :param limit: (optional) limit,default 1; max 1000 + :type limit: int + + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/capital/convert", + params = dict( + startTime = start_time, + endTime = end_time, + page = page, + limit = limit + )) + + # <=================================================================> + # + # ETF + # + # <=================================================================> + + def get_etf_info(self, symbol: Optional[str] = None) -> dict: + """ + ### Get ETF info. + #### Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-etf-info + + :param symbol: (optional) ETF symbol + :type symbol: str + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/etf/info", + params = dict( + symbol = symbol + )) + + # <=================================================================> + # + # Websocket Market Streams + # + # <=================================================================> + + # realized in spot.WebSocket class + + # <=================================================================> + # + # Websocket User Data Streams + # + # <=================================================================> + + def create_listen_key(self) -> dict: + """ + ### Create a ListenKey. + #### Required permission: SPOT_ACCOUNT_R + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#listen-key + + Start a new user data stream. The stream will close after 60 minutes unless a keepalive is sent. + + :return: response dictionary + :rtype: dict + """ + return self.call("POST", "api/v3/userDataStream", params = {"please_sign_it": None}) + + def keep_alive_listen_key(self, listen_key: str) -> dict: + """ + ### Keep-alive a ListenKey. + #### Required permission: none + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#listen-key + + :param listen_key: Listen key + :type listen_key: str + + :return: response dictionary + :rtype: dict + """ + return self.call("PUT", "api/v3/userDataStream", params=dict(listenKey=listen_key)) + + def close_listen_key(self) -> dict: + """ + ### Close a ListenKey. + #### Required permission: None + + Weight(IP): 1, Weight(UID): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#listen-key + + :return: response dictionary + :rtype: dict + """ + return self.call("DELETE", "api/v3/userDataStream", params = {"please_sign_it": None}) + + # <=================================================================> + # + # Rabate Endpoints + # + # <=================================================================> + def get_rebate_history_records(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page: Optional[int] = None) -> dict: + """ + ### Get Rebate History Records. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#rebate-endpoints + + + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param page: (optional) default 1 + :type page: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/rebate/taxQuery", + params = dict( + startTime = start_time, + endTime = end_time, + page = page + )) + + def get_rebate_records_detail(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page: Optional[int] = None) -> dict: + + """ + ### Get Rebate Records Detail. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-rebate-records-detail + + + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param page: (optional) default 1 + :type page: int + + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/rebate/detail", + params = dict( + startTime = start_time, + endTime = end_time, + page = page + )) + + def get_self_rebate_records_detail(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + page: Optional[int] = None) -> dict: + """ + ### Get Self Rebate Records Detail. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-self-rebate-records-detail + + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param page: (optional) default 1 + :type page: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/rebate/detail/kickback", + params = dict( + startTime = start_time, + endTime = end_time, + page = page + )) + + def query_refercode(self) -> dict: + """ + ### Query ReferCode. + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#query-refercode + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "api/v3/rebate/referCode", params=dict(please_sign_me = None)) + + def affiliate_commission_record(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + invite_code: Optional[int] = None, + page: Optional[int] = None, + page_size: Optional[int] = None) -> dict: + """ + ### Get Affiliate Commission Record (affiliate only) + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-affiliate-commission-record-affiliate-only + + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param invite_code: (optional) + :type invite_code: int + :param page: (optional) default 1 + :type page: int + :param page_size: (optional) default 10 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "/api/v3/rebate/affiliate/commission", + params = dict( + startTime = start_time, + endTime = end_time, + inviteCode = invite_code, + page = page, + pageSize = page_size + )) + + def affiliate_withdraw_record(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + invite_code: Optional[int] = None, + page: Optional[int] = None, + page_size: Optional[int] = None) -> dict: + """ + ### Get Affiliate Withdraw Record (affiliate only) + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-affiliate-withdraw-record-affiliate-only + + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param invite_code: (optional) + :type invite_code: int + :param page: (optional) default 1 + :type page: int + :param page_size: (optional) default 10 + :type page_size: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "/api/v3/rebate/affiliate/withdraw", + params = dict( + startTime = start_time, + endTime = end_time, + inviteCode = invite_code, + page = page, + pageSize = page_size + )) + + def affiliate_commission_detail_record(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + invite_code: Optional[int] = None, + page: Optional[int] = None, + page_size: Optional[int] = None, + type: Optional[Literal[1, 2, 3]] = None) -> dict: + """ + ### Get Affiliate Withdraw Record (affiliate only) + #### Required permission: SPOT_ACCOUNT_READ + + Weight(IP): 1 + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#get-affiliate-withdraw-record-affiliate-only + + :param start_time: (optional) + :type start_time: int + :param end_time: (optional) + :type end_time: int + :param invite_code: (optional) + :type invite_code: int + :param page: (optional) default 1 + :type page: int + :param page_size: (optional) default 10 + :type page_size: int + :param type: (optional) commission type, 1:spot, 2:futures, 3:ETF + :type type: int + + :return: response dictionary + :rtype: dict + """ + return self.call("GET", "/api/v3/rebate/affiliate/commission/detail", + params = dict( + startTime = start_time, + endTime = end_time, + inviteCode = invite_code, + page = page, + pageSize = page_size, + type = type + )) + +class WebSocket(_SpotWebSocket): + def __init__(self, + api_key: Optional[str] = None, + api_secret: Optional[str] = None, + listenKey: Optional[str] = None, + ping_interval: Optional[int] = 20, + ping_timeout: Optional[int] = 10, + retries: Optional[int] = 10, + restart_on_error: Optional[bool] = True, + trace_logging: Optional[bool] = False, + http_proxy_host: Optional[str] = None, + http_proxy_port: Optional[int] = None, + http_no_proxy: Optional[list] = None, + http_proxy_auth: Optional[tuple] = None, + http_proxy_timeout: Optional[int] = None): + """ + Initializes the class instance with the provided arguments. + + :param api_key: API key for authentication. (Optional) + :type api_key: str + + :param api_secret: API secret for authentication. (Optional) + :type api_secret: str + + :param listenKey: The listen key for the connection to private channels. + If not provided, a listen key will be generated from HTTP api [Permission: SPOT_ACCOUNT_R] (Optional) + :type listenKey: str + + :param ping_interval: The interval in seconds to send a ping request. (Optional) + :type ping_interval: int + + :param ping_timeout: The timeout in seconds for a ping request. (Optional) + :type ping_timeout: int + + :param retries: The number of times to retry a request. (Optional) + :type retries: int + + :param restart_on_error: Whether or not to restart the connection on error. (Optional) + :type restart_on_error: bool + + :param trace_logging: Whether or not to enable trace logging. (Optional) + :type trace_logging: bool + """ + kwargs = dict( + api_key = api_key, + api_secret = api_secret, + ping_interval = ping_interval, + ping_timeout = ping_timeout, + retries = retries, + restart_on_error = restart_on_error, + trace_logging = trace_logging, + 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 + ) + + self.listenKey = listenKey + + # for keep alive connection to private spot websocket + # need to send listen key at connection and send keep-alive request every 60 mins + if api_key and api_secret: + if not self.listenKey: + auth = HTTP(api_key=api_key, api_secret=api_secret).create_listen_key() + self.listenKey = auth.get("listenKey") + logger.debug(f"create listenKey: {self.listenKey}") + + if not self.listenKey: + raise Exception(f"ListenKey not found. Error: {auth}") + + kwargs["endpoint"] = f"wss://wbs.mexc.com/ws?listenKey={self.listenKey}" + + # setup keep-alive connection loop + self.kal = threading.Thread(target=lambda: self._keep_alive_loop()) + self.kal.daemon = True + self.kal.start() + + + super().__init__(**kwargs) + + def _keep_alive_loop(self): + """ + Runs a loop that sends a keep-alive message every 59 minutes to maintain the connection + with the MEXC API. + + :return: None + """ + + while True: + time.sleep(59 * 60) # 59 min + if self.listenKey: + resp = HTTP(api_key=self.api_key, api_secret=self.api_secret).keep_alive_listen_key(self.listenKey) + logger.debug(f"keep-alive listenKey - {self.listenKey}. Response: {resp}") + else: + break + + # <=================================================================> + # + # Public + # + # <=================================================================> + + def deals_stream(self, + callback: Callable[..., None], + symbol: Union[str,List[str]]): + """ + ### Trade Streams + The Trade Streams push raw trade information; each trade has a unique buyer and seller. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#trade-streams + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: Union[str,List[str]] + + :return: None + """ + if isinstance(symbol,str): + symbols=[symbol] #str + else: + symbols=symbol #list + params = [dict( + symbol = s + ) for s in symbols] + topic = "public.deals" + self._ws_subscribe(topic, callback, params) + + def kline_stream(self, + callback: Callable[..., None], + symbol: str, + interval: int): + """ + ### Kline Streams + The Kline/Candlestick Stream push updates to the current klines/candlestick every second. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#kline-streams + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + :param interval: the interval of the kline + :type interval: int + + :return: None + """ + params = [dict( + symbol = symbol, + interval = interval + )] + topic = "public.kline" + self._ws_subscribe(topic, callback, params) + + def increase_depth_stream(self, + callback: Callable[..., None], + symbol: str): + """ + ### Diff.Depth Stream + If the quantity is 0, it means that the order of the price has been cancel or traded,remove the price level. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#diff-depth-stream + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + + :return: None + """ + params = [dict( + symbol = symbol + )] + topic = "public.increase.depth" + self._ws_subscribe(topic, callback, params) + + def limit_depth_stream(self, + callback: Callable[..., None], + symbol: str, + level: int): + """ + ### Partial Book Depth Streams + Top bids and asks, Valid are 5, 10, or 20. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#partial-book-depth-streams + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbol: the name of the contract + :type symbol: str + :param level: the level of the depth. Valid are 5, 10, or 20. + :type level: int + + :return: None + """ + params = [dict( + symbol = symbol, + level = level + )] + topic = "public.limit.depth" + self._ws_subscribe(topic, callback, params) + + def book_ticker(self, + callback: Callable[..., None], + symbol: str): + """ + ### Individual Symbol Book Ticker Streams + Pushes any update to the best bid or ask's price or quantity in real-time for a specified symbol. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#partial-book-depth-streams + + :param callback: the callback function + :type callback: Callable[..., None] + :param symbols: the names of the contracts + :type symbols: str + + :return: None + """ + params = [dict( + symbol = symbol + )] + topic = "public.bookTicker" + self._ws_subscribe(topic, callback, params) + + # <=================================================================> + # + # Private + # + # <=================================================================> + + def account_update(self, callback: Callable[..., None]): + """ + ### Spot Account Update + The server will push an update of the account assets when the account balance changes. + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#websocket-user-data-streams + + :param callback: the callback function + :type callback: Callable[..., None] + + :return: None + """ + params = [{}] + topic = "private.account" + self._ws_subscribe(topic, callback, params) + + def account_deals(self, callback: Callable[..., None]): + """ + ### Spot Account Deals + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#spot-account-deals + + :param callback: the callback function + :type callback: Callable[..., None] + + :return: None + """ + params = [{}] + topic = "private.deals" + self._ws_subscribe(topic, callback, params) + + def account_orders(self, callback: Callable[..., None]): + """ + ### Spot Account Orders + + https://mexcdevelop.github.io/apidocs/spot_v3_en/#spot-account-orders + + :param callback: the callback function + :type callback: Callable[..., None] + + :return: None + """ + params = [{}] + topic = "private.orders" + self._ws_subscribe(topic, callback, params) +