├── .gitignore ├── LICENSE ├── README.md ├── bingx ├── __init__.py ├── __version__.py ├── _helpers.py ├── _http_manager.py ├── exceptions.py ├── main.py ├── perpetual │ ├── __init__.py │ ├── v1 │ │ ├── __init__.py │ │ ├── account.py │ │ ├── market.py │ │ ├── other.py │ │ ├── perpetual.py │ │ ├── trade.py │ │ └── types.py │ └── v2 │ │ ├── __init__.py │ │ ├── account.py │ │ ├── market.py │ │ ├── other.py │ │ ├── perpetual.py │ │ ├── trade.py │ │ └── types.py ├── spot │ ├── __init__.py │ ├── market.py │ ├── other.py │ ├── spot.py │ ├── trade.py │ ├── transfer.py │ └── types.py └── standard │ ├── __init__.py │ └── standard.py ├── examples └── .gitkeep ├── setup.py └── tests ├── perpetual └── v2 │ ├── test_account.py │ ├── test_market.py │ ├── test_other.py │ └── test_trade.py ├── spot ├── test_market.py ├── test_other.py └── test_transfer.py ├── standard └── test_standard.py ├── test_helpers.py └── test_http_manager.py /.gitignore: -------------------------------------------------------------------------------- 1 | # CUSTOM 2 | test.py 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-BingX 2 | 3 | Python-bingx is a powerful and flexible Python package that allows you to easily interact with the BingX API. The package currently supports the Spot API, Standard API, Perpetual Swap API Reference V1, and Perpetual Swap API Reference V2. With python-bingx, you can retrieve real-time market data, manage your account, place trades, and more... 4 | 5 | # Installation 6 | 7 | To install python-bingx, you can use pip: 8 | 9 | ```python 10 | pip install python-bingx 11 | ``` 12 | 13 | # Usage 14 | 15 | There are multiple ways to use python-bingx, depending on your needs and preferences. 16 | 17 | ### Using BingX 18 | 19 | The most straightforward way to use python-bingx is by importing the `BingX` class and initializing it with your API key and secret key: 20 | 21 | ```python 22 | from bingX import BingX 23 | 24 | bingx_client = BingX(api_key="api_key", secret_key="secret_key") 25 | ``` 26 | 27 | Once you have initialized the client, you can call any of the available APIs, for example: 28 | 29 | ```python 30 | # Call the Trade API of Perpetual V2 31 | bingx_client.perpetual_v2.trade.trade_order() 32 | ``` 33 | 34 | ### Using PerpetualV2 35 | 36 | If you prefer to work with a specific API or version, you can import the relevant class and initialize it with your API key and secret key: 37 | 38 | ```python 39 | from bingX.perpetual.v2 import PerpetualV2 40 | 41 | bingx_client = PerpetualV2(api_key="api_key", secret_key="secret_key") 42 | ``` 43 | 44 | Once you have initialized the client, you can call any of the available APIs, for example: 45 | 46 | ```python 47 | # Call the Trade API of Perpetual V2 48 | bingx_client.trade.trade_order() 49 | ``` 50 | 51 | # Handling Responses 52 | 53 | Python-bingx uses requests library to communicate with the API and returns the response in JSON format. You can easily handle the response by accessing the relevant key(s) in the dictionary, for example: 54 | 55 | ```python 56 | # Get the symbol and last price of BTC/USDT 57 | response = bingx_client.perpetual_v2.market.get_ticker("BTC-USDT") 58 | symbol = response["symbol"] 59 | last_price = response["lastPrice"] 60 | ``` 61 | 62 | # Error Handling 63 | 64 | In case of errors or exceptions, python-bingx will raise relevant exceptions with error message and error code. You can catch and handle the exceptions accordingly, for example: 65 | 66 | ```python 67 | from bingX import ClientError, ServerError 68 | 69 | try: 70 | response = bingx_client.perpetual_v2.trade.create_order() 71 | except (ClientError, ServerError) as e: 72 | error_code = e.error_code 73 | error_message = e.error_message 74 | ``` 75 | 76 | # Contributing 77 | 78 | Python-bingx welcomes contributions from the community! If you'd like to contribute, please fork the repository, create a feature branch, make your changes, and submit a pull request. Before submitting, please ensure that your code follows the PEP 8 style guide and includes appropriate tests. 79 | 80 | # License 81 | 82 | Python-bingx is licensed under the MIT License. See the LICENSE file for more information. 83 | -------------------------------------------------------------------------------- /bingx/__init__.py: -------------------------------------------------------------------------------- 1 | from bingX.exceptions import ClientError, ServerError 2 | from bingX.main import BingX 3 | -------------------------------------------------------------------------------- /bingx/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = "python-bingx" 2 | __description__ = "Unofficial Python3 API connector for BingX's HTTP and WebSockets APIs." 3 | __version__ = "1.0.1" 4 | __author__ = "niewiemczego" 5 | __license__ = "MIT" 6 | -------------------------------------------------------------------------------- /bingx/_helpers.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import time 4 | from dataclasses import asdict 5 | from enum import Enum 6 | from typing import Any 7 | 8 | 9 | class DictMixin: 10 | def to_dict(self) -> dict[str, Any]: 11 | def convert_value(value): 12 | if isinstance(value, Enum): 13 | return value.value 14 | return value 15 | return {k: convert_value(v) for k, v in asdict(self).items() if v is not None} 16 | 17 | 18 | def generate_timestamp() -> int: 19 | """ 20 | It returns the current time in milliseconds 21 | """ 22 | return int(time.time() * 10 ** 3) 23 | 24 | 25 | def generate_hash(key: str, query_string: str) -> hmac.HMAC: 26 | """ 27 | It returns the hash of the query string using the secret key with the SHA256 algorithm 28 | 29 | :param key: The secret key that you'll use to generate the hash 30 | :param query_string: The query string that you want to sign 31 | """ 32 | return hmac.new(key.encode(), query_string.encode(), hashlib.sha256) 33 | -------------------------------------------------------------------------------- /bingx/_http_manager.py: -------------------------------------------------------------------------------- 1 | from json.decoder import JSONDecodeError 2 | from typing import Any 3 | 4 | import requests 5 | 6 | from bingX._helpers import generate_hash, generate_timestamp 7 | from bingX.exceptions import ClientError, InvalidMethodException, ServerError 8 | 9 | 10 | class _HTTPManager: 11 | __BASE_URL = "https://open-api.bingx.com" 12 | 13 | def __init__(self, api_key: str, secret_key: str) -> None: 14 | self.__secret_key = secret_key 15 | self.__session = requests.Session() 16 | self.__session.headers.update({'X-BX-APIKEY': api_key}) 17 | 18 | def _generate_signature(self, query_string: str) -> str: 19 | """ 20 | It takes a query string and returns a signature 21 | 22 | :param query_string: The query string that you want to sign 23 | :return: A string of the signature 24 | """ 25 | 26 | hmac = generate_hash(self.__secret_key, query_string) 27 | signature = hmac.hexdigest() 28 | return signature 29 | 30 | def _generate_query_string(self, payload: dict[str, Any] = {}) -> str: 31 | """ 32 | It takes a payload and returns a query string 33 | 34 | :param payload: The payload that you want to convert to a query string 35 | :return: A string of the query string 36 | """ 37 | 38 | payload["timestamp"] = generate_timestamp() 39 | query_string = '&'.join(f'{k}={v}' for k, v in payload.items() if v) 40 | query_string += f"&signature={self._generate_signature(query_string)}" 41 | return query_string 42 | 43 | def _request(self, method: str, endpoint: str, payload: dict[str, Any] = {}, headers: dict[str, Any] = {}) -> requests.Response: 44 | """ 45 | It takes a method, endpoint, payload, and headers, and returns a response 46 | 47 | :param method: The HTTP method to use (GET, POST, PUT, DELETE) 48 | :param endpoint: The endpoint you want to hit i.e. /openApi/swap/v2/trade/order 49 | :param payload: The data to be sent to the server 50 | :param headers: This is a dictionary of headers that will be sent with the request 51 | """ 52 | if headers: 53 | self.__session.headers.update(headers) 54 | 55 | url = f"{self.__BASE_URL}{endpoint}?{self._generate_query_string(payload)}" 56 | match method: 57 | case "GET": 58 | req = self.__session.get(url) 59 | case "POST": 60 | req = self.__session.post(url) 61 | case "PUT": 62 | req = self.__session.put(url) 63 | case "DELETE": 64 | req = self.__session.delete(url) 65 | case _: 66 | raise InvalidMethodException(f"Invalid method used: {method}") 67 | 68 | if req.status_code != 200: 69 | raise ServerError(req.status_code, req.text) 70 | 71 | try: 72 | req_json: dict[str, Any] = req.json() 73 | except JSONDecodeError: # i.e. sometimes it return just int status code 74 | return req 75 | else: 76 | if isinstance(req_json, dict): 77 | if req_json.get("code") is not None and req_json.get("code") != 0: 78 | raise ClientError(req_json.get("code"), req_json.get("msg")) 79 | return req 80 | 81 | def get(self, endpoint: str, payload: dict[str, Any] = {}, headers: dict[str, Any] = {}) -> requests.Response: 82 | """ 83 | It makes a GET request to the given endpoint with the given payload and headers 84 | 85 | :param endpoint: The endpoint you want to hit i.e. /openApi/swap/v2/trade/order 86 | :param payload: The data to be sent to the server 87 | :param headers: This is a dictionary of headers that will be sent with the request 88 | :return: A response object 89 | """ 90 | 91 | return self._request("GET", endpoint, payload, headers) 92 | 93 | def post(self, endpoint: str, payload: dict[str, Any] = {}, headers: dict[str, Any] = {}) -> requests.Response: 94 | """ 95 | It makes a POST request to the given endpoint with the given payload and headers 96 | 97 | :param endpoint: The endpoint you want to hit i.e. /openApi/swap/v2/trade/order 98 | :param payload: The data to be sent to the server 99 | :param headers: This is a dictionary of headers that will be sent with the request 100 | :return: A response object 101 | """ 102 | 103 | return self._request("POST", endpoint, payload, headers) 104 | 105 | def put(self, endpoint: str, payload: dict[str, Any] = {}, headers: dict[str, Any] = {}) -> requests.Response: 106 | """ 107 | It makes a PUT request to the given endpoint with the given payload and headers 108 | 109 | :param endpoint: The endpoint you want to hit i.e. /openApi/swap/v2/trade/order 110 | :param payload: The data to be sent to the server 111 | :param headers: This is a dictionary of headers that will be sent with the request 112 | :return: A response object 113 | """ 114 | 115 | return self._request("PUT", endpoint, payload, headers) 116 | 117 | def delete(self, endpoint: str, payload: dict[str, Any] = {}, headers: dict[str, Any] = {}) -> requests.Response: 118 | """ 119 | It makes a DELETE request to the given endpoint with the given payload and headers 120 | 121 | :param endpoint: The endpoint you want to hit i.e. /openApi/swap/v2/trade/order 122 | :param payload: The data to be sent to the server 123 | :param headers: This is a dictionary of headers that will be sent with the request 124 | :return: A response object 125 | """ 126 | 127 | return self._request("DELETE", endpoint, payload, headers) -------------------------------------------------------------------------------- /bingx/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidMethodException(Exception): 2 | """Raised when an invalid method is used""" 3 | pass 4 | 5 | 6 | class OrderException(Exception): 7 | """Raised when an error occurs while creating an order object""" 8 | pass 9 | 10 | 11 | class HistoryOrderException(Exception): 12 | """Raised when an error occurs while creating an order object""" 13 | pass 14 | 15 | 16 | class ClientError(Exception): 17 | BUISNESS_ERROR_CODES = { 18 | 100001: "signature verification failed", 19 | 100202: "Insufficient balance", 20 | 100400: "Invalid parameter", 21 | 100440: "Order price deviates greatly from the market price", 22 | 100500: "Internal system error", 23 | 100503: "Server busy", 24 | 80001: "request failed", 25 | 80012: "service unavailable", 26 | 80014: "Invalid parameter", 27 | 80016: "Order does not exist", 28 | 80017: "position does not exist" 29 | } 30 | 31 | def __init__(self, error_code: int, error_message: str) -> None: 32 | self.error_code = error_code 33 | self.error_message = error_message 34 | super().__init__(self.error_message if len(self.error_message) > 0 else self.BUISNESS_ERROR_CODES.get(self.error_code, "Unknown Error")) 35 | 36 | 37 | class ServerError(Exception): 38 | ERROR_CODES = { 39 | 400: "Bad Request - Invalid request format", 40 | 401: "Unauthorized - Invalid API Key", 41 | 403: "Forbidden - You do not have access to the requested resource", 42 | 404: "Not Found", 43 | 429: "Too Many Requests - Return code is used when breaking a request rate limit.", 44 | 418: "return code is used when an IP has been auto-banned for continuing to send requests after receiving 429 codes.", 45 | 500: "Internal Server Error - We had a problem with our server", 46 | 504: "return code means that the API server has submitted a request to the service center but failed to get a response. It should be noted that the 504 return code does not mean that the request failed. It refers to an unknown status. The request may have been executed, or it may have failed. Further confirmation is required." 47 | } 48 | 49 | def __init__(self, error_code: int, error_message: str) -> None: 50 | self.error_code = error_code 51 | self.error_message = error_message 52 | super().__init__(self.error_message if len(self.error_message) > 0 else self.ERROR_CODES.get(self.error_code, "Unknown Error")) 53 | -------------------------------------------------------------------------------- /bingx/main.py: -------------------------------------------------------------------------------- 1 | from bingX.perpetual.v1 import PerpetualV1 2 | from bingX.perpetual.v2 import PerpetualV2 3 | from bingX.spot import Spot 4 | from bingX.standard import Standard 5 | 6 | 7 | class BingX: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.perpetual_v1 = PerpetualV1(api_key, secret_key) 10 | self.perpetual_v2 = PerpetualV2(api_key, secret_key) 11 | self.spot = Spot(api_key, secret_key) 12 | self.standard = Standard(api_key, secret_key) 13 | 14 | -------------------------------------------------------------------------------- /bingx/perpetual/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niewiemczego/python-bingx/d0a511e21423ae75230ed571d0900ea217ef682f/bingx/perpetual/__init__.py -------------------------------------------------------------------------------- /bingx/perpetual/v1/__init__.py: -------------------------------------------------------------------------------- 1 | from .perpetual import PerpetualV1 2 | -------------------------------------------------------------------------------- /bingx/perpetual/v1/account.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | 5 | 6 | class Account: 7 | def __init__(self, api_key: str, secret_key: str) -> None: 8 | self.__http_manager = _HTTPManager(api_key, secret_key) 9 | 10 | def get_details(self, currency: str) -> dict[str, Any]: 11 | """ 12 | Get asset information of user's Perpetual Account 13 | 14 | https://bingx-api.github.io/docs/swap/account-api.html#_1-get-perpetual-swap-account-asset-information 15 | """ 16 | 17 | endpoint = "/api/v1/user/getBalance" 18 | payload = {"currency": currency.upper()} 19 | 20 | response = self.__http_manager.post(endpoint, payload) 21 | return response.json()["data"] 22 | 23 | def get_swap_positions(self, symbol: str) -> dict[str, Any]: 24 | """ 25 | Retrieve information on users' positions of Perpetual Swap. 26 | 27 | https://bingx-api.github.io/docs/swap/account-api.html#_2-perpetual-swap-positions 28 | """ 29 | 30 | endpoint = "/api/v1/user/getPositions" 31 | payload = {"symbol": symbol.upper()} 32 | 33 | response = self.__http_manager.post(endpoint, payload) 34 | return response.json()["data"] -------------------------------------------------------------------------------- /bingx/perpetual/v1/market.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | 5 | 6 | class Market: 7 | def __init__(self, api_key: str, secret_key: str) -> None: 8 | self.__http_manager = _HTTPManager(api_key, secret_key) 9 | 10 | def get_contract_info(self) -> list[dict[str, Any]]: 11 | """ 12 | Get the contract information of the swap contract 13 | 14 | https://bingx-api.github.io/docs/swap/market-api.html#_1-contract-information 15 | """ 16 | 17 | endpoint = "/api/v1/market/getAllContracts" 18 | 19 | response = self.__http_manager.get(endpoint) 20 | return response.json()["data"] 21 | 22 | def get_latest_price_of_trading_pair(self, symbol: str) -> dict[str, Any]: 23 | """ 24 | It returns the latest price of a trading pair. If no transaction pair parameters are sent, all transaction pair information will be returned 25 | 26 | :param symbol: The trading pair you want to get the latest price of 27 | 28 | https://bingx-api.github.io/docs/swap/market-api.html#_2-get-latest-price-of-a-trading-pair 29 | """ 30 | 31 | endpoint = "/api/v1/market/getLatestPrice" 32 | 33 | payload = {"symbol": symbol.upper()} 34 | 35 | response = self.__http_manager.get(endpoint, payload) 36 | return response.json()["data"] 37 | 38 | def get_market_depth(self, symbol: str, level: int = 5) -> dict[str, Any]: 39 | """ 40 | It returns the market depth of a given symbol 41 | 42 | :param symbol: The symbol you want to get the market depth for 43 | :param level: Number of levels 44 | 45 | https://bingx-api.github.io/docs/swap/market-api.html#_3-get-market-depth 46 | """ 47 | 48 | endpoint = "/api/v1/market/getMarketDepth" 49 | 50 | payload = {"symbol": symbol.upper(), "level": level} 51 | 52 | response = self.__http_manager.get(endpoint, payload) 53 | return response.json()["data"] 54 | 55 | def get_latest_trade_of_trading_pair(self, symbol: str) -> dict[str, Any]: 56 | """ 57 | It returns the latest trade of a trading pair. 58 | 59 | :param symbol: The trading pair you want to get the latest trades for 60 | 61 | https://bingx-api.github.io/docs/swap/market-api.html#_4-the-latest-trade-of-a-trading-pair 62 | """ 63 | 64 | endpoint = "/api/v1/market/getMarketTrades" 65 | 66 | payload = {"symbol": symbol.upper()} 67 | 68 | response = self.__http_manager.get(endpoint, payload) 69 | return response.json()["data"] 70 | 71 | def get_current_funding_rate(self, symbol: str) -> dict[str, Any]: 72 | """ 73 | Get the current funding rate for a given symbol 74 | 75 | :param symbol: The symbol you want to get the funding rate for. If you don't specify a symbol, you'll get the funding rate for all symbols 76 | 77 | https://bingx-api.github.io/docs/swap/market-api.html#_5-current-funding-rate 78 | """ 79 | 80 | endpoint = "/api/v1/market/getLatestFunding" 81 | payload = {"symbol": symbol.upper()} 82 | 83 | response = self.__http_manager.get(endpoint, payload) 84 | return response.json()["data"] 85 | 86 | def get_funding_rate_history(self, symbol: str) -> dict[str, Any]: 87 | """ 88 | It returns the funding rate history for a given symbol. 89 | 90 | :param symbol: The symbol you want to get the funding rate for 91 | 92 | https://bingx-api.github.io/docs/swap/market-api.html#_6-funding-rate-history 93 | """ 94 | 95 | endpoint = "/api/v1/market/getHistoryFunding" 96 | payload = {"symbol": symbol.upper()} 97 | 98 | response = self.__http_manager.get(endpoint, payload) 99 | return response.json()["data"] 100 | 101 | def get_k_line_data(self, symbol: str, kline_type: str) -> dict[str, Any]: 102 | """ 103 | Get the latest Kline Data. 104 | 105 | :param symbol: The trading pair you want to get the Kline data for 106 | :param kline_type: The type of K-Line (minutes, hours, weeks etc.) 107 | 108 | https://bingx-api.github.io/docs/swap/market-api.html#_7-get-k-line-data 109 | """ 110 | 111 | endpoint = "/api/v1/market/getLatestKline" 112 | payload = {"symbol": symbol.upper(), "klineType": kline_type} 113 | 114 | response = self.__http_manager.get(endpoint, payload) 115 | return response.json()["data"] 116 | 117 | def get_k_line_data_history(self, symbol: str, kline_type: str, start_time: int, end_time: int) -> dict[str, Any]: 118 | """ 119 | Get the K-Line history data of the trading price over a certain period of time. 120 | 121 | :param symbol: The trading pair you want to get the Kline data for 122 | :param kline_type: The type of K-Line (minutes, hours, weeks etc.) 123 | :param start_time: The start time of the Kline data you want to get 124 | :param end_time: The end time of the Kline data you want to get 125 | 126 | https://bingx-api.github.io/docs/swap/market-api.html#_8-k-line-data-history 127 | """ 128 | 129 | endpoint = "/api/v1/market/getHistoryKlines" 130 | payload = {"symbol": symbol.upper(), "klineType": kline_type, "startTime": start_time, "endTime": end_time} 131 | 132 | response = self.__http_manager.get(endpoint, payload) 133 | return response.json()["data"] 134 | 135 | def get_swap_open_positions(self, symbol: str) -> dict[str, Any]: 136 | """ 137 | It returns the open positions for a given symbol. 138 | 139 | :param symbol: The symbol you want to get the open interest for 140 | 141 | https://bingx-api.github.io/docs/swap/market-api.html#_9-get-swap-open-positions 142 | """ 143 | 144 | endpoint = "/api/v1/market/getOpenPositions" 145 | payload = {"symbol": symbol.upper()} 146 | 147 | response = self.__http_manager.get(endpoint, payload) 148 | return response.json()["data"] 149 | 150 | def get_ticker(self, symbol: str | None = None) -> dict[str, Any] : 151 | """ 152 | It returns the ticker for a given symbol. 153 | If no transaction pair parameters are sent, all transaction pair information will be returned 154 | 155 | :param symbol: The symbol you want to get the ticker for. If you don't specify a symbol, you'll getthe ticker for all symbols 156 | 157 | https://bingx-api.github.io/docs/swap/market-api.html#_10-get-ticker 158 | """ 159 | 160 | endpoint = "/api/v1/market/getTicker" 161 | payload = {} if symbol is None else {"symbol": symbol.upper()} 162 | 163 | response = self.__http_manager.get(endpoint, payload) 164 | return response.json()["data"] 165 | -------------------------------------------------------------------------------- /bingx/perpetual/v1/other.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.exceptions import ServerError 5 | 6 | 7 | class Other: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def generate_listen_key(self) -> dict[str, Any]: 12 | """ 13 | Generates a listen key valid for 1 hour 14 | 15 | https://bingx-api.github.io/docs/swap/other-interface.html#generate-listen-key 16 | """ 17 | 18 | endpoint = "/api/v1/user/auth/userDataStream" 19 | 20 | response = self.__http_manager.post(endpoint) 21 | return response.json() 22 | 23 | def extend_listen_key_validity_period(self, listen_key: str) -> int: 24 | """ 25 | The validity period is extended to 60 minutes after this call, and it is recommended to send a ping every 30 minutes. 26 | 27 | 200 - success, 204 - not content, 404 - not find key 28 | 29 | return: 200 if the listen key is extended successfully 30 | 31 | https://bingx-api.github.io/docs/swap/other-interface.html#extend-listen-key-validity-period 32 | """ 33 | 34 | endpoint = "/api/v1/user/auth/userDataStream" 35 | payload = {"listenKey": listen_key} 36 | 37 | try: 38 | response = self.__http_manager.put(endpoint, payload) 39 | except ServerError as e: 40 | return e.error_code 41 | return response.status_code 42 | 43 | def delete_listen_key(self, listen_key: str) -> int: 44 | """ 45 | Delete User data flow. 46 | 47 | 200 - success, 204 - not content, 404 - not find key 48 | 49 | return: 200 if the listen key is deleted successfully 50 | 51 | https://bingx-api.github.io/docs/swap/other-interface.html#delete-listen-key 52 | """ 53 | 54 | endpoint = "/api/v1/user/auth/userDataStream" 55 | payload = {"listenKey": listen_key} 56 | 57 | try: 58 | response = self.__http_manager.delete(endpoint, payload) 59 | except ServerError as e: 60 | return e.error_code 61 | return response.status_code -------------------------------------------------------------------------------- /bingx/perpetual/v1/perpetual.py: -------------------------------------------------------------------------------- 1 | from bingX.perpetual.v1.account import Account 2 | from bingX.perpetual.v1.market import Market 3 | from bingX.perpetual.v1.other import Other 4 | from bingX.perpetual.v1.trade import Trade 5 | 6 | 7 | class PerpetualV1: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.account = Account(api_key, secret_key) 10 | self.market = Market(api_key, secret_key) 11 | self.trade = Trade(api_key, secret_key) 12 | self.other = Other(api_key, secret_key) 13 | -------------------------------------------------------------------------------- /bingx/perpetual/v1/trade.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.perpetual.v1.types import MarginType, Order, PositionSide 5 | 6 | 7 | class Trade: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def create_order(self, order: Order) -> dict[str, Any]: 12 | """ 13 | The current account places an order on the specified symbol contract. 14 | 15 | https://bingx-api.github.io/docs/swap/trade-api.html#_1-place-a-new-order 16 | """ 17 | 18 | endpoint = "/api/v1/user/trade" 19 | payload = order.to_dict() 20 | 21 | response = self.__http_manager.post(endpoint, payload) 22 | return response.json()["data"] 23 | 24 | def one_click_close_position(self, symbol: str, position_id: int) -> dict[str, Any]: 25 | """ 26 | After querying the position information, you can close the position by one-click based on the position ID. Please note that the one-click closed position is traded at market price. 27 | 28 | https://bingx-api.github.io/docs/swap/trade-api.html#_2-one-click-close-position 29 | """ 30 | 31 | endpoint = "/api/v1/user/oneClickClosePosition" 32 | payload = {"symbol": symbol.upper(), "positionId": position_id} 33 | 34 | response = self.__http_manager.post(endpoint, payload) 35 | return response.json()["data"] 36 | 37 | def one_click_close_all_position(self, symbol: str, position_id: int) -> dict[str, Any]: 38 | """ 39 | Close all positions within the current account by one click. Please note that the one-click closed positions are traded at market price. 40 | 41 | https://bingx-api.github.io/docs/swap/trade-api.html#_3-one-click-close-all-positions 42 | """ 43 | 44 | endpoint = "/api/v1/user/oneClickCloseAllPositions" 45 | 46 | response = self.__http_manager.post(endpoint) 47 | return response.json()["data"] 48 | 49 | def cancel_order(self, order_id: int, symbol: str) -> dict[str, Any]: 50 | """ 51 | Cancel an order that is currently in a unfilled state 52 | 53 | https://bingx-api.github.io/docs/swap/trade-api.html#_4-cancel-an-order 54 | """ 55 | 56 | endpoint = "/api/v1/user/cancelOrder" 57 | payload = {"orderId": order_id, "symbol": symbol.upper()} 58 | 59 | response = self.__http_manager.post(endpoint, payload) 60 | return response.json()["data"] 61 | 62 | def cancel_batch_orders(self, order_ids: list[int], symbol: str) -> dict[str, Any]: 63 | """ 64 | Cancel a batch of orders that are currently in a unfilled state 65 | 66 | https://bingx-api.github.io/docs/swap/trade-api.html#_5-cancel-a-batch-of-orders 67 | """ 68 | 69 | endpoint = "/api/v1/user/batchCancelOrders" 70 | payload = {"symbol": symbol.upper(), "oids": order_ids} 71 | 72 | response = self.__http_manager.post(endpoint, payload) 73 | return response.json()["data"] 74 | 75 | def cancel_all_orders(self) -> dict[str, Any]: 76 | """ 77 | Cancel all orders that are currently in a unfilled state 78 | 79 | https://bingx-api.github.io/docs/swap/trade-api.html#_6-cancel-all-orders 80 | """ 81 | 82 | endpoint = "/api/v1/user/cancelAll" 83 | 84 | response = self.__http_manager.post(endpoint) 85 | return response.json()["data"] 86 | 87 | def get_unfilled_order_acquisition(self, symbol: str) -> dict[str, Any]: 88 | """ 89 | Query the details of unfilled orders within the current account over a certain period of time 90 | 91 | https://bingx-api.github.io/docs/swap/trade-api.html#_7-unfilled-order-acquisition 92 | """ 93 | 94 | endpoint = "/api/v1/user/pendingOrders" 95 | payload = {"symbol": symbol.upper()} 96 | 97 | response = self.__http_manager.post(endpoint, payload) 98 | return response.json()["data"] 99 | 100 | def get_order(self, order_id: int, symbol: str) -> dict[str, Any]: 101 | """ 102 | Query order details 103 | 104 | https://bingx-api.github.io/docs/swap/trade-api.html#_8-query-order-details 105 | """ 106 | 107 | endpoint = "/api/v1/user/queryOrderStatus" 108 | payload = {"symbol": symbol.upper(), "orderId": order_id} 109 | 110 | response = self.__http_manager.get(endpoint, payload) 111 | return response.json()["data"] 112 | 113 | def get_margin_mode(self, symbol: str) -> dict[str, Any]: 114 | """ 115 | Query the user's margin mode on the specified symbol contract: isolated or cross. 116 | 117 | https://bingx-api.github.io/docs/swap/trade-api.html#_9-query-margin-mode 118 | """ 119 | 120 | endpoint = "/api/v1/user/getMarginMode" 121 | payload = {"symbol": symbol.upper()} 122 | 123 | response = self.__http_manager.get(endpoint, payload) 124 | return response.json()["data"] 125 | 126 | def change_margin_mode(self, symbol: str, margin_type: MarginType) -> dict[str, Any]: 127 | """ 128 | Change the user's margin mode on the specified symbol contract: isolated margin or cross margin.] 129 | 130 | https://bingx-api.github.io/docs/swap/trade-api.html#_10-switch-margin-mode 131 | """ 132 | 133 | endpoint = "/api/v1/user/setMarginMode" 134 | payload = {"symbol": symbol, "marginMode": margin_type.value} 135 | 136 | response = self.__http_manager.post(endpoint, payload) 137 | return response.json()["data"] 138 | 139 | def get_leverage(self, symbol: str) -> dict[str, Any]: 140 | """ 141 | Query the opening leverage of the user in the specified symbol contract. 142 | 143 | https://bingx-api.github.io/docs/swap/trade-api.html#_11-query-leverage 144 | """ 145 | 146 | endpoint = "/api/v1/user/getLeverage" 147 | payload = {"symbol": symbol.upper()} 148 | 149 | response = self.__http_manager.post(endpoint, payload) 150 | return response.json()["data"] 151 | 152 | def change_leverage(self, symbol: str, position_side: PositionSide, leverage: int, recvWindow: int | None = None) -> dict[str, Any]: 153 | """ 154 | Switch the leverage size of a certain trading pair for long or short positions 155 | 156 | https://bingx-api.github.io/docs/swap/trade-api.html#_12-switch-leverage 157 | """ 158 | 159 | endpoint = "/api/v1/user/setLeverage" 160 | payload = {"symbol": symbol.upper(), "side": position_side.value, "leverage": leverage} 161 | 162 | response = self.__http_manager.post(endpoint, payload) 163 | return response.json()["data"] 164 | 165 | def get_force_orders(self, symbol: str, auto_close_type, last_order_id: int, length: int) -> dict[str, Any]: 166 | """ 167 | Query the user's forced liquidation order 168 | 169 | https://bingx-api.github.io/docs/swap/trade-api.html#_13-user-s-force-orders 170 | """ 171 | 172 | endpoint = "/api/v1/user/forceOrders" 173 | payload = {"symbol": symbol.upper(), "autoCloseType": auto_close_type, "lastOrderId": last_order_id, "length": length} 174 | 175 | response = self.__http_manager.post(endpoint, payload) 176 | return response.json()["data"] 177 | 178 | def get_orders_history(self, last_order_id: int, length: int, symbol: str | None = None) -> dict[str, Any]: 179 | """ 180 | Query the user's historical orders (order status is completed or canceled). The maximum query time range shall not exceed 7 days. 181 | Query data within the last 7 days by default 182 | 183 | https://bingx-api.github.io/docs/swap/trade-api.html#_14-user-s-history-orders 184 | """ 185 | 186 | endpoint = "/api/v1/user/historyOrders" 187 | payload = {"lastOrderId": last_order_id, "length": length} if symbol is None else {"symbol": symbol, "lastOrderId": last_order_id, "length": length} 188 | 189 | response = self.__http_manager.post(endpoint, payload) 190 | return response.json()["data"] 191 | 192 | def place_stop_order(self, position_id: str, entrust_volume: float, order_id: str | None = None, stop_loss_price: float | None = None, take_profit_price: float | None = None) -> dict[str, Any]: 193 | """ 194 | 195 | https://bingx-api.github.io/docs/swap/trade-api.html#_15-place-a-stop-order 196 | """ 197 | 198 | endpoint = "/api/v1/user/stopOrder" 199 | payload = {"positionId": position_id, "entrustVolume": entrust_volume, "orderId": order_id, "stopLossPrice": stop_loss_price, "takeProfitPrice": take_profit_price} 200 | 201 | response = self.__http_manager.post(endpoint, payload) 202 | return response.json()["data"] 203 | 204 | def cancel_stop_order(self, order_id: str) -> dict[str, Any]: 205 | """ 206 | 207 | https://bingx-api.github.io/docs/swap/trade-api.html#_16-cancel-stop-order 208 | """ 209 | 210 | endpoint = "/api/v1/user/cancelStopOrder" 211 | payload = {"orderId": order_id} 212 | 213 | response = self.__http_manager.post(endpoint, payload) 214 | return response.json()["data"] 215 | 216 | def get_stop_orders(self, symbol: str) -> dict[str, Any]: 217 | """ 218 | 219 | https://bingx-api.github.io/docs/swap/trade-api.html#_17-query-stop-orders 220 | """ 221 | 222 | endpoint = "/api/v1/user/pendingStopOrders" 223 | payload = {"symbol": symbol.upper()} 224 | 225 | response = self.__http_manager.post(endpoint, payload) 226 | return response.json()["data"] 227 | 228 | def get_history_stop_orders(self, symbol: str, last_order_id: int, lenght: int) -> dict[str, Any]: 229 | """ 230 | 231 | https://bingx-api.github.io/docs/swap/trade-api.html#_18-query-history-stop-orders 232 | """ 233 | 234 | endpoint = "/api/v1/user/historyStopOrders" 235 | payload = {"symbol": symbol.upper(), "lastOrderId": last_order_id, "length": lenght} 236 | 237 | response = self.__http_manager.post(endpoint, payload) 238 | return response.json()["data"] -------------------------------------------------------------------------------- /bingx/perpetual/v1/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum 3 | 4 | from bingX._helpers import DictMixin 5 | from bingX.exceptions import HistoryOrderException, OrderException 6 | 7 | 8 | class Side(Enum): 9 | BID = "Bid" 10 | ASK = "Ask" 11 | 12 | 13 | class OrderType(Enum): 14 | LIMIT = "Limit" 15 | MARKET = "Market" 16 | 17 | 18 | class TimeInForce(Enum): 19 | IOC = "IOC" 20 | POC = "POC" 21 | 22 | 23 | class Action(Enum): 24 | OPEN = "Open" 25 | CLOSE = "Close" 26 | 27 | 28 | class MarginType(Enum): 29 | ISOLATED = "Isolated" 30 | CROSSED = "Crossed" 31 | 32 | 33 | class PositionSide(Enum): 34 | LONG = "Long" 35 | SHORT = "Short" 36 | 37 | 38 | @dataclass 39 | class Order(DictMixin): 40 | symbol: str 41 | side: Side 42 | entrust_price: float 43 | entrust_volume: float 44 | type: OrderType 45 | action: Action 46 | taker_profit_price: float | None = None 47 | stop_loss_price: float | None = None 48 | -------------------------------------------------------------------------------- /bingx/perpetual/v2/__init__.py: -------------------------------------------------------------------------------- 1 | from .perpetual import PerpetualV2 2 | -------------------------------------------------------------------------------- /bingx/perpetual/v2/account.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.perpetual.v2.types import ProfitLossFundFlow 5 | 6 | 7 | class Account: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def get_details(self, recvWindow: int | None = None) -> dict[str, Any]: 12 | """ 13 | Get asset information of user's Perpetual Account 14 | 15 | https://bingx-api.github.io/docs/swapV2/account-api.html#_1-get-perpetual-swap-account-asset-information 16 | """ 17 | 18 | endpoint = "/openApi/swap/v2/user/balance" 19 | payload = {} if recvWindow is None else {"recvWindow": recvWindow} 20 | 21 | response = self.__http_manager.get(endpoint, payload) 22 | return response.json()["data"] 23 | 24 | def get_swap_positions(self, symbol: str | None = None, recvWindow: int | None = None) -> list[dict[str, Any]]: 25 | """ 26 | Retrieve information on users' positions of Perpetual Swap. 27 | 28 | https://bingx-api.github.io/docs/swapV2/account-api.html#_2-perpetual-swap-positions 29 | """ 30 | 31 | endpoint = "/openApi/swap/v2/user/positions" 32 | if symbol is None: 33 | payload = {} if recvWindow is None else {"recvWindow": recvWindow} 34 | else: 35 | payload = {"symbol": symbol.upper()} if recvWindow is None else {"symbol": symbol.upper(), "recvWindow": recvWindow} 36 | 37 | response = self.__http_manager.get(endpoint, payload) 38 | return response.json()["data"] 39 | 40 | def get_profit_loss_fund_flow(self, profit_loss_fund_flow: ProfitLossFundFlow) -> list[dict[str, Any]]: 41 | """ 42 | Query the capital flow of the perpetual contract under the current account. 43 | If neither startTime nor endTime is sent, only the data of the last 7 days will be returned. 44 | If the incomeType is not sent, return all types of account profit and loss fund flow. 45 | Only keep the last 3 months data. 46 | 47 | https://bingx-api.github.io/docs/swapV2/account-api.html#_3-get-account-profit-and-loss-fund-flow 48 | """ 49 | endpoint = "/openApi/swap/v2/user/income" 50 | payload = profit_loss_fund_flow.to_dict() 51 | 52 | response = self.__http_manager.get(endpoint, payload) 53 | return response.json()["data"] 54 | -------------------------------------------------------------------------------- /bingx/perpetual/v2/market.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | 5 | 6 | class Market: 7 | def __init__(self, api_key: str, secret_key: str) -> None: 8 | self.__http_manager = _HTTPManager(api_key, secret_key) 9 | 10 | def get_contract_info(self) -> list[dict[str, Any]]: 11 | """ 12 | Get the contract information of the swap contract 13 | 14 | https://bingx-api.github.io/docs/swapV2/market-api.html#_1-contract-information 15 | """ 16 | 17 | endpoint = "/openApi/swap/v2/quote/contracts" 18 | 19 | response = self.__http_manager.get(endpoint) 20 | return response.json()["data"] 21 | 22 | def get_latest_price_of_trading_pair(self, symbol: str | None = None) -> list[dict[str, Any]] | dict[str, Any]: 23 | """ 24 | It returns the latest price of a trading pair. If no transaction pair parameters are sent, all transaction pair information will be returned 25 | 26 | :param symbol: The trading pair you want to get the latest price of 27 | 28 | https://bingx-api.github.io/docs/swapV2/market-api.html#_2-get-latest-price-of-a-trading-pair 29 | """ 30 | 31 | endpoint = "/openApi/swap/v2/quote/price" 32 | 33 | payload = {} if symbol is None else {"symbol": symbol.upper()} 34 | 35 | response = self.__http_manager.get(endpoint, payload) 36 | return response.json()["data"] 37 | 38 | def get_market_depth(self, symbol: str, limit: int = 20) -> dict[str, Any]: 39 | """ 40 | It returns the market depth of a given symbol 41 | 42 | :param symbol: The symbol you want to get the market depth for 43 | :param limit: The number of price levels to return, optional value:[5, 10, 20, 50, 100, 500, 1000] 44 | 45 | https://bingx-api.github.io/docs/swapV2/market-api.html#_3-get-market-depth 46 | """ 47 | 48 | endpoint = "/openApi/swap/v2/quote/depth" 49 | 50 | payload = {"symbol": symbol.upper(), "limit": limit} 51 | 52 | response = self.__http_manager.get(endpoint, payload) 53 | return response.json()["data"] 54 | 55 | def get_latest_trade_of_trading_pair(self, symbol: str, limit: int = 500) -> list[dict[str, Any]]: 56 | """ 57 | It returns the latest trade of a trading pair. 58 | 59 | :param symbol: The trading pair you want to get the latest trades for 60 | :param limit: The number of trades to return, maximum 1000 61 | 62 | https://bingx-api.github.io/docs/swapV2/market-api.html#_4-the-latest-trade-of-a-trading-pair 63 | """ 64 | 65 | endpoint = "/openApi/swap/v2/quote/trades" 66 | 67 | payload = {"symbol": symbol.upper(), "limit": limit} 68 | 69 | response = self.__http_manager.get(endpoint, payload) 70 | return response.json()["data"] 71 | 72 | def get_current_funding_rate(self, symbol: str | None = None) -> list[dict[str, Any]] | dict[str, Any]: 73 | """ 74 | Get the current funding rate for a given symbol 75 | 76 | :param symbol: The symbol you want to get the funding rate for. If you don't specify a symbol, you'll get the funding rate for all symbols 77 | 78 | https://bingx-api.github.io/docs/swapV2/market-api.html#_5-current-funding-rate 79 | """ 80 | 81 | endpoint = "/openApi/swap/v2/quote/premiumIndex" 82 | payload = {} if symbol is None else {"symbol": symbol.upper()} 83 | 84 | response = self.__http_manager.get(endpoint, payload) 85 | return response.json()["data"] 86 | 87 | def get_funding_rate_history(self, symbol: str, start_time: int | None = None, end_time: int | None = None, limit: int = 100) -> list[dict[str, Any]]: 88 | """ 89 | It returns the funding rate history for a given symbol. 90 | If both startTime and endTime are not sent, return the latest limit data. 91 | If the amount of data between startTime and endTime is greater than limit, return the data in the case of startTime + limit. 92 | 93 | :param symbol: The symbol you want to get the funding rate for 94 | :param start_time: The start time of the data you want to query 95 | :param end_time: The end time of the data you want to query 96 | :param limit: The number of results to return, maximum 1000 97 | 98 | https://bingx-api.github.io/docs/swapV2/market-api.html#_6-funding-rate-history 99 | """ 100 | 101 | endpoint = "/openApi/swap/v2/quote/fundingRate" 102 | payload = {"symbol": symbol.upper(), "limit": limit} if start_time is None or end_time is None else {"symbol": symbol.upper(), "startTime": start_time, "endTime": end_time, "limit": limit} 103 | 104 | response = self.__http_manager.get(endpoint, payload) 105 | return response.json()["data"] 106 | 107 | def get_k_line_data(self, symbol: str, interval: str, start_time: int | None = None, end_time: int | None = None, limit: int = 500) -> list[dict[str, Any]] | dict[str, Any]: 108 | """ 109 | Get the latest Kline Data. 110 | If startTime and endTime are not sent, the latest k-line data will be returned by default 111 | 112 | :param symbol: The trading pair you want to get the Kline data for 113 | :param interval: The interval of the Kline data, possible values: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 1w, 1M 114 | :param start_time: The start time of the Kline data, in milliseconds 115 | :param end_time: The end time of the Kline data, in milliseconds 116 | :param limit: The number of Kline data to return, maximum 1440 117 | 118 | https://bingx-api.github.io/docs/swapV2/market-api.html#_7-k-line-data 119 | """ 120 | 121 | endpoint = "/openApi/swap/v2/quote/klines" 122 | payload = {"symbol": symbol.upper(), "interval": interval, "limit": limit} if start_time is None or end_time is None else {"symbol": symbol.upper(), "interval": interval, "startTime": start_time, "endTime": end_time, "limit": limit} 123 | 124 | response = self.__http_manager.get(endpoint, payload) 125 | return response.json()["data"] 126 | 127 | def get_swap_open_positions(self, symbol: str) -> dict[str, Any]: 128 | """ 129 | It returns the open positions for a given symbol. 130 | 131 | :param symbol: The symbol you want to get the open interest for 132 | 133 | https://bingx-api.github.io/docs/swapV2/market-api.html#_8-get-swap-open-positions 134 | """ 135 | 136 | endpoint = "/openApi/swap/v2/quote/openInterest" 137 | payload = {"symbol": symbol.upper()} 138 | 139 | response = self.__http_manager.get(endpoint, payload) 140 | return response.json()["data"] 141 | 142 | def get_ticker(self, symbol: str | None = None) -> list[dict[str, Any]] | dict[str, Any] : 143 | """ 144 | It returns the ticker for a given symbol. 145 | If no transaction pair parameters are sent, all transaction pair information will be returned 146 | 147 | :param symbol: The symbol you want to get the ticker for. If you don't specify a symbol, you'll getthe ticker for all symbols 148 | 149 | https://bingx-api.github.io/docs/swapV2/market-api.html#_9-get-ticker 150 | """ 151 | 152 | endpoint = "/openApi/swap/v2/quote/ticker" 153 | payload = {} if symbol is None else {"symbol": symbol.upper()} 154 | 155 | response = self.__http_manager.get(endpoint, payload) 156 | return response.json()["data"] 157 | -------------------------------------------------------------------------------- /bingx/perpetual/v2/other.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.exceptions import ServerError 5 | 6 | 7 | class Other: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def generate_listen_key(self) -> dict[str, Any]: 12 | """ 13 | Generates a listen key valid for 1 hour 14 | 15 | https://bingx-api.github.io/docs/swapV2/other-interface.html#generate-listen-key 16 | """ 17 | 18 | endpoint = "/openApi/user/auth/userDataStream" 19 | 20 | response = self.__http_manager.post(endpoint) 21 | return response.json() 22 | 23 | def extend_listen_key_validity_period(self, listen_key: str) -> int: 24 | """ 25 | The validity period is extended to 60 minutes after this call, and it is recommended to send a ping every 30 minutes. 26 | 27 | 200 - success, 204 - not content, 404 - not find key 28 | 29 | return: 200 if the listen key is extended successfully 30 | 31 | https://bingx-api.github.io/docs/swapV2/other-interface.html#extend-listen-key-validity-period 32 | """ 33 | 34 | endpoint = "/openApi/user/auth/userDataStream" 35 | payload = {"listenKey": listen_key} 36 | 37 | try: 38 | response = self.__http_manager.put(endpoint, payload) 39 | except ServerError as e: 40 | return e.error_code 41 | return response.status_code 42 | 43 | def delete_listen_key(self, listen_key: str) -> int: 44 | """ 45 | Delete User data flow. 46 | 47 | 200 - success, 204 - not content, 404 - not find key 48 | 49 | return: 200 if the listen key is deleted successfully 50 | 51 | https://bingx-api.github.io/docs/swapV2/other-interface.html#delete-listen-key 52 | """ 53 | 54 | endpoint = "/openApi/user/auth/userDataStream" 55 | payload = {"listenKey": listen_key} 56 | 57 | try: 58 | response = self.__http_manager.delete(endpoint, payload) 59 | except ServerError as e: 60 | return e.error_code 61 | return response.status_code -------------------------------------------------------------------------------- /bingx/perpetual/v2/perpetual.py: -------------------------------------------------------------------------------- 1 | from bingX.perpetual.v2.account import Account 2 | from bingX.perpetual.v2.market import Market 3 | from bingX.perpetual.v2.other import Other 4 | from bingX.perpetual.v2.trade import Trade 5 | 6 | 7 | class PerpetualV2: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.account = Account(api_key, secret_key) 10 | self.market = Market(api_key, secret_key) 11 | self.trade = Trade(api_key, secret_key) 12 | self.other = Other(api_key, secret_key) 13 | -------------------------------------------------------------------------------- /bingx/perpetual/v2/trade.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.perpetual.v2.types import ( 5 | ForceOrder, 6 | HistoryOrder, 7 | MarginType, 8 | Order, 9 | PositionSide, 10 | ) 11 | 12 | 13 | class Trade: 14 | def __init__(self, api_key: str, secret_key: str) -> None: 15 | self.__http_manager = _HTTPManager(api_key, secret_key) 16 | 17 | def create_order(self, order: Order) -> dict[str, Any]: 18 | """ 19 | The current account places an order on the specified symbol contract. 20 | 21 | examples: 22 | - create long: Order(symbol="DOGE-USDT", side=Side.BUY, positionSide=PositionSide.LONG, quantity=100.0) 23 | - create short: Order(symbol="DOGE-USDT", side=Side.SELL, positionSide=PositionSide.SHORT, quantity=100.0) 24 | 25 | 26 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_1-trade-order 27 | """ 28 | 29 | endpoint = "/openApi/swap/v2/trade/order" 30 | payload = order.to_dict() 31 | 32 | response = self.__http_manager.post(endpoint, payload) 33 | return response.json()["data"] 34 | 35 | def close_order(self, order: Order) -> dict[str, Any]: 36 | """ 37 | The current account closes an order on the specified symbol contract. This is custom method which is not documented in the official API. 38 | 39 | examples: 40 | - close long: Order(symbol="DOGE-USDT", side=Side.SELL, positionSide=PositionSide.LONG, quantity=100.0) 41 | - close short: Order(symbol="DOGE-USDT", side=Side.BUY, positionSide=PositionSide.SHORT, quantity=100.0) 42 | """ 43 | 44 | endpoint = "/openApi/swap/v2/trade/order" 45 | payload = order.to_dict() 46 | 47 | response = self.__http_manager.post(endpoint, payload) 48 | return response.json()["data"] 49 | 50 | def bulk_create_order(self, orders: list[Order], recvWindow: int | None = None) -> dict[str, Any]: 51 | """ 52 | The current account performs batch order operations on the specified symbol contract. 53 | 54 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_2-bulk-order 55 | """ 56 | 57 | endpoint = "/openApi/swap/v2/trade/batchOrders" 58 | payload = {"batchOrders": [order.to_dict() for order in orders]} if recvWindow is None else {"batchOrders": [order.to_dict() for order in orders], "recvWindow": recvWindow} 59 | 60 | response = self.__http_manager.post(endpoint, payload) 61 | return response.json()["data"] 62 | 63 | def close_all_positions(self, recvWindow: int | None = None) -> dict[str, Any]: 64 | """ 65 | One-click liquidation of all positions under the current account. Note that one-click liquidation is triggered by a market order. 66 | 67 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_3-one-click-close-all-positions 68 | """ 69 | 70 | endpoint = "/openApi/swap/v2/trade/closeAllPositions" 71 | payload = {} if recvWindow is None else {"recvWindow": recvWindow} 72 | 73 | response = self.__http_manager.post(endpoint, payload) 74 | return response.json()["data"] 75 | 76 | def cancel_order(self, order_id: int, symbol: str, recvWindow: int | None = None) -> dict[str, Any]: 77 | """ 78 | Cancel an order that the current account is in the current entrusted state. 79 | 80 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_4-cancel-an-order 81 | """ 82 | 83 | endpoint = "/openApi/swap/v2/trade/order" 84 | payload = {"orderId": order_id, "symbol": symbol} if recvWindow is None else {"orderId": order_id, "symbol": symbol, "recvWindow": recvWindow} 85 | 86 | response = self.__http_manager.delete(endpoint, payload) 87 | return response.json()["data"] 88 | 89 | def cancel_batch_orders(self, order_ids: list[int], symbol: str, recvWindow: int | None = None) -> dict[str, Any]: 90 | """ 91 | Batch cancellation of some of the orders whose current account is in the current entrusted state. 92 | 93 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_5-cancel-a-batch-of-orders 94 | """ 95 | 96 | endpoint = "/openApi/swap/v2/trade/batchOrders" 97 | payload = {"orderIdList": order_ids, "symbol": symbol} if recvWindow is None else {"orderIdList": order_ids, "symbol": symbol, "recvWindow": recvWindow} 98 | 99 | response = self.__http_manager.delete(endpoint, payload) 100 | return response.json()["data"] 101 | 102 | def cancel_all_orders(self, symbol: str, recvWindow: int | None = None) -> dict[str, Any]: 103 | """ 104 | Cancel all orders in the current entrusted state of the current account. 105 | 106 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_6-cancel-all-orders 107 | """ 108 | 109 | endpoint = "/openApi/swap/v2/trade/allOpenOrders" 110 | payload = {"symbol": symbol} if recvWindow is None else {"symbol": symbol, "recvWindow": recvWindow} 111 | 112 | response = self.__http_manager.delete(endpoint, payload) 113 | return response.json()["data"] 114 | 115 | def get_open_orders(self, symbol: str | None = None, recvWindow: int | None = None) -> dict[str, Any]: 116 | """ 117 | Query all orders that the user is currently entrusted with. 118 | 119 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_7-query-all-current-pending-orders 120 | """ 121 | 122 | endpoint = "/openApi/swap/v2/trade/openOrders" 123 | if symbol is None: 124 | payload = {} if recvWindow is None else {"recvWindow": recvWindow} 125 | else: 126 | payload = {"symbol": symbol} if recvWindow is None else {"symbol": symbol, "recvWindow": recvWindow} 127 | 128 | response = self.__http_manager.get(endpoint, payload) 129 | return response.json()["data"] 130 | 131 | def get_order(self, order_id: int, symbol: str, recvWindow: int | None = None) -> dict[str, Any]: 132 | """ 133 | Query order details 134 | 135 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_8-query-order 136 | """ 137 | 138 | endpoint = "/openApi/swap/v2/trade/order" 139 | payload = {"symbol": symbol, "orderId": order_id} if recvWindow is None else {"symbol": symbol, "orderId": order_id, "recvWindow": recvWindow} 140 | 141 | response = self.__http_manager.get(endpoint, payload) 142 | return response.json()["data"] 143 | 144 | def get_margin_mode(self, symbol: str, recvWindow: int | None = None) -> dict[str, Any]: 145 | """ 146 | Query the user's margin mode on the specified symbol contract: isolated or cross. 147 | 148 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_9-query-margin-mode 149 | """ 150 | 151 | endpoint = "/openApi/swap/v2/trade/marginType" 152 | payload = {"symbol": symbol} if recvWindow is None else {"symbol": symbol, "recvWindow": recvWindow} 153 | 154 | response = self.__http_manager.get(endpoint, payload) 155 | return response.json()["data"] 156 | 157 | def change_margin_mode(self, symbol: str, margin_type: MarginType, recvWindow: int | None = None) -> dict[str, Any]: 158 | """ 159 | Change the user's margin mode on the specified symbol contract: isolated margin or cross margin.] 160 | 161 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_10-switch-margin-mode 162 | """ 163 | 164 | endpoint = "/openApi/swap/v2/trade/marginType" 165 | payload = {"symbol": symbol, "marginType": margin_type.value} if recvWindow is None else {"symbol": symbol, "marginType": margin_type.value, "recvWindow": recvWindow} 166 | 167 | response = self.__http_manager.post(endpoint, payload) 168 | return response.json()["data"] 169 | 170 | def get_leverage(self, symbol: str, recvWindow: int | None = None) -> dict[str, Any]: 171 | """ 172 | Query the opening leverage of the user in the specified symbol contract. 173 | 174 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_11-query-leverage 175 | """ 176 | 177 | endpoint = "/openApi/swap/v2/trade/leverage" 178 | payload = {"symbol": symbol} if recvWindow is None else {"symbol": symbol, "recvWindow": recvWindow} 179 | 180 | response = self.__http_manager.get(endpoint, payload) 181 | return response.json()["data"] 182 | 183 | def change_leverage(self, symbol: str, position_side: PositionSide, leverage: int, recvWindow: int | None = None) -> dict[str, Any]: 184 | """ 185 | Adjust the user's opening leverage in the specified symbol contract. 186 | 187 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_12-switch-leverage 188 | """ 189 | 190 | endpoint = "/openApi/swap/v2/trade/leverage" 191 | payload = {"symbol": symbol, "side": position_side.value, "leverage": leverage} if recvWindow is None else {"symbol": symbol, "side": position_side.value, "leverage": leverage, "recvWindow": recvWindow} 192 | 193 | response = self.__http_manager.post(endpoint, payload) 194 | return response.json()["data"] 195 | 196 | def get_force_orders(self, force_order: ForceOrder) -> dict[str, Any]: 197 | """ 198 | Query the user's forced liquidation order. If "autoCloseType" is not passed, both forced liquidation orders and ADL liquidation orders will be returned. 199 | If "startTime" is not passed, only the data within 7 days before "endTime" will be returned 200 | 201 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_13-user-s-force-orders 202 | """ 203 | 204 | endpoint = "/openApi/swap/v2/trade/forceOrders" 205 | payload = force_order.to_dict() 206 | 207 | response = self.__http_manager.get(endpoint, payload) 208 | return response.json()["data"] 209 | 210 | def get_orders_history(self, history_order: HistoryOrder) -> dict[str, Any]: 211 | """ 212 | Query the user's historical orders (order status is completed or canceled). The maximum query time range shall not exceed 7 days. 213 | Query data within the last 7 days by default 214 | 215 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_14-user-s-history-orders 216 | """ 217 | 218 | endpoint = "/openApi/swap/v2/trade/allOrders" 219 | payload = history_order.to_dict() 220 | 221 | response = self.__http_manager.get(endpoint, payload) 222 | return response.json()["data"] 223 | 224 | def change_isolated_margin(self, symbol: str, amount: float, type: int, position_side: PositionSide = PositionSide.LONG, recvWindow: int | None = None) -> dict[str, Any]: 225 | """ 226 | Adjust the isolated margin funds for the positions in the isolated position mode. 227 | 228 | :param symbol: The symbol you want to trade 229 | :param amount: The amount of margin to be added or removed 230 | :param type: 1 for increase, 2 for decrease 231 | :param position_side: PositionSide = PositionSide.LONG 232 | :param recvWindow: The number of milliseconds the request is valid for 233 | 234 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_15-adjust-isolated-margin 235 | """ 236 | 237 | endpoint = "/openApi/swap/v2/trade/positionMargin" 238 | payload = {"symbol": symbol, "amount": amount, "type": type, "positionSide": position_side.value} if recvWindow is None else {"symbol": symbol, "amount": amount, "type": type, "positionSide": position_side.value, "recvWindow": recvWindow} 239 | 240 | response = self.__http_manager.post(endpoint, payload) 241 | return response.json() 242 | -------------------------------------------------------------------------------- /bingx/perpetual/v2/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum 3 | from typing import Any 4 | 5 | from bingX._helpers import DictMixin 6 | from bingX.exceptions import OrderException 7 | 8 | 9 | class MarginType(Enum): 10 | ISOLATED = "ISOLATED" 11 | CROSSED = "CROSSED" 12 | 13 | 14 | class PositionSide(Enum): 15 | LONG = "LONG" 16 | SHORT = "SHORT" 17 | 18 | 19 | class Side(Enum): 20 | BUY = "BUY" 21 | SELL = "SELL" 22 | 23 | 24 | class OrderType(Enum): 25 | LIMIT = "LIMIT" 26 | MARKET = "MARKET" 27 | STOP_MARKET = "STOP_MARKET" 28 | TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET" 29 | TRIGGER_LIMIT = "TRIGGER_LIMIT" 30 | TRIGGER_MARKET = "TRIGGER_MARKET" 31 | 32 | 33 | @dataclass 34 | class Order(DictMixin): 35 | symbol: str 36 | side: Side 37 | positionSide: PositionSide 38 | quantity: float | None = None 39 | type: OrderType = OrderType.MARKET 40 | price: float | None = None 41 | stop_price: float | None = None 42 | recv_window: int | None = None 43 | 44 | def __post_init__(self): 45 | if self.type == OrderType.LIMIT: 46 | if (self.quantity is None) or (self.price is None): 47 | raise OrderException("LIMIT order must have quantity and price") 48 | elif self.type == OrderType.MARKET: 49 | if self.quantity is None: 50 | raise OrderException("MARKET order must have quantity") 51 | elif self.type == OrderType.TRIGGER_LIMIT: 52 | if (self.quantity is None) or (self.stop_price is None) or (self.price is None): 53 | raise OrderException("TRIGGER_LIMIT order must have quantity, stop_price and price") 54 | elif self.type in [OrderType.STOP_MARKET, OrderType.TAKE_PROFIT_MARKET, OrderType.TRIGGER_MARKET]: 55 | if (self.quantity is None) or (self.stop_price is None): 56 | raise OrderException("STOP_MARKET, TAKE_PROFIT_MARKET and TRIGGER_MARKET orders must have quantity and stop_price") 57 | 58 | 59 | class IncomeType(Enum): 60 | TRANSFER = "TRANSFER" 61 | REALIZED_PNL = "REALIZED_PNL" 62 | FUNDING_FEE = "FUNDING_FEE" 63 | TRADING_FEE = "TRADING_FEE" 64 | INSURANCE_CLEAR = "INSURANCE_CLEAR" 65 | TRIAL_FUND = "TRIAL_FUND" 66 | ADL = "ADL" 67 | SYSTEM_DEDUCTION = "SYSTEM_DEDUCTION" 68 | 69 | 70 | @dataclass 71 | class ProfitLossFundFlow(DictMixin): 72 | symbol: str | None = None 73 | income_type: IncomeType | None = None 74 | start_time: int | None = None 75 | end_time: int | None = None 76 | limit: int = 100 77 | recv_window: int | None = None 78 | 79 | 80 | @dataclass 81 | class ForceOrder(DictMixin): 82 | symbol: str | None = None 83 | auto_close_type: str | None = None 84 | start_time: int | None = None 85 | end_time: int | None = None 86 | limit: int = 50 87 | recv_window: int | None = None 88 | 89 | 90 | @dataclass 91 | class HistoryOrder(DictMixin): 92 | symbol: str 93 | order_id: int | None = None 94 | start_time: int | None = None 95 | end_time: int | None = None 96 | limit: int = 500 97 | recv_window: int | None = None 98 | -------------------------------------------------------------------------------- /bingx/spot/__init__.py: -------------------------------------------------------------------------------- 1 | from bingX.spot.spot import Spot 2 | -------------------------------------------------------------------------------- /bingx/spot/market.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.spot.types import HistoryOrder, Order 5 | 6 | 7 | class Market: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def get_symbols(self, symbol: str | None = None) -> dict[str, Any]: 12 | """ 13 | Get the list of symbols and their details 14 | 15 | :param symbol: The symbol of the trading pair 16 | :return: A dictionary of symbols and their associated information. 17 | 18 | https://bingx-api.github.io/docs/spot/market-interface.html#query-symbols 19 | """ 20 | 21 | endpoint = "/openApi/spot/v1/common/symbols" 22 | payload = {} if symbol is None else {"symbol": symbol} 23 | 24 | response = self.__http_manager.get(endpoint, payload) 25 | return response.json()["data"] 26 | 27 | def get_transaction_records(self, symbol: str, limit: int = 100) -> list[dict[str, Any]]: 28 | """ 29 | Get the transaction records of a symbol 30 | 31 | :param symbol: The symbol of the trading pair 32 | :param limit: The number of transaction records to return. Default 100, max 100 33 | 34 | https://bingx-api.github.io/docs/spot/market-interface.html#query-transaction-records 35 | """ 36 | 37 | endpoint = "/openApi/spot/v1/market/trades" 38 | payload = {"symbol": symbol, "limit": limit} 39 | 40 | response = self.__http_manager.get(endpoint, payload) 41 | return response.json()["data"] 42 | 43 | def get_depth_details(self, symbol: str, limit: int = 20) -> dict[str, Any]: 44 | """ 45 | Get the depth details for a given symbol 46 | 47 | :param symbol: The symbol of the trading pair 48 | :param limit: The number of transaction records to return. Default 20, max 100 49 | 50 | https://bingx-api.github.io/docs/spot/market-interface.html#query-depth-information 51 | """ 52 | 53 | endpoint = "/openApi/spot/v1/market/depth" 54 | payload = {"symbol": symbol, "limit": limit} 55 | 56 | response = self.__http_manager.get(endpoint, payload) 57 | return response.json()["data"] 58 | 59 | def get_k_line_data(self, symbol: str, interval: str, start_time: int | None = None, end_time: int | None = None, limit: int = 1) -> list[dict[str, Any]] | dict[str, Any]: 60 | """ 61 | Get the latest Kline Data. 62 | If startTime and endTime are not sent, the latest k-line data will be returned by default 63 | 64 | :param symbol: The trading pair you want to get the Kline data for 65 | :param interval: The interval of the Kline data, possible values: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 1w, 1M 66 | :param start_time: The start time of the Kline data, in milliseconds 67 | :param end_time: The end time of the Kline data, in milliseconds 68 | :param limit: The number of Kline data to return, maximum 1440 69 | 70 | https://bingx-api.github.io/docs/#/en-us/spot/market-api.html#Candlestick%20chart%20data 71 | """ 72 | VALID_INTERVALS = ["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"] 73 | if interval not in VALID_INTERVALS: 74 | raise ValueError("[!] INVALID INTERVAL VALUE. Valid Intervals are: ", str(VALID_INTERVALS)) 75 | 76 | endpoint = "/openApi/spot/v2/market/kline" 77 | payload = {"symbol": symbol.upper(), "interval": interval, "limit": limit} if start_time is None or end_time is None else {"symbol": symbol.upper(), "interval": interval, "startTime": start_time, "endTime": end_time, "limit": limit} 78 | 79 | response = self.__http_manager.get(endpoint, payload) 80 | return response.json()["data"] 81 | -------------------------------------------------------------------------------- /bingx/spot/other.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.exceptions import ServerError 5 | 6 | 7 | class Other: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def generate_listen_key(self) -> dict[str, Any]: 12 | """ 13 | Generates a listen key valid for 1 hour 14 | 15 | https://bingx-api.github.io/docs/swapV2/other-interface.html#generate-listen-key 16 | """ 17 | 18 | endpoint = "/openApi/user/auth/userDataStream" 19 | 20 | response = self.__http_manager.post(endpoint) 21 | return response.json() 22 | 23 | def extend_listen_key_validity_period(self, listen_key: str) -> int: 24 | """ 25 | The validity period is extended to 60 minutes after this call, and it is recommended to send a ping every 30 minutes. 26 | 27 | 200 - success, 204 - not content, 404 - not find key 28 | 29 | return: 200 if the listen key is extended successfully 30 | 31 | https://bingx-api.github.io/docs/swapV2/other-interface.html#extend-listen-key-validity-period 32 | """ 33 | 34 | endpoint = "/openApi/user/auth/userDataStream" 35 | payload = {"listenKey": listen_key} 36 | 37 | try: 38 | response = self.__http_manager.put(endpoint, payload) 39 | except ServerError as e: 40 | return e.error_code 41 | return response.status_code 42 | 43 | def delete_listen_key(self, listen_key: str) -> int: 44 | """ 45 | Delete User data flow. 46 | 47 | 200 - success, 204 - not content, 404 - not find key 48 | 49 | return: 200 if the listen key is deleted successfully 50 | 51 | https://bingx-api.github.io/docs/swapV2/other-interface.html#delete-listen-key 52 | """ 53 | 54 | endpoint = "/openApi/user/auth/userDataStream" 55 | payload = {"listenKey": listen_key} 56 | 57 | try: 58 | response = self.__http_manager.delete(endpoint, payload) 59 | except ServerError as e: 60 | return e.error_code 61 | return response.status_code 62 | -------------------------------------------------------------------------------- /bingx/spot/spot.py: -------------------------------------------------------------------------------- 1 | from bingX.spot.market import Market 2 | from bingX.spot.trade import Trade 3 | from bingX.spot.transfer import Transfer 4 | 5 | 6 | class Spot: 7 | def __init__(self, api_key: str, secret_key: str) -> None: 8 | self.trade = Trade(api_key, secret_key) 9 | self.market = Market(api_key, secret_key) 10 | self.transfer = Transfer(api_key, secret_key) -------------------------------------------------------------------------------- /bingx/spot/trade.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.spot.types import HistoryOrder, Order 5 | 6 | 7 | class Trade: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def create_order(self, order: Order) -> dict[str, Any]: 12 | """ 13 | The current account places an order on the specified symbol contract. For limit orders, price is required. 14 | For limit orders, either quantity or quoteOrderQty is required. When two parameters are passed at the same time, the server uses the parameter quantity first. 15 | For buy-side market orders, quoteOrderQty is required. For sell-side market orders, quantity is required. 16 | Orders created by the interface will not be displayed on the APP and web pages. 17 | 18 | https://bingx-api.github.io/docs/swapV2/trade-api.html#_1-trade-order 19 | """ 20 | 21 | endpoint = "/openApi/spot/v1/trade/order" 22 | payload = order.to_dict() 23 | 24 | response = self.__http_manager.post(endpoint, payload) 25 | return response.json()["data"] 26 | 27 | def cancel_order(self, order_id: int, symbol: str, recv_window: int | None = None) -> dict[str, Any]: 28 | """ 29 | Cancel an order that the current account is in the current entrusted state. 30 | 31 | https://bingx-api.github.io/docs/spot/trade-interface.html#cancel-an-order 32 | """ 33 | 34 | endpoint = "/openApi/spot/v1/trade/cancel" 35 | payload = {"symbol": symbol, "orderId": order_id} if recv_window is None else {"orderId": order_id, "symbol": symbol, "recvWindow": recv_window} 36 | 37 | response = self.__http_manager.post(endpoint, payload) 38 | return response.json()["data"] 39 | 40 | def get_order(self, order_id: int, symbol: str, recv_window: int | None = None) -> dict[str, Any]: 41 | """ 42 | Query order details 43 | 44 | https://bingx-api.github.io/docs/spot/trade-interface.html#query-orders 45 | """ 46 | 47 | endpoint = "/openApi/spot/v1/trade/query" 48 | payload = {"symbol": symbol, "orderId": order_id} if recv_window is None else {"symbol": symbol, "orderId": order_id, "recvWindow": recv_window} 49 | 50 | response = self.__http_manager.get(endpoint, payload) 51 | return response.json()["data"] 52 | 53 | def get_open_orders(self, symbol: str | None = None, recv_window: int | None = None) -> dict[str, Any]: 54 | """ 55 | Query all orders that the user is currently entrusted with. 56 | 57 | https://bingx-api.github.io/docs/spot/trade-interface.html#query-open-orders 58 | """ 59 | 60 | endpoint = "/openApi/spot/v1/trade/openOrders" 61 | payload = {"symbol": symbol} if recv_window is None else {"symbol": symbol, "recvWindow": recv_window} 62 | 63 | response = self.__http_manager.get(endpoint, payload) 64 | return response.json()["data"] 65 | 66 | def get_orders_history(self, history_order: HistoryOrder) -> dict[str, Any]: 67 | """ 68 | Query the user's historical orders. If orderId is set, orders >= orderId. Otherwise, the most recent orders will be returned. 69 | If startTime and endTime are provided, orderId is not required. 70 | 71 | https://bingx-api.github.io/docs/spot/trade-interface.html#query-order-history 72 | """ 73 | 74 | endpoint = "/openApi/spot/v1/trade/historyOrders" 75 | payload = history_order.to_dict() 76 | 77 | response = self.__http_manager.get(endpoint, payload) 78 | return response.json()["data"] 79 | 80 | def get_assets(self, recv_window: int | None = None) -> dict[str, Any]: 81 | """ 82 | Query the user's asset information. 83 | 84 | https://bingx-api.github.io/docs/spot/trade-interface.html#query-assets 85 | """ 86 | 87 | endpoint = "/openApi/spot/v1/account/balance" 88 | payload = {} if recv_window is None else {"recvWindow": recv_window} 89 | 90 | response = self.__http_manager.get(endpoint, payload) 91 | return response.json()["data"] 92 | -------------------------------------------------------------------------------- /bingx/spot/transfer.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.spot.types import ( 5 | HistoryDeposit, 6 | HistoryTransfer, 7 | HistoryWithdraw, 8 | UniversalTransfer, 9 | ) 10 | 11 | 12 | class Transfer: 13 | def __init__(self, api_key: str, secret_key: str) -> None: 14 | self.__http_manager = _HTTPManager(api_key, secret_key) 15 | 16 | def universal_transfer(self, transfer: UniversalTransfer) -> dict[str, Any]: 17 | """ 18 | 19 | https://bingx-api.github.io/docs/spot/user-interface.html#user-universal-transfer 20 | """ 21 | 22 | endpoint = "/openApi/api/v3/asset/transfer" 23 | payload = transfer.to_dict() 24 | 25 | response = self.__http_manager.post(endpoint, payload) 26 | return response.json() 27 | 28 | def get_universal_transfer_history(self, history_transfer: HistoryTransfer) -> dict[str, Any]: 29 | """ 30 | 31 | https://bingx-api.github.io/docs/spot/user-interface.html#query-user-universal-transfer-history-user-data 32 | """ 33 | 34 | endpoint = "/openApi/api/v3/asset/transfer" 35 | payload = history_transfer.to_dict() 36 | 37 | response = self.__http_manager.get(endpoint, payload) 38 | return response.json() 39 | 40 | def get_deposit_history(self, deposit_history: HistoryDeposit) -> list[dict[str, Any]]: 41 | """ 42 | 43 | https://bingx-api.github.io/docs/spot/user-interface.html#deposit-history-supporting-network 44 | """ 45 | 46 | endpoint = "/openApi/api/v3/capital/deposit/hisrec" 47 | payload = deposit_history.to_dict() 48 | 49 | response = self.__http_manager.get(endpoint, payload) 50 | return response.json() 51 | 52 | def get_withdraw_history(self, withdraw_history: HistoryWithdraw) -> list[dict[str, Any]]: 53 | """ 54 | 55 | https://bingx-api.github.io/docs/spot/user-interface.html#withdraw-history-supporting-network 56 | """ 57 | 58 | endpoint = "/openApi/api/v3/capital/withdraw/history" 59 | payload = withdraw_history.to_dict() 60 | 61 | response = self.__http_manager.get(endpoint, payload) 62 | return response.json() 63 | -------------------------------------------------------------------------------- /bingx/spot/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict, dataclass 2 | from enum import Enum 3 | from typing import Any 4 | 5 | from bingX._helpers import DictMixin 6 | from bingX.exceptions import HistoryOrderException, OrderException 7 | 8 | 9 | class TransferType(Enum): 10 | FUND_SFUTURES = "FUND_SFUTURES" 11 | SFUTURES_FUND = "SFUTURES_FUND" 12 | FUND_PFUTURES = "FUND_PFUTURES" 13 | PFUTURES_FUND = "PFUTURES_FUND" 14 | SFUTURES_PFUTURES = "SFUTURES_PFUTURES" 15 | PFUTURES_SFUTURES = "PFUTURES_SFUTURES" 16 | 17 | 18 | class Side(Enum): 19 | BUY = "BUY" 20 | SELL = "SELL" 21 | 22 | 23 | class OrderType(Enum): 24 | LIMIT = "LIMIT" 25 | MARKET = "MARKET" 26 | 27 | 28 | class TimeInForce(Enum): 29 | IOC = "IOC" 30 | POC = "POC" 31 | 32 | 33 | @dataclass 34 | class Order(DictMixin): 35 | symbol: str 36 | side: Side 37 | type: OrderType 38 | type_in_force: TimeInForce | None = None 39 | quantity: float | None = None 40 | quote_order_qty: float | None = None 41 | price: float | None = None 42 | recvWindow: int | None = None 43 | 44 | def __post_init__(self): 45 | if self.type == OrderType.LIMIT: 46 | if (self.quantity is None) or (self.price is None): 47 | raise OrderException("LIMIT order must have quantity/quote_order_qty and price") 48 | elif self.type == OrderType.MARKET: 49 | if self.side == Side.BUY: 50 | if self.quote_order_qty is None: 51 | raise OrderException("BUY MARKET order must have quote_order_qty") 52 | elif self.side == Side.SELL: 53 | if self.quantity is None: 54 | raise OrderException("SELL MARKET order must have quantity") 55 | 56 | 57 | @dataclass 58 | class HistoryOrder(DictMixin): 59 | symbol: str 60 | order_id: int | None = None 61 | start_time: int | None = None 62 | end_time: int | None = None 63 | page_index: int | None = None 64 | page_size: int | None = None 65 | recv_window: int | None = None 66 | 67 | def __post_init__(self): 68 | if self.page_index is not None and self.page_index > 0: 69 | raise(HistoryOrderException("page_index must be greater than 0")) 70 | if self.page_size is not None and self.page_size <= 100: 71 | raise(HistoryOrderException("page_size must be less or equal to 100")) 72 | 73 | 74 | @dataclass 75 | class UniversalTransfer(DictMixin): 76 | type: TransferType 77 | asset: str | None = None 78 | amount: float | None = None 79 | recv_window: int | None = None 80 | 81 | 82 | @dataclass 83 | class HistoryTransfer(DictMixin): 84 | type: TransferType 85 | start_time: int | None = None 86 | end_time: int | None = None 87 | current: int | None = None 88 | size: int | None = None 89 | recv_window: int | None = None 90 | 91 | 92 | @dataclass 93 | class HistoryDeposit(DictMixin): 94 | coin: str | None = None 95 | status: int | None = None 96 | start_time: int | None = None 97 | end_time: int | None = None 98 | offset: int = 0 99 | limit: int = 1000 100 | recv_window: int | None = None 101 | 102 | def __post_init__(self): 103 | if self.offset is not None and self.offset < 0: 104 | raise(HistoryOrderException("offset must be greater than 0")) 105 | 106 | 107 | @dataclass 108 | class HistoryWithdraw(DictMixin): 109 | coin: str | None = None 110 | withdraw_order_id: str | None = None 111 | status: int | None = None 112 | start_time: int | None = None 113 | end_time: int | None = None 114 | offset: int = 0 115 | limit: int = 1000 116 | recv_window: int | None = None 117 | 118 | def __post_init__(self): 119 | if self.offset is not None and self.offset < 0: 120 | raise(HistoryOrderException("offset must be greater than 0")) 121 | -------------------------------------------------------------------------------- /bingx/standard/__init__.py: -------------------------------------------------------------------------------- 1 | from bingX.standard.standard import Standard 2 | -------------------------------------------------------------------------------- /bingx/standard/standard.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from bingX._http_manager import _HTTPManager 4 | from bingX.perpetual.v2.types import HistoryOrder 5 | 6 | 7 | class Standard: 8 | def __init__(self, api_key: str, secret_key: str) -> None: 9 | self.__http_manager = _HTTPManager(api_key, secret_key) 10 | 11 | def get_all_positions(self) -> list[dict[str, Any]]: 12 | """ 13 | 14 | https://bingx-api.github.io/docs/standard/contract-interface.html#position 15 | """ 16 | 17 | endpoint = "/openApi/contract/v1/allPosition" 18 | 19 | response = self.__http_manager.get(endpoint) 20 | return response.json()["data"] 21 | 22 | def get_orders_history(self, order: HistoryOrder) -> list[dict[str, Any]]: 23 | """ 24 | 25 | https://bingx-api.github.io/docs/standard/contract-interface.html#historical-order 26 | """ 27 | 28 | endpoint = "/openApi/contract/v1/allOrders" 29 | payload = order.to_dict() 30 | 31 | response = self.__http_manager.get(endpoint, payload) 32 | return response.json()["data"] 33 | 34 | def get_account_details(self) -> list[dict[str, Any]]: 35 | """ 36 | 37 | https://bingx-api.github.io/docs/standard/contract-interface.html#query-standard-contract-balance 38 | """ 39 | 40 | endpoint = "/openApi/contract/v1/balance" 41 | 42 | response = self.__http_manager.get(endpoint) 43 | return response.json()["data"] 44 | -------------------------------------------------------------------------------- /examples/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niewiemczego/python-bingx/d0a511e21423ae75230ed571d0900ea217ef682f/examples/.gitkeep -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | about = {} 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | with open(os.path.join(here, "bingX", "__version__.py"), "r") as f: 8 | exec(f.read(), about) 9 | 10 | with open("README.md", "r") as fh: 11 | long_description = fh.read() 12 | 13 | setup( 14 | name=about["__title__"], 15 | version=about["__version__"], 16 | author=about["__author__"], 17 | description=about["__description__"], 18 | license=about["__license__"], 19 | packages=find_packages(), 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | url='https://github.com/niewiemczego/python-bingx', 23 | install_requires=[ 24 | 'requests', 'websockets' 25 | ], 26 | keywords='bingx exchange rest api bitcoin ethereum btc eth', 27 | classifiers=[ 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.9', 33 | 'Programming Language :: Python :: 3.10', 34 | 'Programming Language :: Python :: 3.11', 35 | 'Programming Language :: Python', 36 | 'Topic :: Software Development :: Libraries :: Python Modules', 37 | ], 38 | ) -------------------------------------------------------------------------------- /tests/perpetual/v2/test_account.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX.exceptions import ClientError 7 | from bingX.perpetual.v2.account import Account 8 | from bingX.perpetual.v2.types import IncomeType, ProfitLossFundFlow 9 | 10 | load_dotenv() 11 | 12 | class TestAccount: 13 | @pytest.fixture 14 | def account(self) -> Account: 15 | api_key = os.getenv('API_KEY') 16 | secret_key = os.getenv('SECRET_KEY') 17 | return Account(api_key, secret_key) 18 | 19 | def test_get_details_valid(self, account: Account): 20 | response = account.get_details() 21 | assert isinstance(response, dict) 22 | 23 | def test_get_swap_positions_valid(self, account: Account): 24 | response = account.get_swap_positions() 25 | assert isinstance(response, list) 26 | 27 | def test_get_swap_positions_valid_symbol(self, account: Account): 28 | response = account.get_swap_positions("BTC-USDT") 29 | assert isinstance(response, dict) 30 | 31 | def test_get_swap_positions_valid_symbol(self, account: Account): 32 | with pytest.raises(ClientError): 33 | account.get_swap_positions("BTCUSDT") 34 | 35 | def test_get_profit_loss_fund_flow_valid(self, account: Account): 36 | response = account.get_profit_loss_fund_flow(ProfitLossFundFlow()) 37 | assert isinstance(response, list) 38 | 39 | def test_get_profit_loss_fund_flow_valid_symbol(self, account: Account): 40 | profit_loss_fund_flow = ProfitLossFundFlow("BTC-USDT") 41 | response = account.get_profit_loss_fund_flow(profit_loss_fund_flow) 42 | assert isinstance(response, list) 43 | 44 | def test_get_profit_loss_fund_flow_invalid_symbol(self, account: Account): 45 | profit_loss_fund_flow = ProfitLossFundFlow("BTCUSDT") 46 | response = account.get_profit_loss_fund_flow(profit_loss_fund_flow) 47 | assert response is None 48 | 49 | def test_get_profit_loss_fund_flow_valid_income_type(self, account: Account): 50 | profit_loss_fund_flow = ProfitLossFundFlow("BTC-USDT", IncomeType.FUNDING_FEE) 51 | response = account.get_profit_loss_fund_flow(profit_loss_fund_flow) 52 | assert isinstance(response, list) 53 | 54 | def test_get_profit_loss_fund_flow_invalid_income_type(self): 55 | with pytest.raises(AttributeError): 56 | ProfitLossFundFlow("BTC-USDT", IncomeType.INVALID) 57 | 58 | def test_get_profit_loss_fund_flow_invalid_limit(self, account: Account): 59 | with pytest.raises(ClientError): 60 | profit_loss_fund_flow = ProfitLossFundFlow("BTC-USDT", IncomeType.FUNDING_FEE, limit=123456) 61 | account.get_profit_loss_fund_flow(profit_loss_fund_flow) 62 | -------------------------------------------------------------------------------- /tests/perpetual/v2/test_market.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX.exceptions import ClientError 7 | from bingX.perpetual.v2.market import Market 8 | 9 | load_dotenv() 10 | 11 | class TestMarket: 12 | @pytest.fixture 13 | def market(self) -> Market: 14 | api_key = os.getenv('API_KEY') 15 | secret_key = os.getenv('SECRET_KEY') 16 | return Market(api_key, secret_key) 17 | 18 | def test_get_contract_info(self, market: Market): 19 | response = market.get_contract_info() 20 | assert isinstance(response, list) 21 | 22 | def test_get_latest_price_of_trading_pair_valid(self, market: Market): 23 | response = market.get_latest_price_of_trading_pair() 24 | assert isinstance(response, list) 25 | 26 | def test_get_latest_price_of_trading_pair_valid_symbol(self, market: Market): 27 | response = market.get_latest_price_of_trading_pair("BTC-USDT") 28 | assert isinstance(response, dict) 29 | 30 | def test_get_latest_price_of_trading_pair_invalid_symbol(self, market: Market): 31 | with pytest.raises(ClientError): 32 | market.get_latest_price_of_trading_pair("BTCUSDT") 33 | 34 | def test_get_market_depth_valid_symbol(self, market: Market): 35 | response = market.get_market_depth("BTC-USDT") 36 | assert isinstance(response, dict) 37 | 38 | def test_get_market_depth_invalid_symbol(self, market: Market): 39 | with pytest.raises(ClientError): 40 | market.get_market_depth("BTCUSDT") 41 | 42 | def test_get_market_depth_invalid_limit(self, market: Market): 43 | with pytest.raises(ClientError): 44 | market.get_market_depth("BTC-USDT", 123456) 45 | 46 | def test_get_latest_trade_of_trading_pair_valid_symbol(self, market: Market): 47 | response = market.get_latest_trade_of_trading_pair("BTC-USDT") 48 | assert isinstance(response, list) 49 | 50 | def test_get_latest_trade_of_trading_pair_invalid_symbol(self, market: Market): 51 | with pytest.raises(ClientError): 52 | market.get_latest_trade_of_trading_pair("BTCUSDT") 53 | 54 | def test_get_latest_trade_of_trading_pair_invalid_limit(self, market: Market): 55 | with pytest.raises(ClientError): 56 | market.get_latest_trade_of_trading_pair("BTC-USDT", 123456) 57 | 58 | def test_get_current_funding_rate_valid(self, market: Market): 59 | response = market.get_current_funding_rate() 60 | assert isinstance(response, list) 61 | 62 | def test_get_current_funding_rate_valid_symbol(self, market: Market): 63 | response = market.get_current_funding_rate("BTC-USDT") 64 | assert isinstance(response, dict) 65 | 66 | def test_get_current_funding_rate_invalid_symbol(self, market: Market): 67 | with pytest.raises(ClientError): 68 | market.get_current_funding_rate("BTCUSDT") 69 | 70 | #TODO: add more test for this functions for args: start_time, end_time 71 | def test_get_funding_rate_history_valid_symbol(self, market: Market): 72 | response = market.get_funding_rate_history("BTC-USDT") 73 | assert isinstance(response, list) 74 | 75 | def test_get_funding_rate_history_invalid_symbol(self, market: Market): 76 | with pytest.raises(ClientError): 77 | market.get_funding_rate_history("BTCUSDT") 78 | 79 | def test_get_funding_rate_history_invalid_limit(self, market: Market): 80 | with pytest.raises(ClientError): 81 | market.get_funding_rate_history("BTCUSDT", limit=123456) 82 | 83 | #TODO: add more test for this functions for args: start_time, end_time 84 | def test_get_k_line_data_valid(self, market: Market): 85 | response = market.get_k_line_data("BTC-USDT", "1m") 86 | assert isinstance(response, dict) 87 | 88 | def test_get_k_line_data_invalid_symbol(self, market: Market): 89 | with pytest.raises(ClientError): 90 | market.get_k_line_data("BTCUSDT", "1m") 91 | 92 | def test_get_k_line_data_invalid_interval(self, market: Market): 93 | with pytest.raises(ClientError): 94 | market.get_k_line_data("BTC-USDT", "1Y") 95 | 96 | def test_get_k_line_data_invalid_limit(self, market: Market): 97 | with pytest.raises(ClientError): 98 | market.get_k_line_data("BTC-USDT", "1m", limit=123456) 99 | 100 | def test_get_swap_open_positions_valid(self, market: Market): 101 | response = market.get_swap_open_positions("BTC-USDT") 102 | assert isinstance(response, dict) 103 | 104 | def test_get_swap_open_positions_invalid(self, market: Market): 105 | with pytest.raises(ClientError): 106 | market.get_swap_open_positions("BTCUSDT") 107 | 108 | def test_get_ticker_valid(self, market: Market): 109 | response = market.get_ticker() 110 | assert isinstance(response, list) 111 | 112 | def test_get_ticker_valid_symbol(self, market: Market): 113 | response = market.get_ticker("BTC-USDT") 114 | assert isinstance(response, dict) 115 | 116 | def test_get_ticker_invalid(self, market: Market): 117 | with pytest.raises(ClientError): 118 | market.get_ticker("BTCUSDT") 119 | -------------------------------------------------------------------------------- /tests/perpetual/v2/test_other.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX.exceptions import ClientError 7 | from bingX.perpetual.v2.other import Other 8 | 9 | load_dotenv() 10 | 11 | class TestTrade: 12 | @pytest.fixture 13 | def other(self) -> Other: 14 | api_key = os.getenv('API_KEY') 15 | secret_key = os.getenv('SECRET_KEY') 16 | return Other(api_key, secret_key) 17 | 18 | def test_generate_listen_key_valid(self, other: Other): 19 | response = other.generate_listen_key() 20 | assert isinstance(response, dict) 21 | assert isinstance(response.get("listenKey"), str) 22 | 23 | def test_extend_listen_key_validity_period_valid(self, other: Other): 24 | response = other.generate_listen_key() 25 | listen_key = response.get("listenKey") 26 | assert isinstance(listen_key, str) 27 | response = other.extend_listen_key_validity_period(listen_key) 28 | assert response == 200 29 | 30 | def test_extend_listen_key_validity_period_invalid(self, other: Other): 31 | fake_listen_key = "a8ea75681542e66f1a50a1616dd06ed77dab61baa0c296bca03a9b13ee5f2dd7" 32 | response = other.extend_listen_key_validity_period(fake_listen_key) 33 | assert response == 404 34 | 35 | def test_delete_listen_key_valid(self, other: Other): 36 | response = other.generate_listen_key() 37 | listen_key = response.get("listenKey") 38 | response = other.delete_listen_key(listen_key) 39 | assert response == 200 40 | -------------------------------------------------------------------------------- /tests/perpetual/v2/test_trade.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX.exceptions import ClientError 7 | from bingX.perpetual.v2.trade import Trade 8 | from bingX.perpetual.v2.types import ( 9 | ForceOrder, 10 | HistoryOrder, 11 | MarginType, 12 | Order, 13 | OrderType, 14 | PositionSide, 15 | Side, 16 | ) 17 | 18 | load_dotenv() 19 | 20 | class TestTrade: 21 | @pytest.fixture 22 | def trade(self) -> Trade: 23 | api_key = os.getenv('API_KEY') 24 | secret_key = os.getenv('SECRET_KEY') 25 | return Trade(api_key, secret_key) 26 | 27 | # def test_create_order(self, trade: Trade): 28 | # order = Order("DOGEUSDT", OrderType.MARKET, Side.BUY, PositionSide.LONG, quantity=10.0) 29 | # response = trade.create_order(order) 30 | # assert isinstance(response, dict) 31 | # assert response["data"]["order"]["symbol"] == "DOGEUSDT" 32 | # assert response["data"]["order"]["orderType"] == "MARKET" 33 | # assert response["data"]["order"]["side"] == "BUY" 34 | # assert response["data"]["order"]["positionSide"] == "LONG" -------------------------------------------------------------------------------- /tests/spot/test_market.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | import pytest 5 | from dotenv import load_dotenv 6 | 7 | from bingX.exceptions import ClientError 8 | from bingX.spot.market import Market 9 | 10 | load_dotenv() 11 | 12 | class TestMarket: 13 | @pytest.fixture 14 | def market(self) -> Market: 15 | api_key = os.getenv('API_KEY') 16 | secret_key = os.getenv('SECRET_KEY') 17 | return Market(api_key, secret_key) 18 | 19 | def test_get_symbols_valid(self, market: Market): 20 | response = market.get_symbols() 21 | assert isinstance(response, dict) 22 | 23 | def test_get_symbols_valid_symbol(self, market: Market): 24 | response = market.get_symbols("BTC-USDT") 25 | assert isinstance(response, dict) 26 | 27 | def test_get_symbols_invalid_symbol(self, market: Market): 28 | with pytest.raises(ClientError): 29 | market.get_symbols("BTCUSDT") 30 | 31 | def test_get_transaction_records_valid(self, market: Market): 32 | response = market.get_transaction_records("BTC-USDT") 33 | assert isinstance(response, list) 34 | 35 | def test_get_transaction_records_invalid(self, market: Market): 36 | with pytest.raises(ClientError): 37 | market.get_transaction_records("BTCUSDT") 38 | 39 | def test_get_depth_details_valid(self, market: Market): 40 | response = market.get_depth_details("BTC-USDT") 41 | assert isinstance(response, dict) 42 | 43 | def test_get_depth_details_invalid(self, market: Market): 44 | with pytest.raises(ClientError): 45 | market.get_depth_details("BTCUSDT") -------------------------------------------------------------------------------- /tests/spot/test_other.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX.exceptions import ClientError 7 | from bingX.spot.other import Other 8 | 9 | load_dotenv() 10 | 11 | class TestTrade: 12 | @pytest.fixture 13 | def other(self) -> Other: 14 | api_key = os.getenv('API_KEY') 15 | secret_key = os.getenv('SECRET_KEY') 16 | return Other(api_key, secret_key) 17 | 18 | def test_generate_listen_key_valid(self, other: Other): 19 | response = other.generate_listen_key() 20 | assert isinstance(response, dict) 21 | assert isinstance(response.get("listenKey"), str) 22 | 23 | def test_extend_listen_key_validity_period_valid(self, other: Other): 24 | response = other.generate_listen_key() 25 | listen_key = response.get("listenKey") 26 | assert isinstance(listen_key, str) 27 | response = other.extend_listen_key_validity_period(listen_key) 28 | assert response == 200 29 | 30 | def test_extend_listen_key_validity_period_invalid(self, other: Other): 31 | fake_listen_key = "a8ea75681542e66f1a50a1616dd06ed77dab61baa0c296bca03a9b13ee5f2dd7" 32 | response = other.extend_listen_key_validity_period(fake_listen_key) 33 | assert response == 404 34 | 35 | def test_delete_listen_key_valid(self, other: Other): 36 | response = other.generate_listen_key() 37 | listen_key = response.get("listenKey") 38 | response = other.delete_listen_key(listen_key) 39 | assert response == 200 40 | -------------------------------------------------------------------------------- /tests/spot/test_transfer.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | import pytest 5 | from dotenv import load_dotenv 6 | 7 | from bingX.exceptions import ClientError 8 | from bingX.spot.transfer import Transfer 9 | from bingX.spot.types import ( 10 | HistoryDeposit, 11 | HistoryTransfer, 12 | HistoryWithdraw, 13 | TransferType, 14 | UniversalTransfer, 15 | ) 16 | 17 | load_dotenv() 18 | 19 | class TestTransfer: 20 | @pytest.fixture 21 | def transfer(self) -> Transfer: 22 | api_key = os.getenv('API_KEY') 23 | secret_key = os.getenv('SECRET_KEY') 24 | return Transfer(api_key, secret_key) 25 | 26 | def test_universal_transfer_valid_type(self, transfer: Transfer): 27 | response = transfer.universal_transfer(UniversalTransfer(TransferType.FUND_PFUTURES)) 28 | assert isinstance(response, dict) 29 | 30 | def test_universal_transfer_invalid_type(self, transfer: Transfer): 31 | with pytest.raises(AttributeError): 32 | transfer.universal_transfer(UniversalTransfer(TransferType.INVALID)) 33 | 34 | def test_get_universal_transfer_history_valid_type(self, transfer: Transfer): 35 | response = transfer.get_universal_transfer_history(HistoryTransfer(TransferType.FUND_PFUTURES)) 36 | assert isinstance(response, dict) 37 | 38 | def test_get_universal_transfer_history_invalid_type(self, transfer: Transfer): 39 | with pytest.raises(AttributeError): 40 | transfer.get_universal_transfer_history(HistoryTransfer(TransferType.INVALID)) 41 | 42 | def test_deposit_history_valid(self, transfer: Transfer): 43 | response = transfer.get_deposit_history(HistoryDeposit()) 44 | assert isinstance(response, list) 45 | 46 | def test_withdraw_history_valid(self, transfer: Transfer): 47 | response = transfer.get_withdraw_history(HistoryWithdraw()) 48 | assert isinstance(response, list) -------------------------------------------------------------------------------- /tests/standard/test_standard.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX.exceptions import ClientError 7 | from bingX.perpetual.v2.types import HistoryOrder 8 | from bingX.standard import Standard 9 | 10 | load_dotenv() 11 | 12 | class TestStandard: 13 | @pytest.fixture 14 | def standard(self) -> Standard: 15 | api_key = os.getenv('API_KEY') 16 | secret_key = os.getenv('SECRET_KEY') 17 | return Standard(api_key, secret_key) 18 | 19 | def test_get_all_positions_valid(self, standard: Standard): 20 | response = standard.get_all_positions() 21 | assert isinstance(response, list) 22 | 23 | def test_get_orders_history_valid_symbol(self, standard: Standard): 24 | response = standard.get_orders_history(HistoryOrder("BTC-USDT")) 25 | assert isinstance(response, list) 26 | 27 | def test_get_account_details(self, standard: Standard): 28 | response = standard.get_account_details() 29 | assert isinstance(response, list) -------------------------------------------------------------------------------- /tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import os 4 | 5 | import pytest 6 | from dotenv import load_dotenv 7 | 8 | from bingX._helpers import generate_hash, generate_timestamp 9 | 10 | load_dotenv() 11 | 12 | 13 | @pytest.fixture 14 | def secret_key() -> str: 15 | return os.getenv("SECRET_KEY") 16 | 17 | 18 | def test_generate_timestamp(): 19 | timestamp = generate_timestamp() 20 | assert isinstance(timestamp, int) 21 | assert timestamp > 0 22 | 23 | 24 | def test_generate_hash(secret_key: str): 25 | query_string = "example_query_string" 26 | expected_hash = hmac.new(secret_key.encode(), query_string.encode(), hashlib.sha256) 27 | result_hash = generate_hash(secret_key, query_string) 28 | 29 | assert isinstance(result_hash, hmac.HMAC) 30 | assert result_hash.digest() == expected_hash.digest() 31 | assert result_hash.hexdigest() == expected_hash.hexdigest() -------------------------------------------------------------------------------- /tests/test_http_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from bingX._http_manager import _HTTPManager 7 | from bingX.exceptions import ClientError, InvalidMethodException, ServerError 8 | 9 | load_dotenv() 10 | 11 | class TestHTTPManager: 12 | @pytest.fixture() 13 | def http_manager(self) -> _HTTPManager: 14 | api_key = os.getenv("API_KEY") 15 | secret_key = os.getenv("SECRET_KEY") 16 | return _HTTPManager(api_key, secret_key) 17 | 18 | def test_request_get_valid(self, http_manager: _HTTPManager): 19 | response = http_manager._request("GET", "/openApi/swap/v2/quote/contracts") 20 | assert isinstance(response.json(), dict) 21 | 22 | def test_request_get_invalid(self, http_manager: _HTTPManager): 23 | with pytest.raises(ClientError): 24 | http_manager._request("GET", "/openApi/swap/v2/quote/contracts1") 25 | 26 | #TODO: add test for post, put, delete 27 | 28 | def test_generate_signature(self, http_manager: _HTTPManager): 29 | query_string = "foo=bar&baz=qux" 30 | expected_signature = "402028be4bd6689f237c39366108c5d67422134d89b3a9b6e651a71599d8449e" # it has been generated with secret_key from .env 31 | assert http_manager._generate_signature(query_string) == expected_signature 32 | 33 | def test_generate_query_string(self, http_manager: _HTTPManager): 34 | payload = {"foo": "bar", "baz": "qux"} 35 | generated_query_string = http_manager._generate_query_string(payload) 36 | generated_query_string_timestamp = generated_query_string[generated_query_string.find("timestamp=") + len("timestamp="):generated_query_string.find("timestamp=") + len("timestamp=") + 13] 37 | signature = http_manager._generate_signature(f"foo=bar&baz=qux×tamp={generated_query_string_timestamp}") 38 | expected_query_string = f"foo=bar&baz=qux×tamp={generated_query_string_timestamp}&signature={signature}" 39 | assert generated_query_string == expected_query_string 40 | 41 | def test_invalid_method(self, http_manager: _HTTPManager): 42 | with pytest.raises(InvalidMethodException): 43 | http_manager._request("INVALID", "/openApi/swap/v2/trade/order") 44 | 45 | def test_server_error(self, http_manager: _HTTPManager): 46 | with pytest.raises(ServerError): 47 | http_manager._request("PUT", "/openApi/swap/v2/trade/order", payload={"test": "test"}) 48 | 49 | def test_client_error(self, http_manager: _HTTPManager): 50 | with pytest.raises(ClientError): 51 | http_manager._request("GET", "/openApi/swap/v2/trade", payload={"test": "test"}) --------------------------------------------------------------------------------