├── .DS_Store ├── .all-contributorsrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── examples ├── direct_session.py ├── http_example_explanatory.py ├── http_example_quickstart.py ├── http_example_rsa_authentication.py ├── websocket_example_explanatory.py ├── websocket_example_quickstart.py ├── websocket_example_rsa_authentication.py ├── websocket_http_example_spread_trading_quickstart.py ├── websocket_trading_example_quickstart.py └── wrapper_class.py ├── pybit ├── .vscode │ └── settings.json ├── __init__.py ├── _helpers.py ├── _http_manager.py ├── _v5_account.py ├── _v5_asset.py ├── _v5_broker.py ├── _v5_crypto_loan.py ├── _v5_earn.py ├── _v5_institutional_loan.py ├── _v5_market.py ├── _v5_misc.py ├── _v5_position.py ├── _v5_pre_upgrade.py ├── _v5_spot_leverage_token.py ├── _v5_spot_margin_trade.py ├── _v5_spread.py ├── _v5_trade.py ├── _v5_user.py ├── _websocket_stream.py ├── _websocket_trading.py ├── account.py ├── asset.py ├── broker.py ├── crypto_loan.py ├── earn.py ├── exceptions.py ├── helpers.py ├── institutional_loan.py ├── market.py ├── misc.py ├── position.py ├── pre_upgrade.py ├── spot_leverage_token.py ├── spot_margin_trade.py ├── spread.py ├── trade.py ├── unified_trading.py └── user.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_pybit.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bybit-exchange/pybit/63cb8922ccae000cae0394c094ada3c07723b371/.DS_Store -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "verata-veritatis", 10 | "name": "verata-veritatis", 11 | "avatar_url": "https://avatars0.githubusercontent.com/u/9677388?v=4", 12 | "profile": "https://github.com/verata-veritatis", 13 | "contributions": [ 14 | "code", 15 | "doc" 16 | ] 17 | }, 18 | { 19 | "login": "APF20", 20 | "name": "APF20", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/74583612?v=4", 22 | "profile": "https://github.com/APF20", 23 | "contributions": [ 24 | "code" 25 | ] 26 | } 27 | ], 28 | "contributorsPerLine": 7, 29 | "projectName": "pybit", 30 | "projectOwner": "bybit-exchange", 31 | "repoType": "github", 32 | "repoHost": "https://github.com", 33 | "skipCi": true, 34 | "commitConvention": "angular" 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # PyCharm idea files 132 | *.idea 133 | 134 | # VSCode files 135 | *.vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.7" 5 | - "3.8" 6 | - "3.9" 7 | - "3.10" 8 | cache: pip 9 | before_install: 10 | - "pip install -U pip" 11 | - "export PYTHONPATH=$PYTHONPATH:$(pwd)" 12 | install: 13 | - pip install -r requirements.txt 14 | script: 15 | - python -m unittest 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright for portions of pybit are held by verata-veritatis, 2020. Since 4 | version 2.0.0, all other copyright are held by bybit-exchange, 2022. 5 | 6 | Copyright (c) 2020 verata-veritatis 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include CHANGELOG.md 4 | recursive-include examples * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pybit 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | [![Build Status](https://img.shields.io/pypi/pyversions/pybit)](https://www.python.org/downloads/) 7 | [![Build Status](https://img.shields.io/pypi/v/pybit)](https://pypi.org/project/pybit/) 8 | ![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat) 9 | 10 | Official Python3 API connector for Bybit's HTTP and WebSockets APIs. 11 | 12 | ## Table of Contents 13 | 14 | - [About](#about) 15 | - [Development](#development) 16 | - [Installation](#installation) 17 | - [Usage](#usage) 18 | - [Contact](#contact) 19 | - [Contributors](#contributors) 20 | - [Donations](#donations) 21 | 22 | ## About 23 | Put simply, `pybit` (Python + Bybit) is the official lightweight one-stop-shop module for the Bybit HTTP and WebSocket APIs. Originally created by [Verata Veritatis](https://github.com/verata-veritatis), it's now maintained by Bybit employees – however, you're still welcome to contribute! 24 | 25 | It was designed with the following vision in mind: 26 | 27 | > I was personally never a fan of auto-generated connectors that used a mosh-pit of various modules you didn't want (sorry, `bravado`) and wanted to build my own Python3-dedicated connector with very little external resources. The goal of the connector is to provide traders and developers with an easy-to-use high-performing module that has an active issue and discussion board leading to consistent improvements. 28 | 29 | ## Development 30 | `pybit` is being actively developed, and new Bybit API changes should arrive on `pybit` very quickly. `pybit` uses `requests` and `websocket-client` for its methods, alongside other built-in modules. Anyone is welcome to branch/fork the repository and add their own upgrades. If you think you've made substantial improvements to the module, submit a pull request and we'll gladly take a look. 31 | 32 | ## Installation 33 | `pybit` requires Python 3.9.1 or higher. The module can be installed manually or via [PyPI](https://pypi.org/project/pybit/) with `pip`: 34 | ``` 35 | pip install pybit 36 | ``` 37 | 38 | ## Usage 39 | You can retrieve a specific market like so: 40 | ```python 41 | from pybit.unified_trading import HTTP 42 | ``` 43 | Create an HTTP session and connect via WebSocket for Inverse on mainnet: 44 | ```python 45 | session = HTTP( 46 | testnet=False, 47 | api_key="...", 48 | api_secret="...", 49 | ) 50 | ``` 51 | Information can be sent to, or retrieved from, the Bybit APIs: 52 | 53 | ```python 54 | # Get the orderbook of the USDT Perpetual, BTCUSDT 55 | session.get_orderbook(category="linear", symbol="BTCUSDT") 56 | 57 | # Create five long USDC Options orders. 58 | # (Currently, only USDC Options support sending orders in bulk.) 59 | payload = {"category": "option"} 60 | orders = [{ 61 | "symbol": "BTC-30JUN23-20000-C", 62 | "side": "Buy", 63 | "orderType": "Limit", 64 | "qty": "0.1", 65 | "price": i, 66 | } for i in [15000, 15500, 16000, 16500, 16600]] 67 | 68 | payload["request"] = orders 69 | # Submit the orders in bulk. 70 | session.place_batch_order(payload) 71 | ``` 72 | Check out the example python files or the list of endpoints below for more information on available 73 | endpoints and methods. Usage examples on the `HTTP` methods can 74 | be found in the [examples folder](https://github.com/bybit-exchange/pybit/tree/master/examples). 75 | 76 | 77 | ## Contact 78 | Reach out for support on your chosen platform: 79 | - [Telegram](https://t.me/BybitAPI) group chat 80 | - [Discord](https://discord.com/invite/VBwVwS2HUs) server 81 | 82 | ## Contributors 83 | 84 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |

dextertd

💻 📖

ervuks

💻 📖

verata-veritatis

💻 📖

APF20

💻

Cameron Harder-Hutton

💻

Tom Rumpf

💻

OnJohn

💻

Todd Conley

🤔

Kolya

💻
103 | 104 | 105 | 106 | 107 | 108 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 109 | -------------------------------------------------------------------------------- /examples/direct_session.py: -------------------------------------------------------------------------------- 1 | from pybit.unified_trading import HTTP 2 | 3 | 4 | BYBIT_API_KEY = "api_key" 5 | BYBIT_API_SECRET = "api_secret" 6 | TESTNET = True # True means your API keys were generated on testnet.bybit.com 7 | 8 | 9 | # Create direct HTTP session instance 10 | 11 | session = HTTP( 12 | api_key=BYBIT_API_KEY, 13 | api_secret=BYBIT_API_SECRET, 14 | testnet=TESTNET, 15 | ) 16 | 17 | # Place order 18 | 19 | response = session.place_order( 20 | category="spot", 21 | symbol="ETHUSDT", 22 | side="Sell", 23 | orderType="Market", 24 | qty="0.1", 25 | timeInForce="GTC", 26 | ) 27 | 28 | # Example to cancel orders 29 | 30 | response = session.get_open_orders( 31 | category="linear", 32 | symbol="BTCUSDT", 33 | ) 34 | 35 | orders = response["result"]["list"] 36 | 37 | for order in orders: 38 | if order["orderStatus"] == "Untriggered": 39 | session.cancel_order( 40 | category="linear", 41 | symbol=order["symbol"], 42 | orderId=order["orderId"], 43 | ) 44 | 45 | 46 | # Batch cancel orders 47 | 48 | orders_to_cancel = [ 49 | {"category": "option", "symbol": o["symbol"], "orderId": o["orderId"]} 50 | for o in response["result"]["list"] 51 | if o["orderStatus"] == "New" 52 | ] 53 | 54 | response = session.cancel_batch_order( 55 | category="option", 56 | request=orders_to_cancel, 57 | ) 58 | -------------------------------------------------------------------------------- /examples/http_example_explanatory.py: -------------------------------------------------------------------------------- 1 | """ 2 | To see which endpoints are available, check the Bybit API documentation: 3 | https://bybit-exchange.github.io/docs/v5/market/kline 4 | """ 5 | 6 | # Import HTTP from the unified_trading module. 7 | from pybit.unified_trading import HTTP 8 | 9 | # Set up logging (optional) 10 | import logging 11 | logging.basicConfig(filename="pybit.log", level=logging.DEBUG, 12 | format="%(asctime)s %(levelname)s %(message)s") 13 | 14 | 15 | # You can create an authenticated or unauthenticated HTTP session. 16 | # You can skip authentication by not passing any value for the key and secret. 17 | 18 | session = HTTP( 19 | testnet=True, 20 | api_key="...", 21 | api_secret="...", 22 | ) 23 | 24 | # Get the orderbook of the USDT Perpetual, BTCUSDT 25 | print(session.get_orderbook(category="linear", symbol="BTCUSDT")) 26 | # Note how the "category" parameter determines the type of market to fetch this 27 | # data for. Look at the docstring of the get_orderbook to navigate to the API 28 | # documentation to see the supported categories for this and other endpoints. 29 | 30 | # Get wallet balance of the Unified Trading Account 31 | print(session.get_wallet_balance(accountType="UNIFIED")) 32 | 33 | # Place an order on that USDT Perpetual 34 | print(session.place_order( 35 | category="linear", 36 | symbol="BTCUSDT", 37 | side="Buy", 38 | orderType="Market", 39 | qty="0.001", 40 | )) 41 | 42 | # Place an order on the Inverse Contract, ETHUSD 43 | print(session.place_order( 44 | category="inverse", 45 | symbol="ETHUSD", 46 | side="Buy", 47 | orderType="Market", 48 | qty="1", 49 | )) 50 | 51 | # Place an order on the Spot market, MNTUSDT 52 | print(session.place_order( 53 | category="spot", 54 | symbol="MNTUSDT", 55 | side="Buy", 56 | orderType="Market", 57 | qty="10", 58 | )) 59 | -------------------------------------------------------------------------------- /examples/http_example_quickstart.py: -------------------------------------------------------------------------------- 1 | from pybit.unified_trading import HTTP 2 | 3 | session = HTTP( 4 | testnet=True, 5 | api_key="...", 6 | api_secret="...", 7 | ) 8 | 9 | print(session.get_orderbook(category="linear", symbol="BTCUSDT")) 10 | 11 | print(session.place_order( 12 | category="linear", 13 | symbol="BTCUSDT", 14 | side="Buy", 15 | orderType="Market", 16 | qty="0.001", 17 | )) 18 | -------------------------------------------------------------------------------- /examples/http_example_rsa_authentication.py: -------------------------------------------------------------------------------- 1 | """ 2 | RSA authentication is an alternative way to create your API key. 3 | Learn about RSA authentication here: 4 | https://www.bybit.com/en-US/help-center/bybitHC_Article?id=000001923&language=en_US 5 | """ 6 | from pybit.unified_trading import HTTP 7 | 8 | # The API key is given to you by Bybit's API management page after inputting 9 | # your RSA public key. 10 | api_key = "xxxxxxx" 11 | # The API secret is your RSA generated private key. It begins with the line: 12 | # -----BEGIN PRIVATE KEY----- 13 | with open("my_rsa_private_key.pem", "r") as private_key_file: 14 | api_secret = private_key_file.read() 15 | 16 | session = HTTP( 17 | testnet=True, 18 | rsa_authentication=True, # <-- Must be True. 19 | api_key=api_key, 20 | api_secret=api_secret, 21 | log_requests=True, 22 | ) 23 | 24 | print(session.get_positions(category="linear", symbol="BTCUSDT")) 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/websocket_example_explanatory.py: -------------------------------------------------------------------------------- 1 | """ 2 | To see which WebSocket topics are available, check the Bybit API documentation: 3 | https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook 4 | """ 5 | 6 | from time import sleep 7 | 8 | # Import WebSocket from the unified_trading module. 9 | from pybit.unified_trading import WebSocket 10 | 11 | # Set up logging (optional) 12 | import logging 13 | logging.basicConfig(filename="pybit.log", level=logging.DEBUG, 14 | format="%(asctime)s %(levelname)s %(message)s") 15 | 16 | 17 | # Connect with authentication! 18 | # Here, we are connecting to the "linear" WebSocket, which will deliver public 19 | # market data for the linear (USDT) perpetuals. 20 | # The available channel types are, for public market data: 21 | # inverse – Inverse Contracts; 22 | # linear – USDT Perpetual, USDC Contracts; 23 | # spot – Spot Trading; 24 | # option – USDC Options; 25 | # and for private data: 26 | # private – Private account data for all markets. 27 | 28 | ws = WebSocket( 29 | testnet=True, 30 | channel_type="linear", 31 | ) 32 | 33 | ws_private = WebSocket( 34 | testnet=True, 35 | channel_type="private", 36 | api_key="...", 37 | api_secret="...", 38 | trace_logging=True, 39 | ) 40 | 41 | 42 | # Let's fetch the orderbook for BTCUSDT. First, we'll define a function. 43 | def handle_orderbook(message): 44 | # I will be called every time there is new orderbook data! 45 | print(message) 46 | orderbook_data = message["data"] 47 | 48 | # Now, we can subscribe to the orderbook stream and pass our arguments: 49 | # our depth, symbol, and callback function. 50 | ws.orderbook_stream(50, "BTCUSDT", handle_orderbook) 51 | 52 | 53 | # To subscribe to private data, the process is the same: 54 | def handle_position(message): 55 | # I will be called every time there is new position data! 56 | print(message) 57 | 58 | ws_private.position_stream(handle_position) 59 | 60 | 61 | while True: 62 | # This while loop is required for the program to run. You may execute 63 | # additional code for your trading logic here. 64 | sleep(1) 65 | -------------------------------------------------------------------------------- /examples/websocket_example_quickstart.py: -------------------------------------------------------------------------------- 1 | from pybit.unified_trading import WebSocket 2 | from time import sleep 3 | 4 | ws = WebSocket( 5 | testnet=True, 6 | channel_type="linear", 7 | ) 8 | 9 | def handle_message(message): 10 | print(message) 11 | 12 | ws.orderbook_stream(50, "BTCUSDT", handle_message) 13 | 14 | while True: 15 | sleep(1) 16 | -------------------------------------------------------------------------------- /examples/websocket_example_rsa_authentication.py: -------------------------------------------------------------------------------- 1 | """ 2 | RSA authentication is an alternative way to create your API key. 3 | Learn about RSA authentication here: 4 | https://www.bybit.com/en-US/help-center/bybitHC_Article?id=000001923&language=en_US 5 | """ 6 | from pybit.unified_trading import WebSocket 7 | from time import sleep 8 | 9 | # The API key is given to you by Bybit's API management page after inputting 10 | # your RSA public key. 11 | api_key = "xxxxxxxx" 12 | # The API secret is your RSA generated private key. It begins with the line: 13 | # -----BEGIN PRIVATE KEY----- 14 | with open("my_rsa_private_key.pem", "r") as private_key_file: 15 | api_secret = private_key_file.read() 16 | 17 | ws = WebSocket( 18 | testnet=True, 19 | channel_type="private", 20 | rsa_authentication=True, # <-- Must be True. 21 | api_key=api_key, 22 | api_secret=api_secret, 23 | trace_logging=True, 24 | ) 25 | 26 | def handle_message(message): 27 | print(message) 28 | 29 | ws.order_stream( 30 | callback=handle_message 31 | ) 32 | 33 | while True: 34 | sleep(1) 35 | -------------------------------------------------------------------------------- /examples/websocket_http_example_spread_trading_quickstart.py: -------------------------------------------------------------------------------- 1 | from pybit.unified_trading import WebSocket 2 | from pybit.unified_trading import WebsocketSpreadTrading 3 | from pybit.unified_trading import SpreadHTTP 4 | from time import sleep 5 | 6 | 7 | # The public websocket for spread trading 8 | ws = WebsocketSpreadTrading( 9 | testnet=True, 10 | trace_logging=True, 11 | ) 12 | 13 | # The private websocket for spread trading (same connection as normal) 14 | ws_private = WebSocket( 15 | testnet=True, 16 | trace_logging=True, 17 | channel_type="private", 18 | api_key="xxxxxxxxxxxxxxxxxx", 19 | api_secret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 20 | ) 21 | 22 | def handle_message(message): 23 | print(message) 24 | 25 | ws.orderbook_stream(25, "SOLUSDT_SOL/USDT", handle_message) 26 | ws.trade_stream("SOLUSDT_SOL/USDT", handle_message) 27 | ws.ticker_stream("SOLUSDT_SOL/USDT", handle_message) 28 | 29 | ws_private.spread_order_stream(handle_message) 30 | ws_private.spread_execution_stream(handle_message) 31 | 32 | 33 | session = SpreadHTTP( 34 | testnet=True, 35 | api_key="xxxxxxxxxxxxxxxxxx", 36 | api_secret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 37 | ) 38 | 39 | symbol = "BTCUSDT_BTC/USDT" 40 | print(session.get_instruments_info(symbol=symbol)) 41 | print(session.place_order( 42 | symbol=symbol, 43 | side="Buy", 44 | orderType="Market", 45 | qty="0.001", 46 | )) 47 | 48 | 49 | while True: 50 | sleep(1) -------------------------------------------------------------------------------- /examples/websocket_trading_example_quickstart.py: -------------------------------------------------------------------------------- 1 | from pybit.unified_trading import WebSocketTrading 2 | from time import sleep 3 | 4 | ws_trading = WebSocketTrading( 5 | testnet=True, 6 | api_key="...", 7 | api_secret="...", 8 | ) 9 | 10 | def handle_place_order_message(message): 11 | # Receive the orderId, amend the order, then cancel it. 12 | print(message) 13 | sleep(1) 14 | ws_trading.amend_order( 15 | handle_amend_order_message, 16 | category="linear", 17 | symbol="BTCUSDT", 18 | order_id=message["data"]["orderId"], 19 | qty="0.002" 20 | ) 21 | sleep(1) 22 | ws_trading.cancel_order( 23 | handle_cancel_order_message, 24 | category="linear", 25 | symbol="BTCUSDT", 26 | order_id=message["data"]["orderId"] 27 | ) 28 | 29 | def handle_amend_order_message(message): 30 | print(message) 31 | 32 | def handle_cancel_order_message(message): 33 | print(message) 34 | 35 | def handle_batch_place_order_message(message): 36 | print(message) 37 | 38 | while True: 39 | # Simplistic example; place an order every 10 seconds. 40 | ws_trading.place_order( 41 | handle_place_order_message, 42 | category="linear", 43 | symbol="BTCUSDT", 44 | side="Buy", 45 | orderType="Limit", 46 | price="60000", 47 | qty="0.001" 48 | ) 49 | sleep(1) 50 | # Batch place two orders. Note that batch amend and cancel can be used in 51 | # the same way – by passing a list of dictionaries to the request parameter. 52 | request = [ 53 | { 54 | "symbol": "BTCUSDT", 55 | "side": "Buy", 56 | "orderType": "Limit", 57 | "qty": "0.001", 58 | "price": "82000", 59 | "timeInForce": "GTC", 60 | "positionIdx": 0 61 | }, 62 | { 63 | "symbol": "BTCUSDT", 64 | "side": "Buy", 65 | "orderType": "Limit", 66 | "qty": "0.002", 67 | "price": "82005", 68 | "timeInForce": "GTC", 69 | "positionIdx": 0 70 | } 71 | ] 72 | ws_trading.place_batch_order( 73 | handle_batch_place_order_message, 74 | category="linear", 75 | request=request 76 | ) 77 | sleep(10) 78 | -------------------------------------------------------------------------------- /examples/wrapper_class.py: -------------------------------------------------------------------------------- 1 | from pybit.unified_trading import HTTP 2 | 3 | 4 | BYBIT_API_KEY = "api_key" 5 | BYBIT_API_SECRET = "api_secret" 6 | TESTNET = True # True means your API keys were generated on testnet.bybit.com 7 | 8 | 9 | class BybitWrapper: 10 | def __init__( 11 | self, 12 | api_key: str = None, 13 | api_secret: str = None, 14 | testnet: bool = None, 15 | ): 16 | self.instance = HTTP( 17 | api_key=api_key, 18 | api_secret=api_secret, 19 | testnet=testnet, 20 | log_requests=True, 21 | ) 22 | 23 | def get_max_leverage(self, category: str, symbol: str): 24 | """ 25 | Get max leverage for symbol in category 26 | """ 27 | symbols = self.instance.get_instruments_info(category=category) 28 | result = symbols["result"]["list"] 29 | return [d for d in result if d["symbol"] == symbol][0][ 30 | "leverageFilter" 31 | ]["maxLeverage"] 32 | 33 | def get_kline_data(self, symbol: str = "BTCUSDT"): 34 | kline_data = self.instance.get_kline( 35 | category="linear", 36 | symbol=symbol, 37 | interval=60, 38 | )["result"] 39 | 40 | # Getting 0 element from list 41 | data_list = kline_data["list"][0] 42 | 43 | print(f"Open price: {data_list[1]}") 44 | print(f"High price: {data_list[2]}") 45 | print(f"Low price: {data_list[3]}") 46 | print(f"Close price: {data_list[4]}") 47 | 48 | def cancel_all_orders( 49 | self, 50 | category: str = "spot", 51 | symbol: str = "ETHUSDT", 52 | ) -> dict: 53 | """ 54 | Cancel orders by category and symbol 55 | """ 56 | return self.instance.cancel_all_orders( 57 | category=category, symbol=symbol 58 | ) 59 | 60 | def get_realtime_orders( 61 | self, 62 | category: str, 63 | symbol: str = None, 64 | ) -> dict: 65 | """ 66 | Get realtime orders 67 | """ 68 | return self.instance.get_open_orders( 69 | category=category, 70 | symbol=symbol, 71 | ) 72 | 73 | def get_order_history(self, **kwargs) -> dict: 74 | return self.instance.get_order_history(**kwargs)["result"]["list"] 75 | 76 | 77 | # Initialize wrapper instance 78 | 79 | wrapper = BybitWrapper( 80 | api_key=BYBIT_API_KEY, 81 | api_secret=BYBIT_API_SECRET, 82 | testnet=TESTNET, 83 | ) 84 | 85 | # Actual usage 86 | response = wrapper.get_realtime_orders(category="linear", symbol="ETHUSDT") 87 | -------------------------------------------------------------------------------- /pybit/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.typeCheckingMode": "off", 3 | "python.formatting.provider": "black", 4 | "python.formatting.blackArgs": ["--line-length", "79"], 5 | "python.linting.enabled": true 6 | } -------------------------------------------------------------------------------- /pybit/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "5.11.0" 2 | -------------------------------------------------------------------------------- /pybit/_helpers.py: -------------------------------------------------------------------------------- 1 | import time 2 | import re 3 | import copy 4 | 5 | 6 | def generate_timestamp(): 7 | """ 8 | Return a millisecond integer timestamp. 9 | """ 10 | return int(time.time() * 10**3) 11 | 12 | 13 | def identify_ws_method(input_wss_url, wss_dictionary): 14 | """ 15 | This method matches the input_wss_url with a particular WSS method. This 16 | helps ensure that, when subscribing to a custom topic, the topic 17 | subscription message is sent down the correct WSS connection. 18 | """ 19 | path = re.compile(r"(wss://)?([^/\s]+)(.*)") 20 | input_wss_url_path = path.match(input_wss_url).group(3) 21 | for wss_url, function_call in wss_dictionary.items(): 22 | wss_url_path = path.match(wss_url).group(3) 23 | if input_wss_url_path == wss_url_path: 24 | return function_call 25 | 26 | 27 | def find_index(source, target, key): 28 | """ 29 | Find the index in source list of the targeted ID. 30 | """ 31 | return next(i for i, j in enumerate(source) if j[key] == target[key]) 32 | 33 | 34 | def make_private_args(args): 35 | """ 36 | Exists to pass on the user's arguments to a lower-level class without 37 | giving the user access to that classes attributes (ie, passing on args 38 | without inheriting the parent class). 39 | """ 40 | args.pop("self") 41 | return args 42 | 43 | 44 | def make_public_kwargs(private_kwargs): 45 | public_kwargs = copy.deepcopy(private_kwargs) 46 | public_kwargs.pop("api_key", "") 47 | public_kwargs.pop("api_secret", "") 48 | return public_kwargs 49 | 50 | 51 | def are_connections_connected(active_connections): 52 | for connection in active_connections: 53 | if not connection.is_connected(): 54 | return False 55 | return True 56 | 57 | 58 | def is_inverse_contract(symbol: str): 59 | if re.search(r"(USD)([HMUZ]\d\d|$)", symbol): 60 | return True 61 | 62 | 63 | def is_usdt_perpetual(symbol: str): 64 | if symbol.endswith("USDT"): 65 | return True 66 | 67 | 68 | def is_usdc_perpetual(symbol: str): 69 | if symbol.endswith("USDC"): 70 | return True 71 | 72 | 73 | def is_usdc_option(symbol: str): 74 | if re.search(r"[A-Z]{3}-.*-[PC]$", symbol): 75 | return True 76 | -------------------------------------------------------------------------------- /pybit/_http_manager.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from dataclasses import dataclass, field 3 | import time 4 | import hmac 5 | import hashlib 6 | from Crypto.Hash import SHA256 7 | from Crypto.PublicKey import RSA 8 | from Crypto.Signature import PKCS1_v1_5 9 | import base64 10 | import json 11 | import logging 12 | import requests 13 | 14 | from datetime import datetime as dt, timezone 15 | 16 | from .exceptions import FailedRequestError, InvalidRequestError 17 | from . import _helpers 18 | 19 | # Requests will use simplejson if available. 20 | try: 21 | from simplejson.errors import JSONDecodeError 22 | except ImportError: 23 | from json.decoder import JSONDecodeError 24 | 25 | HTTP_URL = "https://{SUBDOMAIN}.{DOMAIN}.{TLD}" 26 | SUBDOMAIN_TESTNET = "api-testnet" 27 | SUBDOMAIN_MAINNET = "api" 28 | DEMO_SUBDOMAIN_TESTNET = "api-demo-testnet" 29 | DEMO_SUBDOMAIN_MAINNET = "api-demo" 30 | DOMAIN_MAIN = "bybit" 31 | DOMAIN_ALT = "bytick" 32 | TLD_MAIN = "com" 33 | TLD_NL = "nl" 34 | TLD_HK = "com.hk" 35 | 36 | 37 | def generate_signature(use_rsa_authentication, secret, param_str): 38 | def generate_hmac(): 39 | hash = hmac.new( 40 | bytes(secret, "utf-8"), 41 | param_str.encode("utf-8"), 42 | hashlib.sha256, 43 | ) 44 | return hash.hexdigest() 45 | 46 | def generate_rsa(): 47 | hash = SHA256.new(param_str.encode("utf-8")) 48 | encoded_signature = base64.b64encode( 49 | PKCS1_v1_5.new(RSA.importKey(secret)).sign( 50 | hash 51 | ) 52 | ) 53 | return encoded_signature.decode() 54 | 55 | if not use_rsa_authentication: 56 | return generate_hmac() 57 | else: 58 | return generate_rsa() 59 | 60 | 61 | @dataclass 62 | class _V5HTTPManager: 63 | testnet: bool = field(default=False) 64 | domain: str = field(default=DOMAIN_MAIN) 65 | tld: str = field(default=TLD_MAIN) 66 | demo: bool = field(default=False) 67 | rsa_authentication: str = field(default=False) 68 | api_key: str = field(default=None) 69 | api_secret: str = field(default=None) 70 | logging_level: logging = field(default=logging.INFO) 71 | log_requests: bool = field(default=False) 72 | timeout: int = field(default=10) 73 | recv_window: bool = field(default=5000) 74 | force_retry: bool = field(default=False) 75 | retry_codes: defaultdict[dict] = field(default_factory=dict) 76 | ignore_codes: dict = field(default_factory=dict) 77 | max_retries: bool = field(default=3) 78 | retry_delay: bool = field(default=3) 79 | referral_id: bool = field(default=None) 80 | record_request_time: bool = field(default=False) 81 | return_response_headers: bool = field(default=False) 82 | 83 | def __post_init__(self): 84 | subdomain = SUBDOMAIN_TESTNET if self.testnet else SUBDOMAIN_MAINNET 85 | domain = DOMAIN_MAIN if not self.domain else self.domain 86 | if self.demo: 87 | if self.testnet: 88 | subdomain = DEMO_SUBDOMAIN_TESTNET 89 | else: 90 | subdomain = DEMO_SUBDOMAIN_MAINNET 91 | url = HTTP_URL.format(SUBDOMAIN=subdomain, DOMAIN=domain, TLD=self.tld) 92 | self.endpoint = url 93 | 94 | if not self.ignore_codes: 95 | self.ignore_codes = set() 96 | if not self.retry_codes: 97 | self.retry_codes = {10002, 10006, 30034, 30035, 130035, 130150} 98 | self.logger = logging.getLogger(__name__) 99 | if len(logging.root.handlers) == 0: 100 | # no handler on root logger set -> we add handler just for this logger to not mess with custom logic from outside 101 | handler = logging.StreamHandler() 102 | handler.setFormatter( 103 | logging.Formatter( 104 | fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 105 | datefmt="%Y-%m-%d %H:%M:%S", 106 | ) 107 | ) 108 | handler.setLevel(self.logging_level) 109 | self.logger.addHandler(handler) 110 | 111 | self.logger.debug("Initializing HTTP session.") 112 | 113 | self.client = requests.Session() 114 | self.client.headers.update( 115 | { 116 | "Content-Type": "application/json", 117 | "Accept": "application/json", 118 | } 119 | ) 120 | if self.referral_id: 121 | self.client.headers.update({"Referer": self.referral_id}) 122 | 123 | @staticmethod 124 | def prepare_payload(method, parameters): 125 | """ 126 | Prepares the request payload and validates parameter value types. 127 | """ 128 | 129 | def cast_values(): 130 | string_params = [ 131 | "qty", 132 | "price", 133 | "triggerPrice", 134 | "takeProfit", 135 | "stopLoss", 136 | ] 137 | integer_params = ["positionIdx"] 138 | for key, value in parameters.items(): 139 | if key in string_params: 140 | if type(value) != str: 141 | parameters[key] = str(value) 142 | elif key in integer_params: 143 | if type(value) != int: 144 | parameters[key] = int(value) 145 | 146 | if method == "GET": 147 | payload = "&".join( 148 | [ 149 | str(k) + "=" + str(v) 150 | for k, v in sorted(parameters.items()) 151 | if v is not None 152 | ] 153 | ) 154 | return payload 155 | else: 156 | cast_values() 157 | return json.dumps(parameters) 158 | 159 | def _auth(self, payload, recv_window, timestamp): 160 | """ 161 | Prepares authentication signature per Bybit API specifications. 162 | """ 163 | 164 | if self.api_key is None or self.api_secret is None: 165 | raise PermissionError("Authenticated endpoints require keys.") 166 | 167 | param_str = str(timestamp) + self.api_key + str(recv_window) + payload 168 | 169 | return generate_signature( 170 | self.rsa_authentication, self.api_secret, param_str 171 | ) 172 | 173 | @staticmethod 174 | def _verify_string(params, key): 175 | if key in params: 176 | if not isinstance(params[key], str): 177 | return False 178 | else: 179 | return True 180 | return True 181 | 182 | def _submit_request(self, method=None, path=None, query=None, auth=False): 183 | """ 184 | Submits the request to the API. 185 | 186 | Notes 187 | ------------------- 188 | We use the params argument for the GET method, and data argument for 189 | the POST method. Dicts passed to the data argument must be 190 | JSONified prior to submitting request. 191 | 192 | """ 193 | 194 | if query is None: 195 | query = {} 196 | 197 | # Store original recv_window. 198 | recv_window = self.recv_window 199 | 200 | # Bug fix: change floating whole numbers to integers to prevent 201 | # auth signature errors. 202 | if query is not None: 203 | for i in query.keys(): 204 | if isinstance(query[i], float) and query[i] == int(query[i]): 205 | query[i] = int(query[i]) 206 | 207 | # Remove params with None value from the request. 208 | query = {key: value for key, value in query.items() 209 | if value is not None} 210 | 211 | # Send request and return headers with body. Retry if failed. 212 | retries_attempted = self.max_retries 213 | req_params = None 214 | 215 | while True: 216 | retries_attempted -= 1 217 | if retries_attempted < 0: 218 | raise FailedRequestError( 219 | request=f"{method} {path}: {req_params}", 220 | message="Bad Request. Retries exceeded maximum.", 221 | status_code=400, 222 | time=dt.now(timezone.utc).strftime("%H:%M:%S"), 223 | resp_headers=None, 224 | ) 225 | 226 | retries_remaining = f"{retries_attempted} retries remain." 227 | 228 | req_params = self.prepare_payload(method, query) 229 | 230 | # Authenticate if we are using a private endpoint. 231 | if auth: 232 | # Prepare signature. 233 | timestamp = _helpers.generate_timestamp() 234 | signature = self._auth( 235 | payload=req_params, 236 | recv_window=recv_window, 237 | timestamp=timestamp, 238 | ) 239 | headers = { 240 | "Content-Type": "application/json", 241 | "X-BAPI-API-KEY": self.api_key, 242 | "X-BAPI-SIGN": signature, 243 | "X-BAPI-SIGN-TYPE": "2", 244 | "X-BAPI-TIMESTAMP": str(timestamp), 245 | "X-BAPI-RECV-WINDOW": str(recv_window), 246 | } 247 | else: 248 | headers = {} 249 | 250 | if method == "GET": 251 | if req_params: 252 | r = self.client.prepare_request( 253 | requests.Request( 254 | method, path + f"?{req_params}", headers=headers 255 | ) 256 | ) 257 | else: 258 | r = self.client.prepare_request( 259 | requests.Request(method, path, headers=headers) 260 | ) 261 | else: 262 | r = self.client.prepare_request( 263 | requests.Request( 264 | method, path, data=req_params, headers=headers 265 | ) 266 | ) 267 | 268 | # Log the request. 269 | if self.log_requests: 270 | if req_params: 271 | self.logger.debug( 272 | f"Request -> {method} {path}. Body: {req_params}. " 273 | f"Headers: {r.headers}" 274 | ) 275 | else: 276 | self.logger.debug( 277 | f"Request -> {method} {path}. Headers: {r.headers}" 278 | ) 279 | 280 | # Attempt the request. 281 | try: 282 | s = self.client.send(r, timeout=self.timeout) 283 | 284 | # If requests fires an error, retry. 285 | except ( 286 | requests.exceptions.ReadTimeout, 287 | requests.exceptions.SSLError, 288 | requests.exceptions.ConnectionError, 289 | ) as e: 290 | if self.force_retry: 291 | self.logger.error(f"{e}. {retries_remaining}") 292 | time.sleep(self.retry_delay) 293 | continue 294 | else: 295 | raise e 296 | 297 | # Check HTTP status code before trying to decode JSON. 298 | if s.status_code != 200: 299 | if s.status_code == 403: 300 | error_msg = "You have breached the IP rate limit or your IP is from the USA." 301 | else: 302 | error_msg = "HTTP status code is not 200." 303 | self.logger.debug(f"Response text: {s.text}") 304 | raise FailedRequestError( 305 | request=f"{method} {path}: {req_params}", 306 | message=error_msg, 307 | status_code=s.status_code, 308 | time=dt.now(timezone.utc).strftime("%H:%M:%S"), 309 | resp_headers=s.headers, 310 | ) 311 | 312 | # Convert response to dictionary, or raise if requests error. 313 | try: 314 | s_json = s.json() 315 | 316 | # If we have trouble converting, handle the error and retry. 317 | except JSONDecodeError as e: 318 | if self.force_retry: 319 | self.logger.error(f"{e}. {retries_remaining}") 320 | time.sleep(self.retry_delay) 321 | continue 322 | else: 323 | self.logger.debug(f"Response text: {s.text}") 324 | raise FailedRequestError( 325 | request=f"{method} {path}: {req_params}", 326 | message="Conflict. Could not decode JSON.", 327 | status_code=409, 328 | time=dt.now(timezone.utc).strftime("%H:%M:%S"), 329 | resp_headers=s.headers, 330 | ) 331 | 332 | ret_code = "retCode" 333 | ret_msg = "retMsg" 334 | 335 | # If Bybit returns an error, raise. 336 | if s_json[ret_code]: 337 | # Generate error message. 338 | error_msg = f"{s_json[ret_msg]} (ErrCode: {s_json[ret_code]})" 339 | 340 | # Set default retry delay. 341 | delay_time = self.retry_delay 342 | 343 | # Retry non-fatal whitelisted error requests. 344 | if s_json[ret_code] in self.retry_codes: 345 | # 10002, recv_window error; add 2.5 seconds and retry. 346 | if s_json[ret_code] == 10002: 347 | error_msg += ". Added 2.5 seconds to recv_window" 348 | recv_window += 2500 349 | 350 | # 10006, rate limit error; wait until 351 | # X-Bapi-Limit-Reset-Timestamp and retry. 352 | elif s_json[ret_code] == 10006: 353 | self.logger.error( 354 | f"{error_msg}. Hit the API rate limit. " 355 | f"Sleeping, then trying again. Request: {path}" 356 | ) 357 | 358 | # Calculate how long we need to wait in milliseconds. 359 | limit_reset_time = int(s.headers["X-Bapi-Limit-Reset-Timestamp"]) 360 | limit_reset_str = dt.fromtimestamp(limit_reset_time / 10**3).strftime( 361 | "%H:%M:%S.%f")[:-3] 362 | delay_time = (int(limit_reset_time) - _helpers.generate_timestamp()) / 10**3 363 | error_msg = ( 364 | f"API rate limit will reset at {limit_reset_str}. " 365 | f"Sleeping for {int(delay_time * 10**3)} milliseconds" 366 | ) 367 | 368 | # Log the error. 369 | self.logger.error(f"{error_msg}. {retries_remaining}") 370 | time.sleep(delay_time) 371 | continue 372 | 373 | elif s_json[ret_code] in self.ignore_codes: 374 | pass 375 | 376 | else: 377 | raise InvalidRequestError( 378 | request=f"{method} {path}: {req_params}", 379 | message=s_json[ret_msg], 380 | status_code=s_json[ret_code], 381 | time=dt.now(timezone.utc).strftime("%H:%M:%S"), 382 | resp_headers=s.headers, 383 | ) 384 | else: 385 | if self.log_requests: 386 | self.logger.debug( 387 | f"Response headers: {s.headers}" 388 | ) 389 | 390 | if self.return_response_headers: 391 | return s_json, s.elapsed, s.headers, 392 | elif self.record_request_time: 393 | return s_json, s.elapsed 394 | else: 395 | return s_json 396 | -------------------------------------------------------------------------------- /pybit/_v5_account.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .account import Account 3 | 4 | 5 | class AccountHTTP(_V5HTTPManager): 6 | def get_wallet_balance(self, **kwargs): 7 | """Obtain wallet balance, query asset information of each currency, and account risk rate information under unified margin mode. 8 | By default, currency information with assets or liabilities of 0 is not returned. 9 | 10 | Required args: 11 | accountType (string): Account type 12 | Unified account: UNIFIED 13 | Normal account: CONTRACT 14 | 15 | Returns: 16 | Request results as dictionary. 17 | 18 | Additional information: 19 | https://bybit-exchange.github.io/docs/v5/account/wallet-balance 20 | """ 21 | return self._submit_request( 22 | method="GET", 23 | path=f"{self.endpoint}{Account.GET_WALLET_BALANCE}", 24 | query=kwargs, 25 | auth=True, 26 | ) 27 | 28 | def get_transferable_amount(self, **kwargs): 29 | """Query the available amount to transfer of a specific coin in the Unified wallet. 30 | 31 | Required args: 32 | coinName (string): Coin name, uppercase only 33 | 34 | Returns: 35 | Request results as dictionary. 36 | 37 | Additional information: 38 | https://bybit-exchange.github.io/docs/v5/account/unified-trans-amnt 39 | """ 40 | return self._submit_request( 41 | method="GET", 42 | path=f"{self.endpoint}{Account.GET_TRANSFERABLE_AMOUNT}", 43 | query=kwargs, 44 | auth=True, 45 | ) 46 | 47 | def upgrade_to_unified_trading_account(self, **kwargs): 48 | """Upgrade Unified Account 49 | 50 | Returns: 51 | Request results as dictionary. 52 | 53 | Additional information: 54 | https://bybit-exchange.github.io/docs/v5/account/upgrade-unified-account 55 | """ 56 | return self._submit_request( 57 | method="POST", 58 | path=f"{self.endpoint}{Account.UPGRADE_TO_UNIFIED_ACCOUNT}", 59 | query=kwargs, 60 | auth=True, 61 | ) 62 | 63 | def get_borrow_history(self, **kwargs): 64 | """Get interest records, sorted in reverse order of creation time. 65 | 66 | Returns: 67 | Request results as dictionary. 68 | 69 | Additional information: 70 | https://bybit-exchange.github.io/docs/v5/account/borrow-history 71 | """ 72 | return self._submit_request( 73 | method="GET", 74 | path=f"{self.endpoint}{Account.GET_BORROW_HISTORY}", 75 | query=kwargs, 76 | auth=True, 77 | ) 78 | 79 | def repay_liability(self, **kwargs): 80 | """You can manually repay the liabilities of the Unified account 81 | 82 | Returns: 83 | Request results as dictionary. 84 | 85 | Additional information: 86 | https://bybit-exchange.github.io/docs/v5/account/repay-liability 87 | """ 88 | return self._submit_request( 89 | method="POST", 90 | path=f"{self.endpoint}{Account.REPAY_LIABILITY}", 91 | query=kwargs, 92 | auth=True, 93 | ) 94 | 95 | def get_collateral_info(self, **kwargs): 96 | """Get the collateral information of the current unified margin account, including loan interest rate, loanable amount, collateral conversion rate, whether it can be mortgaged as margin, etc. 97 | 98 | Returns: 99 | Request results as dictionary. 100 | 101 | Additional information: 102 | https://bybit-exchange.github.io/docs/v5/account/collateral-info 103 | """ 104 | return self._submit_request( 105 | method="GET", 106 | path=f"{self.endpoint}{Account.GET_COLLATERAL_INFO}", 107 | query=kwargs, 108 | auth=True, 109 | ) 110 | 111 | def set_collateral_coin(self, **kwargs): 112 | """You can decide whether the assets in the Unified account needs to be collateral coins. 113 | 114 | Required args: 115 | coin (string): Coin name 116 | collateralSwitch (string): ON: switch on collateral, OFF: switch off collateral 117 | 118 | Returns: 119 | Request results as dictionary. 120 | 121 | Additional information: 122 | https://bybit-exchange.github.io/docs/v5/account/set-collateral 123 | """ 124 | return self._submit_request( 125 | method="POST", 126 | path=f"{self.endpoint}{Account.SET_COLLATERAL_COIN}", 127 | query=kwargs, 128 | auth=True, 129 | ) 130 | 131 | def batch_set_collateral_coin(self, **kwargs): 132 | """You can decide whether the assets in the Unified account needs to be collateral coins. 133 | 134 | Required args: 135 | request (array): Object 136 | > coin (string): Coin name 137 | > collateralSwitch (string): ON: switch on collateral, OFF: switch off collateral 138 | 139 | Returns: 140 | Request results as dictionary. 141 | 142 | Additional information: 143 | https://bybit-exchange.github.io/docs/v5/account/batch-set-collateral 144 | """ 145 | return self._submit_request( 146 | method="POST", 147 | path=f"{self.endpoint}{Account.BATCH_SET_COLLATERAL_COIN}", 148 | query=kwargs, 149 | auth=True, 150 | ) 151 | 152 | def get_coin_greeks(self, **kwargs): 153 | """Get current account Greeks information 154 | 155 | Returns: 156 | Request results as dictionary. 157 | 158 | Additional information: 159 | https://bybit-exchange.github.io/docs/v5/account/coin-greeks 160 | """ 161 | return self._submit_request( 162 | method="GET", 163 | path=f"{self.endpoint}{Account.GET_COIN_GREEKS}", 164 | query=kwargs, 165 | auth=True, 166 | ) 167 | 168 | def get_fee_rates(self, **kwargs): 169 | """Get the trading fee rate of derivatives. 170 | 171 | Returns: 172 | Request results as dictionary. 173 | 174 | Additional information: 175 | https://bybit-exchange.github.io/docs/v5/account/fee-rate 176 | """ 177 | return self._submit_request( 178 | method="GET", 179 | path=f"{self.endpoint}{Account.GET_FEE_RATE}", 180 | query=kwargs, 181 | auth=True, 182 | ) 183 | 184 | def get_account_info(self, **kwargs): 185 | """Query the margin mode configuration of the account. 186 | 187 | Returns: 188 | Request results as dictionary. 189 | 190 | Additional information: 191 | https://bybit-exchange.github.io/docs/v5/account/account-info 192 | """ 193 | return self._submit_request( 194 | method="GET", 195 | path=f"{self.endpoint}{Account.GET_ACCOUNT_INFO}", 196 | query=kwargs, 197 | auth=True, 198 | ) 199 | 200 | def get_transaction_log(self, **kwargs): 201 | """Query transaction logs in Unified account. 202 | 203 | Returns: 204 | Request results as dictionary. 205 | 206 | Additional information: 207 | https://bybit-exchange.github.io/docs/v5/account/transaction-log 208 | """ 209 | return self._submit_request( 210 | method="GET", 211 | path=f"{self.endpoint}{Account.GET_TRANSACTION_LOG}", 212 | query=kwargs, 213 | auth=True, 214 | ) 215 | 216 | def get_contract_transaction_log(self, **kwargs): 217 | """Query transaction logs in Classic account. 218 | 219 | Returns: 220 | Request results as dictionary. 221 | 222 | Additional information: 223 | https://bybit-exchange.github.io/docs/v5/account/contract-transaction-log 224 | """ 225 | return self._submit_request( 226 | method="GET", 227 | path=f"{self.endpoint}{Account.GET_CONTRACT_TRANSACTION_LOG}", 228 | query=kwargs, 229 | auth=True, 230 | ) 231 | 232 | def set_margin_mode(self, **kwargs): 233 | """Default is regular margin mode. This mode is valid for USDT Perp, USDC Perp and USDC Option. 234 | 235 | Required args: 236 | setMarginMode (string): REGULAR_MARGIN, PORTFOLIO_MARGIN 237 | 238 | Returns: 239 | Request results as dictionary. 240 | 241 | Additional information: 242 | https://bybit-exchange.github.io/docs/v5/account/set-margin-mode 243 | """ 244 | return self._submit_request( 245 | method="POST", 246 | path=f"{self.endpoint}{Account.SET_MARGIN_MODE}", 247 | query=kwargs, 248 | auth=True, 249 | ) 250 | 251 | def set_mmp(self, **kwargs): 252 | """ 253 | Market Maker Protection (MMP) is an automated mechanism designed to protect market makers (MM) against liquidity risks 254 | and over-exposure in the market. It prevents simultaneous trade executions on quotes provided by the MM within a short time span. 255 | The MM can automatically pull their quotes if the number of contracts traded for an underlying asset exceeds the configured 256 | threshold within a certain time frame. Once MMP is triggered, any pre-existing MMP orders will be automatically canceled, 257 | and new orders tagged as MMP will be rejected for a specific duration — known as the frozen period — so that MM can 258 | reassess the market and modify the quotes. 259 | 260 | Required args: 261 | baseCoin (strin): Base coin 262 | window (string): Time window (ms) 263 | frozenPeriod (string): Frozen period (ms). "0" means the trade will remain frozen until manually reset 264 | qtyLimit (string): Trade qty limit (positive and up to 2 decimal places) 265 | deltaLimit (string): Delta limit (positive and up to 2 decimal places) 266 | 267 | Returns: 268 | Request results as dictionary. 269 | 270 | Additional information: 271 | https://bybit-exchange.github.io/docs/v5/account/set-mmp 272 | """ 273 | return self._submit_request( 274 | method="POST", 275 | path=f"{self.endpoint}{Account.SET_MMP}", 276 | query=kwargs, 277 | auth=True, 278 | ) 279 | 280 | def reset_mmp(self, **kwargs): 281 | """Once the mmp triggered, you can unfreeze the account by this endpoint 282 | 283 | Required args: 284 | baseCoin (string): Base coin 285 | 286 | Returns: 287 | Request results as dictionary. 288 | 289 | Additional information: 290 | https://bybit-exchange.github.io/docs/v5/account/reset-mmp 291 | """ 292 | return self._submit_request( 293 | method="POST", 294 | path=f"{self.endpoint}{Account.RESET_MMP}", 295 | query=kwargs, 296 | auth=True, 297 | ) 298 | 299 | def get_mmp_state(self, **kwargs): 300 | """Get MMP state 301 | 302 | Required args: 303 | baseCoin (string): Base coin 304 | 305 | Returns: 306 | Request results as dictionary. 307 | 308 | Additional information: 309 | https://bybit-exchange.github.io/docs/v5/account/get-mmp-state 310 | """ 311 | return self._submit_request( 312 | method="GET", 313 | path=f"{self.endpoint}{Account.GET_MMP_STATE}", 314 | query=kwargs, 315 | auth=True, 316 | ) 317 | -------------------------------------------------------------------------------- /pybit/_v5_asset.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .asset import Asset 3 | 4 | 5 | class AssetHTTP(_V5HTTPManager): 6 | def get_coin_exchange_records(self, **kwargs): 7 | """Query the coin exchange records. 8 | 9 | Returns: 10 | Request results as dictionary. 11 | 12 | Additional information: 13 | https://bybit-exchange.github.io/docs/v5/asset/exchange 14 | """ 15 | return self._submit_request( 16 | method="GET", 17 | path=f"{self.endpoint}{Asset.GET_COIN_EXCHANGE_RECORDS}", 18 | query=kwargs, 19 | auth=True, 20 | ) 21 | 22 | def get_option_delivery_record(self, **kwargs): 23 | """Query option delivery records, sorted by deliveryTime in descending order 24 | 25 | Required args: 26 | category (string): Product type. option 27 | 28 | Returns: 29 | Request results as dictionary. 30 | 31 | Additional information: 32 | https://bybit-exchange.github.io/docs/v5/asset/option-delivery 33 | """ 34 | return self._submit_request( 35 | method="GET", 36 | path=f"{self.endpoint}{Asset.GET_OPTION_DELIVERY_RECORD}", 37 | query=kwargs, 38 | auth=True, 39 | ) 40 | 41 | def get_usdc_contract_settlement(self, **kwargs): 42 | """Query session settlement records of USDC perpetual and futures 43 | 44 | Required args: 45 | category (string): Product type. linear 46 | 47 | Returns: 48 | Request results as dictionary. 49 | 50 | Additional information: 51 | https://bybit-exchange.github.io/docs/v5/asset/settlement 52 | """ 53 | return self._submit_request( 54 | method="GET", 55 | path=f"{self.endpoint}{Asset.GET_USDC_CONTRACT_SETTLEMENT}", 56 | query=kwargs, 57 | auth=True, 58 | ) 59 | 60 | def get_spot_asset_info(self, **kwargs): 61 | """Query asset information 62 | 63 | Required args: 64 | accountType (string): Account type. SPOT 65 | 66 | Returns: 67 | Request results as dictionary. 68 | 69 | Additional information: 70 | https://bybit-exchange.github.io/docs/v5/asset/asset-info 71 | """ 72 | return self._submit_request( 73 | method="GET", 74 | path=f"{self.endpoint}{Asset.GET_SPOT_ASSET_INFO}", 75 | query=kwargs, 76 | auth=True, 77 | ) 78 | 79 | def get_coins_balance(self, **kwargs): 80 | """You could get all coin balance of all account types under the master account, and sub account. 81 | 82 | Required args: 83 | memberId (string): User Id. It is required when you use master api key to check sub account coin balance 84 | accountType (string): Account type 85 | 86 | Returns: 87 | Request results as dictionary. 88 | 89 | Additional information: 90 | https://bybit-exchange.github.io/docs/v5/asset/all-balance 91 | """ 92 | return self._submit_request( 93 | method="GET", 94 | path=f"{self.endpoint}{Asset.GET_ALL_COINS_BALANCE}", 95 | query=kwargs, 96 | auth=True, 97 | ) 98 | 99 | def get_coin_balance(self, **kwargs): 100 | """Query the balance of a specific coin in a specific account type. Supports querying sub UID's balance. 101 | 102 | Required args: 103 | memberId (string): UID. Required when querying sub UID balance 104 | accountType (string): Account type 105 | 106 | Returns: 107 | Request results as dictionary. 108 | 109 | Additional information: 110 | https://bybit-exchange.github.io/docs/v5/asset/account-coin-balance 111 | """ 112 | return self._submit_request( 113 | method="GET", 114 | path=f"{self.endpoint}{Asset.GET_SINGLE_COIN_BALANCE}", 115 | query=kwargs, 116 | auth=True, 117 | ) 118 | 119 | def get_transferable_coin(self, **kwargs): 120 | """Query the transferable coin list between each account type 121 | 122 | Required args: 123 | fromAccountType (string): From account type 124 | toAccountType (string): To account type 125 | 126 | Returns: 127 | Request results as dictionary. 128 | 129 | Additional information: 130 | https://bybit-exchange.github.io/docs/v5/asset/transferable-coin 131 | """ 132 | return self._submit_request( 133 | method="GET", 134 | path=f"{self.endpoint}{Asset.GET_TRANSFERABLE_COIN}", 135 | query=kwargs, 136 | auth=True, 137 | ) 138 | 139 | def create_internal_transfer(self, **kwargs): 140 | """Create the internal transfer between different account types under the same UID. 141 | 142 | Required args: 143 | transferId (string): UUID. Please manually generate a UUID 144 | coin (string): Coin 145 | amount (string): Amount 146 | fromAccountType (string): From account type 147 | toAccountType (string): To account type 148 | 149 | Returns: 150 | Request results as dictionary. 151 | 152 | Additional information: 153 | https://bybit-exchange.github.io/docs/v5/asset/create-inter-transfer 154 | """ 155 | return self._submit_request( 156 | method="POST", 157 | path=f"{self.endpoint}{Asset.CREATE_INTERNAL_TRANSFER}", 158 | query=kwargs, 159 | auth=True, 160 | ) 161 | 162 | def get_internal_transfer_records(self, **kwargs): 163 | """Query the internal transfer records between different account types under the same UID. 164 | 165 | Returns: 166 | Request results as dictionary. 167 | 168 | Additional information: 169 | https://bybit-exchange.github.io/docs/v5/asset/inter-transfer-list 170 | """ 171 | return self._submit_request( 172 | method="GET", 173 | path=f"{self.endpoint}{Asset.GET_INTERNAL_TRANSFER_RECORDS}", 174 | query=kwargs, 175 | auth=True, 176 | ) 177 | 178 | def get_sub_uid(self, **kwargs): 179 | """Query the sub UIDs under a main UID 180 | 181 | Returns: 182 | Request results as dictionary. 183 | 184 | Additional information: 185 | https://bybit-exchange.github.io/docs/v5/asset/sub-uid-list 186 | """ 187 | return self._submit_request( 188 | method="GET", 189 | path=f"{self.endpoint}{Asset.GET_SUB_UID}", 190 | query=kwargs, 191 | auth=True, 192 | ) 193 | 194 | def enable_universal_transfer_for_sub_uid(self, **kwargs): 195 | """Transfer between sub-sub or main-sub 196 | 197 | Required args: 198 | subMemberIds (array): This list has a single item. Separate multiple UIDs by comma, e.g., "uid1,uid2,uid3" 199 | 200 | Returns: 201 | Request results as dictionary. 202 | 203 | Additional information: 204 | https://bybit-exchange.github.io/docs/v5/asset/enable-unitransfer-subuid 205 | """ 206 | self.logger.warning("enable_universal_transfer_for_sub_uid() is depreciated. You no longer need to configure transferable sub UIDs. Now, all sub UIDs are automatically enabled for universal transfer.") 207 | return self._submit_request( 208 | method="POST", 209 | path=f"{self.endpoint}{Asset.ENABLE_UT_FOR_SUB_UID}", 210 | query=kwargs, 211 | auth=True, 212 | ) 213 | 214 | def create_universal_transfer(self, **kwargs): 215 | """Transfer between sub-sub or main-sub. Please make sure you have enabled universal transfer on your sub UID in advance. 216 | 217 | Required args: 218 | transferId (string): UUID. Please manually generate a UUID 219 | coin (string): Coin 220 | amount (string): Amount 221 | fromMemberId (integer): From UID 222 | toMemberId (integer): To UID 223 | fromAccountType (string): From account type 224 | toAccountType (string): To account type 225 | 226 | Returns: 227 | Request results as dictionary. 228 | 229 | Additional information: 230 | https://bybit-exchange.github.io/docs/v5/asset/unitransfer 231 | """ 232 | return self._submit_request( 233 | method="POST", 234 | path=f"{self.endpoint}{Asset.CREATE_UNIVERSAL_TRANSFER}", 235 | query=kwargs, 236 | auth=True, 237 | ) 238 | 239 | def get_universal_transfer_records(self, **kwargs): 240 | """Query universal transfer records 241 | 242 | Returns: 243 | Request results as dictionary. 244 | 245 | Additional information: 246 | https://bybit-exchange.github.io/docs/v5/asset/unitransfer-list 247 | """ 248 | return self._submit_request( 249 | method="GET", 250 | path=f"{self.endpoint}{Asset.GET_UNIVERSAL_TRANSFER_RECORDS}", 251 | query=kwargs, 252 | auth=True, 253 | ) 254 | 255 | def get_allowed_deposit_coin_info(self, **kwargs): 256 | """Query allowed deposit coin information. To find out paired chain of coin, please refer coin info api. 257 | 258 | Returns: 259 | Request results as dictionary. 260 | 261 | Additional information: 262 | https://bybit-exchange.github.io/docs/v5/asset/deposit-coin-spec 263 | """ 264 | return self._submit_request( 265 | method="GET", 266 | path=f"{self.endpoint}{Asset.GET_ALLOWED_DEPOSIT_COIN_INFO}", 267 | query=kwargs, 268 | auth=True, 269 | ) 270 | 271 | def set_deposit_account(self, **kwargs): 272 | """Set auto transfer account after deposit. The same function as the setting for Deposit on web GUI 273 | 274 | Required args: 275 | accountType (string): Account type: UNIFIED,SPOT,OPTION,CONTRACT,FUND 276 | 277 | Returns: 278 | Request results as dictionary. 279 | 280 | Additional information: 281 | https://bybit-exchange.github.io/docs/v5/asset/set-deposit-acct 282 | """ 283 | return self._submit_request( 284 | method="POST", 285 | path=f"{self.endpoint}{Asset.SET_DEPOSIT_ACCOUNT}", 286 | query=kwargs, 287 | auth=True, 288 | ) 289 | 290 | def get_deposit_records(self, **kwargs): 291 | """Query deposit records. 292 | 293 | Returns: 294 | Request results as dictionary. 295 | 296 | Additional information: 297 | https://bybit-exchange.github.io/docs/v5/asset/deposit-record 298 | """ 299 | return self._submit_request( 300 | method="GET", 301 | path=f"{self.endpoint}{Asset.GET_DEPOSIT_RECORDS}", 302 | query=kwargs, 303 | auth=True, 304 | ) 305 | 306 | def get_sub_deposit_records(self, **kwargs): 307 | """Query subaccount's deposit records by MAIN UID's API key. 308 | 309 | Required args: 310 | subMemberId (string): Sub UID 311 | 312 | Returns: 313 | Request results as dictionary. 314 | 315 | Additional information: 316 | https://bybit-exchange.github.io/docs/v5/asset/sub-deposit-record 317 | """ 318 | return self._submit_request( 319 | method="GET", 320 | path=f"{self.endpoint}{Asset.GET_SUB_ACCOUNT_DEPOSIT_RECORDS}", 321 | query=kwargs, 322 | auth=True, 323 | ) 324 | 325 | def get_internal_deposit_records(self, **kwargs): 326 | """Query deposit records within the Bybit platform. These transactions are not on the blockchain. 327 | 328 | Returns: 329 | Request results as dictionary. 330 | 331 | Additional information: 332 | https://bybit-exchange.github.io/docs/v5/asset/internal-deposit-record 333 | """ 334 | return self._submit_request( 335 | method="GET", 336 | path=f"{self.endpoint}{Asset.GET_INTERNAL_DEPOSIT_RECORDS}", 337 | query=kwargs, 338 | auth=True, 339 | ) 340 | 341 | def get_master_deposit_address(self, **kwargs): 342 | """Query the deposit address information of MASTER account. 343 | 344 | Required args: 345 | coin (string): Coin 346 | 347 | Returns: 348 | Request results as dictionary. 349 | 350 | Additional information: 351 | https://bybit-exchange.github.io/docs/v5/asset/master-deposit-addr 352 | """ 353 | return self._submit_request( 354 | method="GET", 355 | path=f"{self.endpoint}{Asset.GET_MASTER_DEPOSIT_ADDRESS}", 356 | query=kwargs, 357 | auth=True, 358 | ) 359 | 360 | def get_sub_deposit_address(self, **kwargs): 361 | """Query the deposit address information of SUB account. 362 | 363 | Required args: 364 | coin (string): Coin 365 | chainType (string): Chain, e.g.,ETH 366 | 367 | Returns: 368 | Request results as dictionary. 369 | 370 | Additional information: 371 | https://bybit-exchange.github.io/docs/v5/asset/sub-deposit-addr 372 | """ 373 | return self._submit_request( 374 | method="GET", 375 | path=f"{self.endpoint}{Asset.GET_SUB_DEPOSIT_ADDRESS}", 376 | query=kwargs, 377 | auth=True, 378 | ) 379 | 380 | def get_coin_info(self, **kwargs): 381 | """Query coin information, including chain information, withdraw and deposit status. 382 | 383 | Returns: 384 | Request results as dictionary. 385 | 386 | Additional information: 387 | https://bybit-exchange.github.io/docs/v5/asset/coin-info 388 | """ 389 | return self._submit_request( 390 | method="GET", 391 | path=f"{self.endpoint}{Asset.GET_COIN_INFO}", 392 | query=kwargs, 393 | auth=True, 394 | ) 395 | 396 | def get_withdrawal_records(self, **kwargs): 397 | """Query withdrawal records. 398 | 399 | Returns: 400 | Request results as dictionary. 401 | 402 | Additional information: 403 | https://bybit-exchange.github.io/docs/v5/asset/withdraw-record 404 | """ 405 | return self._submit_request( 406 | method="GET", 407 | path=f"{self.endpoint}{Asset.GET_WITHDRAWAL_RECORDS}", 408 | query=kwargs, 409 | auth=True, 410 | ) 411 | 412 | def get_withdrawable_amount(self, **kwargs): 413 | """Get withdrawable amount 414 | 415 | Required args: 416 | coin (string): Coin name 417 | 418 | Returns: 419 | Request results as dictionary. 420 | 421 | Additional information: 422 | https://bybit-exchange.github.io/docs/v5/asset/delay-amount 423 | """ 424 | return self._submit_request( 425 | method="GET", 426 | path=f"{self.endpoint}{Asset.GET_WITHDRAWABLE_AMOUNT}", 427 | query=kwargs, 428 | auth=True, 429 | ) 430 | 431 | def withdraw(self, **kwargs): 432 | """Withdraw assets from your Bybit account. You can make an off-chain transfer if the target wallet address is from Bybit. This means that no blockchain fee will be charged. 433 | 434 | Required args: 435 | coin (string): Coin 436 | chain (string): Chain 437 | address (string): Wallet address 438 | tag (string): Tag. Required if tag exists in the wallet address list 439 | amount (string): Withdraw amount 440 | timestamp (integer): Current timestamp (ms). Used for preventing from withdraw replay 441 | 442 | Returns: 443 | Request results as dictionary. 444 | 445 | Additional information: 446 | https://bybit-exchange.github.io/docs/v5/asset/withdraw 447 | """ 448 | return self._submit_request( 449 | method="POST", 450 | path=f"{self.endpoint}{Asset.WITHDRAW}", 451 | query=kwargs, 452 | auth=True, 453 | ) 454 | 455 | def cancel_withdrawal(self, **kwargs): 456 | """Cancel the withdrawal 457 | 458 | Required args: 459 | id (string): Withdrawal ID 460 | 461 | Returns: 462 | Request results as dictionary. 463 | 464 | Additional information: 465 | https://bybit-exchange.github.io/docs/v5/asset/cancel-withdraw 466 | """ 467 | return self._submit_request( 468 | method="POST", 469 | path=f"{self.endpoint}{Asset.CANCEL_WITHDRAWAL}", 470 | query=kwargs, 471 | auth=True, 472 | ) 473 | 474 | def get_convert_coin_list(self, **kwargs): 475 | """Query for the list of coins you can convert to/from. 476 | 477 | Required args: 478 | accountType (string): Wallet type 479 | 480 | Returns: 481 | Request results as dictionary. 482 | 483 | Additional information: 484 | https://bybit-exchange.github.io/docs/v5/asset/convert/convert-coin-list 485 | """ 486 | return self._submit_request( 487 | method="GET", 488 | path=f"{self.endpoint}{Asset.GET_CONVERT_COIN_LIST}", 489 | query=kwargs, 490 | auth=True, 491 | ) 492 | 493 | def request_a_quote(self, **kwargs): 494 | """ 495 | Required args: 496 | fromCoin (string): Convert from coin (coin to sell) 497 | toCoin (string): Convert to coin (coin to buy) 498 | requestCoin (string): Request coin, same as fromCoin 499 | requestAmount (string): request coin amount (the amount you want to sell) 500 | 501 | Returns: 502 | Request results as dictionary. 503 | 504 | Additional information: 505 | https://bybit-exchange.github.io/docs/v5/asset/convert/apply-quote 506 | """ 507 | return self._submit_request( 508 | method="POST", 509 | path=f"{self.endpoint}{Asset.REQUEST_A_QUOTE}", 510 | query=kwargs, 511 | auth=True, 512 | ) 513 | 514 | def confirm_a_quote(self, **kwargs): 515 | """ 516 | Required args: 517 | quoteTxId (string): The quoteTxId from request_a_quote 518 | 519 | Returns: 520 | Request results as dictionary. 521 | 522 | Additional information: 523 | https://bybit-exchange.github.io/docs/v5/asset/convert/confirm-quote 524 | """ 525 | return self._submit_request( 526 | method="POST", 527 | path=f"{self.endpoint}{Asset.CONFIRM_A_QUOTE}", 528 | query=kwargs, 529 | auth=True, 530 | ) 531 | 532 | def get_convert_status(self, **kwargs): 533 | """ 534 | Required args: 535 | quoteTxId (string): Quote tx ID 536 | accountType (string): Wallet type 537 | 538 | Returns: 539 | Request results as dictionary. 540 | 541 | Additional information: 542 | https://bybit-exchange.github.io/docs/v5/asset/convert/get-convert-result 543 | """ 544 | return self._submit_request( 545 | method="GET", 546 | path=f"{self.endpoint}{Asset.GET_CONVERT_STATUS}", 547 | query=kwargs, 548 | auth=True, 549 | ) 550 | 551 | def get_convert_history(self, **kwargs): 552 | """ 553 | Returns: 554 | Request results as dictionary. 555 | 556 | Additional information: 557 | https://bybit-exchange.github.io/docs/v5/asset/convert/get-convert-history 558 | """ 559 | return self._submit_request( 560 | method="GET", 561 | path=f"{self.endpoint}{Asset.GET_CONVERT_HISTORY}", 562 | query=kwargs, 563 | auth=True, 564 | ) 565 | -------------------------------------------------------------------------------- /pybit/_v5_broker.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .broker import Broker 3 | 4 | 5 | class BrokerHTTP(_V5HTTPManager): 6 | def get_broker_earnings(self, **kwargs) -> dict: 7 | """ 8 | Returns: 9 | Request results as dictionary. 10 | 11 | Additional information: 12 | https://bybit-exchange.github.io/docs/v5/broker/earning 13 | """ 14 | self.logger.warning( 15 | "get_broker_earnings() is deprecated. See get_exchange_broker_earnings().") 16 | 17 | return self._submit_request( 18 | method="GET", 19 | path=f"{self.endpoint}{Broker.GET_BROKER_EARNINGS}", 20 | query=kwargs, 21 | auth=True, 22 | ) 23 | 24 | def get_exchange_broker_earnings(self, **kwargs) -> dict: 25 | """ 26 | Returns: 27 | Request results as dictionary. 28 | 29 | Additional information: 30 | https://bybit-exchange.github.io/docs/v5/broker/exchange-earning 31 | """ 32 | 33 | return self._submit_request( 34 | method="GET", 35 | path=f"{self.endpoint}{Broker.GET_EXCHANGE_BROKER_EARNINGS}", 36 | query=kwargs, 37 | auth=True, 38 | ) 39 | -------------------------------------------------------------------------------- /pybit/_v5_crypto_loan.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .crypto_loan import CryptoLoan 3 | 4 | 5 | class CryptoLoanHTTP(_V5HTTPManager): 6 | def get_collateral_coins(self, **kwargs): 7 | """ 8 | Returns: 9 | Request results as dictionary. 10 | 11 | Additional information: 12 | https://bybit-exchange.github.io/docs/v5/crypto-loan/collateral-coin 13 | """ 14 | return self._submit_request( 15 | method="GET", 16 | path=f"{self.endpoint}{CryptoLoan.GET_COLLATERAL_COINS}", 17 | query=kwargs, 18 | ) 19 | 20 | def get_borrowable_coins(self, **kwargs): 21 | """ 22 | Returns: 23 | Request results as dictionary. 24 | 25 | Additional information: 26 | https://bybit-exchange.github.io/docs/v5/crypto-loan/loan-coin 27 | """ 28 | return self._submit_request( 29 | method="GET", 30 | path=f"{self.endpoint}{CryptoLoan.GET_BORROWABLE_COINS}", 31 | query=kwargs, 32 | ) 33 | 34 | def get_account_borrowable_or_collateralizable_limit(self, **kwargs): 35 | """ 36 | Query for the minimum and maximum amounts your account can borrow and 37 | how much collateral you can put up. 38 | 39 | Required args: 40 | loanCurrency (string): Loan coin name 41 | collateralCurrency (string): Collateral coin name 42 | 43 | Returns: 44 | Request results as dictionary. 45 | 46 | Additional information: 47 | https://bybit-exchange.github.io/docs/v5/crypto-loan/acct-borrow-collateral 48 | """ 49 | return self._submit_request( 50 | method="GET", 51 | path=f"{self.endpoint}{CryptoLoan.GET_ACCOUNT_BORROWABLE_OR_COLLATERALIZABLE_LIMIT}", 52 | query=kwargs, 53 | auth=True, 54 | ) 55 | 56 | def borrow_crypto_loan(self, **kwargs): 57 | """ 58 | Required args: 59 | loanCurrency (string): Loan coin name 60 | 61 | Returns: 62 | Request results as dictionary. 63 | 64 | Additional information: 65 | https://bybit-exchange.github.io/docs/v5/crypto-loan/borrow 66 | """ 67 | return self._submit_request( 68 | method="POST", 69 | path=f"{self.endpoint}{CryptoLoan.BORROW_CRYPTO_LOAN}", 70 | query=kwargs, 71 | auth=True, 72 | ) 73 | 74 | def repay_crypto_loan(self, **kwargs): 75 | """ 76 | Required args: 77 | orderId (string): Loan order ID 78 | amount (string): Repay amount 79 | 80 | Returns: 81 | Request results as dictionary. 82 | 83 | Additional information: 84 | https://bybit-exchange.github.io/docs/v5/crypto-loan/repay 85 | """ 86 | return self._submit_request( 87 | method="POST", 88 | path=f"{self.endpoint}{CryptoLoan.REPAY_CRYPTO_LOAN}", 89 | query=kwargs, 90 | auth=True, 91 | ) 92 | 93 | def get_unpaid_loans(self, **kwargs): 94 | """ 95 | Query for your ongoing loans. 96 | 97 | Returns: 98 | Request results as dictionary. 99 | 100 | Additional information: 101 | https://bybit-exchange.github.io/docs/v5/crypto-loan/repay 102 | """ 103 | return self._submit_request( 104 | method="GET", 105 | path=f"{self.endpoint}{CryptoLoan.GET_UNPAID_LOANS}", 106 | query=kwargs, 107 | auth=True, 108 | ) 109 | 110 | def get_loan_repayment_history(self, **kwargs): 111 | """ 112 | Query for loan repayment transactions. A loan may be repaid in multiple 113 | repayments. 114 | 115 | Returns: 116 | Request results as dictionary. 117 | 118 | Additional information: 119 | https://bybit-exchange.github.io/docs/v5/crypto-loan/repay-transaction 120 | """ 121 | return self._submit_request( 122 | method="GET", 123 | path=f"{self.endpoint}{CryptoLoan.GET_LOAN_REPAYMENT_HISTORY}", 124 | query=kwargs, 125 | auth=True, 126 | ) 127 | 128 | def get_completed_loan_history(self, **kwargs): 129 | """ 130 | Query for the last 6 months worth of your completed (fully paid off) 131 | loans. 132 | 133 | Returns: 134 | Request results as dictionary. 135 | 136 | Additional information: 137 | https://bybit-exchange.github.io/docs/v5/crypto-loan/completed-loan-order 138 | """ 139 | return self._submit_request( 140 | method="GET", 141 | path=f"{self.endpoint}{CryptoLoan.GET_COMPLETED_LOAN_ORDER_HISTORY}", 142 | query=kwargs, 143 | auth=True, 144 | ) 145 | 146 | def get_max_allowed_collateral_reduction_amount(self, **kwargs): 147 | """ 148 | Query for the maximum amount by which collateral may be reduced by. 149 | 150 | Returns: 151 | Request results as dictionary. 152 | 153 | Additional information: 154 | https://bybit-exchange.github.io/docs/v5/crypto-loan/reduce-max-collateral-amt 155 | """ 156 | return self._submit_request( 157 | method="GET", 158 | path=f"{self.endpoint}{CryptoLoan.GET_MAX_ALLOWED_COLLATERAL_REDUCTION_AMOUNT}", 159 | query=kwargs, 160 | auth=True, 161 | ) 162 | 163 | def adjust_collateral_amount(self, **kwargs): 164 | """ 165 | You can increase or reduce your collateral amount. When you reduce, 166 | please obey the max. allowed reduction amount. 167 | 168 | Returns: 169 | Request results as dictionary. 170 | 171 | Additional information: 172 | https://bybit-exchange.github.io/docs/v5/crypto-loan/adjust-collateral 173 | """ 174 | return self._submit_request( 175 | method="GET", 176 | path=f"{self.endpoint}{CryptoLoan.ADJUST_COLLATERAL_AMOUNT}", 177 | query=kwargs, 178 | auth=True, 179 | ) 180 | 181 | def get_crypto_loan_ltv_adjustment_history(self, **kwargs): 182 | """ 183 | You can increase or reduce your collateral amount. When you reduce, 184 | please obey the max. allowed reduction amount. 185 | 186 | Returns: 187 | Request results as dictionary. 188 | 189 | Additional information: 190 | https://bybit-exchange.github.io/docs/v5/crypto-loan/ltv-adjust-history 191 | """ 192 | return self._submit_request( 193 | method="GET", 194 | path=f"{self.endpoint}{CryptoLoan.GET_CRYPTO_LOAN_LTV_ADJUSTMENT_HISTORY}", 195 | query=kwargs, 196 | auth=True, 197 | ) 198 | -------------------------------------------------------------------------------- /pybit/_v5_earn.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .earn import Earn 3 | 4 | 5 | class EarnHTTP(_V5HTTPManager): 6 | def get_earn_product_info(self, **kwargs): 7 | """ 8 | Required args: 9 | category (string): FlexibleSaving 10 | 11 | Returns: 12 | Request results as dictionary. 13 | 14 | Additional information: 15 | https://bybit-exchange.github.io/docs/v5/earn/product-info 16 | """ 17 | return self._submit_request( 18 | method="GET", 19 | path=f"{self.endpoint}{Earn.GET_EARN_PRODUCT_INFO}", 20 | query=kwargs, 21 | ) 22 | 23 | def stake_or_redeem(self, **kwargs): 24 | """ 25 | Required args: 26 | category (string): FlexibleSaving 27 | orderType (string): Stake, Redeem 28 | accountType (string): FUND, UNIFIED 29 | 30 | Returns: 31 | Request results as dictionary. 32 | 33 | Additional information: 34 | https://bybit-exchange.github.io/docs/v5/earn/create-order 35 | """ 36 | return self._submit_request( 37 | method="POST", 38 | path=f"{self.endpoint}{Earn.STAKE_OR_REDEEM}", 39 | query=kwargs, 40 | auth=True, 41 | ) 42 | 43 | def get_stake_or_redemption_history(self, **kwargs): 44 | """ 45 | Required args: 46 | category (string): FlexibleSaving 47 | 48 | Returns: 49 | Request results as dictionary. 50 | 51 | Additional information: 52 | https://bybit-exchange.github.io/docs/v5/earn/order-history 53 | """ 54 | return self._submit_request( 55 | method="GET", 56 | path=f"{self.endpoint}{Earn.GET_STAKE_OR_REDEMPTION_HISTORY}", 57 | query=kwargs, 58 | auth=True, 59 | ) 60 | 61 | def get_staked_position(self, **kwargs): 62 | """ 63 | Required args: 64 | category (string): FlexibleSaving 65 | 66 | Returns: 67 | Request results as dictionary. 68 | 69 | Additional information: 70 | https://bybit-exchange.github.io/docs/v5/earn/position 71 | """ 72 | return self._submit_request( 73 | method="GET", 74 | path=f"{self.endpoint}{Earn.GET_STAKED_POSITION}", 75 | query=kwargs, 76 | auth=True, 77 | ) 78 | -------------------------------------------------------------------------------- /pybit/_v5_institutional_loan.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .institutional_loan import InstitutionalLoan as InsLoan 3 | 4 | 5 | class InstitutionalLoanHTTP(_V5HTTPManager): 6 | def get_product_info(self, **kwargs) -> dict: 7 | """ 8 | Returns: 9 | Request results as dictionary. 10 | 11 | Additional information: 12 | https://bybit-exchange.github.io/docs/v5/otc/margin-product-info 13 | """ 14 | return self._submit_request( 15 | method="GET", 16 | path=f"{self.endpoint}{InsLoan.GET_PRODUCT_INFO}", 17 | query=kwargs, 18 | ) 19 | 20 | def get_margin_coin_info(self, **kwargs) -> dict: 21 | """ 22 | Returns: 23 | Request results as dictionary. 24 | 25 | Additional information: 26 | https://bybit-exchange.github.io/docs/v5/otc/margin-coin-convert-info 27 | """ 28 | return self._submit_request( 29 | method="GET", 30 | path=f"{self.endpoint}{InsLoan.GET_MARGIN_COIN_INFO}", 31 | query=kwargs, 32 | ) 33 | 34 | def get_loan_orders(self, **kwargs) -> dict: 35 | """ 36 | Get loan orders information 37 | 38 | Returns: 39 | Request results as dictionary. 40 | 41 | Additional information: 42 | https://bybit-exchange.github.io/docs/v5/otc/loan-info 43 | """ 44 | return self._submit_request( 45 | method="GET", 46 | path=f"{self.endpoint}{InsLoan.GET_LOAN_ORDERS}", 47 | query=kwargs, 48 | auth=True, 49 | ) 50 | 51 | def get_repayment_info(self, **kwargs) -> dict: 52 | """ 53 | Get a list of your loan repayment orders (orders which repaid the loan). 54 | 55 | Returns: 56 | Request results as dictionary. 57 | 58 | Additional information: 59 | https://bybit-exchange.github.io/docs/v5/otc/repay-info 60 | """ 61 | return self._submit_request( 62 | method="GET", 63 | path=f"{self.endpoint}{InsLoan.GET_REPAYMENT_ORDERS}", 64 | query=kwargs, 65 | auth=True, 66 | ) 67 | 68 | def get_ltv(self, **kwargs) -> dict: 69 | """ 70 | Get your loan-to-value ratio. 71 | 72 | Returns: 73 | Request results as dictionary. 74 | 75 | Additional information: 76 | https://bybit-exchange.github.io/docs/v5/otc/ltv-convert 77 | """ 78 | return self._submit_request( 79 | method="GET", 80 | path=f"{self.endpoint}{InsLoan.GET_LTV}", 81 | query=kwargs, 82 | auth=True, 83 | ) 84 | 85 | def bind_or_unbind_uid(self, **kwargs) -> dict: 86 | """ 87 | For the institutional loan product, you can bind new UIDs to the risk 88 | unit or unbind UID from the risk unit. 89 | Returns: 90 | Request results as dictionary. 91 | 92 | Additional information: 93 | https://bybit-exchange.github.io/docs/v5/otc/bind-uid 94 | """ 95 | return self._submit_request( 96 | method="POST", 97 | path=f"{self.endpoint}{InsLoan.BIND_OR_UNBIND_UID}", 98 | query=kwargs, 99 | auth=True, 100 | ) 101 | -------------------------------------------------------------------------------- /pybit/_v5_market.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .market import Market 3 | 4 | 5 | class MarketHTTP(_V5HTTPManager): 6 | def get_server_time(self) -> dict: 7 | """ 8 | Returns: 9 | Request results as dictionary. 10 | 11 | Additional information: 12 | https://bybit-exchange.github.io/docs/v5/market/time 13 | """ 14 | return self._submit_request( 15 | method="GET", 16 | path=f"{self.endpoint}{Market.GET_SERVER_TIME}", 17 | ) 18 | 19 | def get_kline(self, **kwargs) -> dict: 20 | """Query the kline data. Charts are returned in groups based on the requested interval. 21 | 22 | Required args: 23 | category (string): Product type: spot,linear,inverse 24 | symbol (string): Symbol name 25 | interval (string): Kline interval. 26 | 27 | Returns: 28 | Request results as dictionary. 29 | 30 | Additional information: 31 | https://bybit-exchange.github.io/docs/v5/market/kline 32 | """ 33 | return self._submit_request( 34 | method="GET", 35 | path=f"{self.endpoint}{Market.GET_KLINE}", 36 | query=kwargs, 37 | ) 38 | 39 | def get_mark_price_kline(self, **kwargs): 40 | """Query the mark price kline data. Charts are returned in groups based on the requested interval. 41 | 42 | Required args: 43 | category (string): Product type. linear,inverse 44 | symbol (string): Symbol name 45 | interval (string): Kline interval. 1,3,5,15,30,60,120,240,360,720,D,M,W 46 | 47 | Returns: 48 | Request results as dictionary. 49 | 50 | Additional information: 51 | https://bybit-exchange.github.io/docs/v5/market/mark-kline 52 | """ 53 | return self._submit_request( 54 | method="GET", 55 | path=f"{self.endpoint}{Market.GET_MARK_PRICE_KLINE}", 56 | query=kwargs, 57 | ) 58 | 59 | def get_index_price_kline(self, **kwargs): 60 | """Query the index price kline data. Charts are returned in groups based on the requested interval. 61 | 62 | Required args: 63 | category (string): Product type. linear,inverse 64 | symbol (string): Symbol name 65 | interval (string): Kline interval. 1,3,5,15,30,60,120,240,360,720,D,M,W 66 | 67 | Returns: 68 | Request results as dictionary. 69 | 70 | Additional information: 71 | https://bybit-exchange.github.io/docs/v5/market/index-kline 72 | """ 73 | return self._submit_request( 74 | method="GET", 75 | path=f"{self.endpoint}{Market.GET_INDEX_PRICE_KLINE}", 76 | query=kwargs, 77 | ) 78 | 79 | def get_premium_index_price_kline(self, **kwargs): 80 | """Retrieve the premium index price kline data. Charts are returned in groups based on the requested interval. 81 | 82 | Required args: 83 | category (string): Product type. linear 84 | symbol (string): Symbol name 85 | interval (string): Kline interval 86 | 87 | Returns: 88 | Request results as dictionary. 89 | 90 | Additional information: 91 | https://bybit-exchange.github.io/docs/v5/market/preimum-index-kline 92 | """ 93 | return self._submit_request( 94 | method="GET", 95 | path=f"{self.endpoint}{Market.GET_PREMIUM_INDEX_PRICE_KLINE}", 96 | query=kwargs, 97 | ) 98 | 99 | def get_instruments_info(self, **kwargs): 100 | """Query a list of instruments of online trading pair. 101 | 102 | Required args: 103 | category (string): Product type. spot,linear,inverse,option 104 | 105 | Returns: 106 | Request results as dictionary. 107 | 108 | Additional information: 109 | https://bybit-exchange.github.io/docs/v5/market/instrument 110 | """ 111 | return self._submit_request( 112 | method="GET", 113 | path=f"{self.endpoint}{Market.GET_INSTRUMENTS_INFO}", 114 | query=kwargs, 115 | ) 116 | 117 | def get_orderbook(self, **kwargs): 118 | """Query orderbook data 119 | 120 | Required args: 121 | category (string): Product type. spot, linear, inverse, option 122 | symbol (string): Symbol name 123 | 124 | Returns: 125 | Request results as dictionary. 126 | 127 | Additional information: 128 | https://bybit-exchange.github.io/docs/v5/market/orderbook 129 | """ 130 | return self._submit_request( 131 | method="GET", 132 | path=f"{self.endpoint}{Market.GET_ORDERBOOK}", 133 | query=kwargs, 134 | ) 135 | 136 | def get_tickers(self, **kwargs): 137 | """Query the latest price snapshot, best bid/ask price, and trading volume in the last 24 hours. 138 | 139 | Required args: 140 | category (string): Product type. spot,linear,inverse,option 141 | 142 | Returns: 143 | Request results as dictionary. 144 | 145 | Additional information: 146 | https://bybit-exchange.github.io/docs/v5/market/tickers 147 | """ 148 | return self._submit_request( 149 | method="GET", 150 | path=f"{self.endpoint}{Market.GET_TICKERS}", 151 | query=kwargs, 152 | ) 153 | 154 | def get_funding_rate_history(self, **kwargs): 155 | """ 156 | Query historical funding rate. Each symbol has a different funding interval. 157 | For example, if the interval is 8 hours and the current time is UTC 12, then it returns the last funding rate, which settled at UTC 8. 158 | To query the funding rate interval, please refer to instruments-info. 159 | 160 | Required args: 161 | category (string): Product type. linear,inverse 162 | symbol (string): Symbol name 163 | 164 | Returns: 165 | Request results as dictionary. 166 | 167 | Additional information: 168 | https://bybit-exchange.github.io/docs/v5/market/history-fund-rate 169 | """ 170 | return self._submit_request( 171 | method="GET", 172 | path=f"{self.endpoint}{Market.GET_FUNDING_RATE_HISTORY}", 173 | query=kwargs, 174 | ) 175 | 176 | def get_public_trade_history(self, **kwargs): 177 | """Query recent public trading data in Bybit. 178 | 179 | Required args: 180 | category (string): Product type. spot,linear,inverse,option 181 | symbol (string): Symbol name 182 | 183 | Returns: 184 | Request results as dictionary. 185 | 186 | Additional information: 187 | https://bybit-exchange.github.io/docs/v5/market/recent-trade 188 | """ 189 | return self._submit_request( 190 | method="GET", 191 | path=f"{self.endpoint}{Market.GET_PUBLIC_TRADING_HISTORY}", 192 | query=kwargs, 193 | ) 194 | 195 | def get_open_interest(self, **kwargs): 196 | """Get open interest of each symbol. 197 | 198 | Required args: 199 | category (string): Product type. linear,inverse 200 | symbol (string): Symbol name 201 | intervalTime (string): Interval. 5min,15min,30min,1h,4h,1d 202 | 203 | Returns: 204 | Request results as dictionary. 205 | 206 | Additional information: 207 | https://bybit-exchange.github.io/docs/v5/market/open-interest 208 | """ 209 | return self._submit_request( 210 | method="GET", 211 | path=f"{self.endpoint}{Market.GET_OPEN_INTEREST}", 212 | query=kwargs, 213 | ) 214 | 215 | def get_historical_volatility(self, **kwargs): 216 | """Query option historical volatility 217 | 218 | Required args: 219 | category (string): Product type. option 220 | 221 | Returns: 222 | Request results as dictionary. 223 | 224 | Additional information: 225 | https://bybit-exchange.github.io/docs/v5/market/iv 226 | """ 227 | return self._submit_request( 228 | method="GET", 229 | path=f"{self.endpoint}{Market.GET_HISTORICAL_VOLATILITY}", 230 | query=kwargs, 231 | ) 232 | 233 | def get_insurance(self, **kwargs): 234 | """ 235 | Query Bybit insurance pool data (BTC/USDT/USDC etc). 236 | The data is updated every 24 hours. 237 | 238 | Returns: 239 | Request results as dictionary. 240 | 241 | Additional information: 242 | https://bybit-exchange.github.io/docs/v5/market/insurance 243 | """ 244 | return self._submit_request( 245 | method="GET", 246 | path=f"{self.endpoint}{Market.GET_INSURANCE}", 247 | query=kwargs, 248 | ) 249 | 250 | def get_risk_limit(self, **kwargs): 251 | """Query risk limit of futures 252 | 253 | Returns: 254 | Request results as dictionary. 255 | 256 | Additional information: 257 | https://bybit-exchange.github.io/docs/v5/market/risk-limit 258 | """ 259 | return self._submit_request( 260 | method="GET", 261 | path=f"{self.endpoint}{Market.GET_RISK_LIMIT}", 262 | query=kwargs, 263 | ) 264 | 265 | def get_option_delivery_price(self, **kwargs): 266 | """Get the delivery price for option 267 | 268 | Required args: 269 | category (string): Product type. option 270 | 271 | Returns: 272 | Request results as dictionary. 273 | 274 | Additional information: 275 | https://bybit-exchange.github.io/docs/v5/market/delivery-price 276 | """ 277 | return self._submit_request( 278 | method="GET", 279 | path=f"{self.endpoint}{Market.GET_OPTION_DELIVERY_PRICE}", 280 | query=kwargs, 281 | ) 282 | 283 | def get_long_short_ratio(self, **kwargs): 284 | """ 285 | Required args: 286 | category (string): Product type. linear (USDT Perpetual only), inverse 287 | symbol (string): Symbol name 288 | 289 | Returns: 290 | Request results as dictionary. 291 | 292 | Additional information: 293 | https://bybit-exchange.github.io/docs/v5/market/long-short-ratio 294 | """ 295 | return self._submit_request( 296 | method="GET", 297 | path=f"{self.endpoint}{Market.GET_LONG_SHORT_RATIO}", 298 | query=kwargs, 299 | ) 300 | -------------------------------------------------------------------------------- /pybit/_v5_misc.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .misc import Misc 3 | 4 | 5 | class MiscHTTP(_V5HTTPManager): 6 | def get_announcement(self, **kwargs) -> dict: 7 | """ 8 | Required args: 9 | locale (string): Language symbol 10 | 11 | Returns: 12 | Request results as dictionary. 13 | 14 | Additional information: 15 | https://bybit-exchange.github.io/docs/v5/announcement 16 | """ 17 | return self._submit_request( 18 | method="GET", 19 | path=f"{self.endpoint}{Misc.GET_ANNOUNCEMENT}", 20 | query=kwargs, 21 | ) 22 | 23 | def request_demo_trading_funds(self) -> dict: 24 | """ 25 | Returns: 26 | Request results as dictionary. 27 | 28 | Additional information: 29 | https://bybit-exchange.github.io/docs/v5/demo 30 | """ 31 | if not self.demo: 32 | raise Exception( 33 | "You must pass demo=True to the pybit HTTP session to use this " 34 | "method." 35 | ) 36 | return self._submit_request( 37 | method="POST", 38 | path=f"{self.endpoint}{Misc.REQUEST_DEMO_TRADING_FUNDS}", 39 | auth=True, 40 | ) 41 | -------------------------------------------------------------------------------- /pybit/_v5_position.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .position import Position 3 | 4 | 5 | class PositionHTTP(_V5HTTPManager): 6 | def get_positions(self, **kwargs): 7 | """Query real-time position data, such as position size, cumulative realizedPNL. 8 | 9 | Required args: 10 | category (string): Product type 11 | Unified account: linear, option 12 | Normal account: linear, inverse. 13 | 14 | Please note that category is not involved with business logic 15 | 16 | Returns: 17 | Request results as dictionary. 18 | 19 | Additional information: 20 | https://bybit-exchange.github.io/docs/v5/position 21 | """ 22 | return self._submit_request( 23 | method="GET", 24 | path=f"{self.endpoint}{Position.GET_POSITIONS}", 25 | query=kwargs, 26 | auth=True, 27 | ) 28 | 29 | def set_leverage(self, **kwargs): 30 | """Set the leverage 31 | 32 | Required args: 33 | category (string): Product type 34 | Unified account: linear 35 | Normal account: linear, inverse. 36 | 37 | Please note that category is not involved with business logic 38 | symbol (string): Symbol name 39 | buyLeverage (string): [0, max leverage of corresponding risk limit]. 40 | Note: Under one-way mode, buyLeverage must be the same as sellLeverage 41 | sellLeverage (string): [0, max leverage of corresponding risk limit]. 42 | Note: Under one-way mode, buyLeverage must be the same as sellLeverage 43 | 44 | Returns: 45 | Request results as dictionary. 46 | 47 | Additional information: 48 | https://bybit-exchange.github.io/docs/v5/position/leverage 49 | """ 50 | return self._submit_request( 51 | method="POST", 52 | path=f"{self.endpoint}{Position.SET_LEVERAGE}", 53 | query=kwargs, 54 | auth=True, 55 | ) 56 | 57 | def switch_margin_mode(self, **kwargs): 58 | """Select cross margin mode or isolated margin mode 59 | 60 | Required args: 61 | category (string): Product type. linear,inverse 62 | 63 | Please note that category is not involved with business logicUnified account is not applicable 64 | symbol (string): Symbol name 65 | tradeMode (integer): 0: cross margin. 1: isolated margin 66 | buyLeverage (string): The value must be equal to sellLeverage value 67 | sellLeverage (string): The value must be equal to buyLeverage value 68 | 69 | Returns: 70 | Request results as dictionary. 71 | 72 | Additional information: 73 | https://bybit-exchange.github.io/docs/v5/position/cross-isolate 74 | """ 75 | return self._submit_request( 76 | method="POST", 77 | path=f"{self.endpoint}{Position.SWITCH_MARGIN_MODE}", 78 | query=kwargs, 79 | auth=True, 80 | ) 81 | 82 | def set_tp_sl_mode(self, **kwargs): 83 | """Set TP/SL mode to Full or Partial 84 | 85 | Required args: 86 | category (string): Product type 87 | Unified account: linear 88 | Normal account: linear, inverse. 89 | 90 | Please note that category is not involved with business logic 91 | symbol (string): Symbol name 92 | tpSlMode (string): TP/SL mode. Full,Partial 93 | 94 | Returns: 95 | Request results as dictionary. 96 | 97 | Additional information: 98 | https://bybit-exchange.github.io/docs/v5/position/tpsl-mode 99 | """ 100 | return self._submit_request( 101 | method="POST", 102 | path=f"{self.endpoint}{Position.SET_TP_SL_MODE}", 103 | query=kwargs, 104 | auth=True, 105 | ) 106 | 107 | def switch_position_mode(self, **kwargs): 108 | """ 109 | It supports to switch the position mode for USDT perpetual and Inverse futures. 110 | If you are in one-way Mode, you can only open one position on Buy or Sell side. 111 | If you are in hedge mode, you can open both Buy and Sell side positions simultaneously. 112 | 113 | Required args: 114 | category (string): Product type. linear,inverse 115 | 116 | Please note that category is not involved with business logicUnified account is not applicable 117 | 118 | Returns: 119 | Request results as dictionary. 120 | 121 | Additional information: 122 | https://bybit-exchange.github.io/docs/v5/position/position-mode 123 | """ 124 | return self._submit_request( 125 | method="POST", 126 | path=f"{self.endpoint}{Position.SWITCH_POSITION_MODE}", 127 | query=kwargs, 128 | auth=True, 129 | ) 130 | 131 | def set_risk_limit(self, **kwargs): 132 | """ 133 | The risk limit will limit the maximum position value you can hold under different margin requirements. 134 | If you want to hold a bigger position size, you need more margin. This interface can set the risk limit of a single position. 135 | If the order exceeds the current risk limit when placing an order, it will be rejected. Click here to learn more about risk limit. 136 | 137 | Required args: 138 | category (string): Product type 139 | Unified account: linear 140 | Normal account: linear, inverse. 141 | 142 | Please note that category is not involved with business logic 143 | symbol (string): Symbol name 144 | riskId (integer): Risk limit ID 145 | 146 | Returns: 147 | Request results as dictionary. 148 | 149 | Additional information: 150 | https://bybit-exchange.github.io/docs/v5/position/set-risk-limit 151 | """ 152 | return self._submit_request( 153 | method="POST", 154 | path=f"{self.endpoint}{Position.SET_RISK_LIMIT}", 155 | query=kwargs, 156 | auth=True, 157 | ) 158 | 159 | def set_trading_stop(self, **kwargs): 160 | """Set the take profit, stop loss or trailing stop for the position. 161 | 162 | Required args: 163 | category (string): Product type 164 | Unified account: linear 165 | Normal account: linear, inverse. 166 | 167 | Please note that category is not involved with business logic 168 | symbol (string): Symbol name 169 | 170 | Returns: 171 | Request results as dictionary. 172 | 173 | Additional information: 174 | https://bybit-exchange.github.io/docs/v5/position/trading-stop 175 | """ 176 | return self._submit_request( 177 | method="POST", 178 | path=f"{self.endpoint}{Position.SET_TRADING_STOP}", 179 | query=kwargs, 180 | auth=True, 181 | ) 182 | 183 | def set_auto_add_margin(self, **kwargs): 184 | """Turn on/off auto-add-margin for isolated margin position 185 | 186 | Required args: 187 | category (string): Product type. linear 188 | symbol (string): Symbol name 189 | autoAddMargin (integer): Turn on/off. 0: off. 1: on 190 | 191 | Returns: 192 | Request results as dictionary. 193 | 194 | Additional information: 195 | https://bybit-exchange.github.io/docs/v5/position/auto-add-margin 196 | """ 197 | return self._submit_request( 198 | method="POST", 199 | path=f"{self.endpoint}{Position.SET_AUTO_ADD_MARGIN}", 200 | query=kwargs, 201 | auth=True, 202 | ) 203 | 204 | def add_or_reduce_margin(self, **kwargs): 205 | """Manually add or reduce margin for isolated margin position 206 | 207 | Required args: 208 | category (string): Product type. linear 209 | symbol (string): Symbol name 210 | margin (string): Add or reduce. To add, use 10. To reduce, use -10 211 | 212 | Returns: 213 | Request results as dictionary. 214 | 215 | Additional information: 216 | https://bybit-exchange.github.io/docs/v5/position/manual-add-margin 217 | """ 218 | return self._submit_request( 219 | method="POST", 220 | path=f"{self.endpoint}{Position.ADD_MARGIN}", 221 | query=kwargs, 222 | auth=True, 223 | ) 224 | 225 | def get_executions(self, **kwargs): 226 | """Query users' execution records, sorted by execTime in descending order 227 | 228 | Required args: 229 | category (string): 230 | Product type Unified account: spot, linear, option 231 | Normal account: linear, inverse. 232 | 233 | Please note that category is not involved with business logic 234 | 235 | Returns: 236 | Request results as dictionary. 237 | 238 | Additional information: 239 | https://bybit-exchange.github.io/docs/v5/order/execution 240 | """ 241 | return self._submit_request( 242 | method="GET", 243 | path=f"{self.endpoint}{Position.GET_EXECUTIONS}", 244 | query=kwargs, 245 | auth=True, 246 | ) 247 | 248 | def get_closed_pnl(self, **kwargs): 249 | """Query user's closed profit and loss records. The results are sorted by createdTime in descending order. 250 | 251 | Required args: 252 | category (string): 253 | Product type Unified account: linear 254 | Normal account: linear, inverse. 255 | 256 | Please note that category is not involved with business logic 257 | 258 | Returns: 259 | Request results as dictionary. 260 | 261 | Additional information: 262 | https://bybit-exchange.github.io/docs/v5/position/close-pnl 263 | """ 264 | return self._submit_request( 265 | method="GET", 266 | path=f"{self.endpoint}{Position.GET_CLOSED_PNL}", 267 | query=kwargs, 268 | auth=True, 269 | ) 270 | -------------------------------------------------------------------------------- /pybit/_v5_pre_upgrade.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .pre_upgrade import PreUpgrade 3 | 4 | 5 | class PreUpgradeHTTP(_V5HTTPManager): 6 | def get_pre_upgrade_order_history(self, **kwargs) -> dict: 7 | """ 8 | After the account is upgraded to a Unified account, you can get the 9 | orders which occurred before the upgrade. 10 | 11 | Required args: 12 | category (string): Product type. linear, inverse, option 13 | 14 | Returns: 15 | Request results as dictionary. 16 | 17 | Additional information: 18 | https://bybit-exchange.github.io/docs/v5/pre-upgrade/order-list 19 | """ 20 | return self._submit_request( 21 | method="GET", 22 | path=f"{self.endpoint}{PreUpgrade.GET_PRE_UPGRADE_ORDER_HISTORY}", 23 | query=kwargs, 24 | auth=True, 25 | ) 26 | 27 | def get_pre_upgrade_trade_history(self, **kwargs) -> dict: 28 | """ 29 | Get users' execution records which occurred before you upgraded the 30 | account to a Unified account, sorted by execTime in descending order 31 | 32 | Required args: 33 | category (string): Product type. linear, inverse, option 34 | 35 | Returns: 36 | Request results as dictionary. 37 | 38 | Additional information: 39 | https://bybit-exchange.github.io/docs/v5/pre-upgrade/execution 40 | """ 41 | return self._submit_request( 42 | method="GET", 43 | path=f"{self.endpoint}{PreUpgrade.GET_PRE_UPGRADE_TRADE_HISTORY}", 44 | query=kwargs, 45 | auth=True, 46 | ) 47 | 48 | def get_pre_upgrade_closed_pnl(self, **kwargs) -> dict: 49 | """ 50 | Query user's closed profit and loss records from before you upgraded the 51 | account to a Unified account. The results are sorted by createdTime in 52 | descending order. 53 | 54 | Required args: 55 | category (string): Product type linear, inverse 56 | symbol (string): Symbol name 57 | 58 | Returns: 59 | Request results as dictionary. 60 | 61 | Additional information: 62 | https://bybit-exchange.github.io/docs/v5/pre-upgrade/close-pnl 63 | """ 64 | return self._submit_request( 65 | method="GET", 66 | path=f"{self.endpoint}{PreUpgrade.GET_PRE_UPGRADE_CLOSED_PNL}", 67 | query=kwargs, 68 | auth=True, 69 | ) 70 | 71 | def get_pre_upgrade_transaction_log(self, **kwargs) -> dict: 72 | """ 73 | Query transaction logs which occurred in the USDC Derivatives wallet 74 | before the account was upgraded to a Unified account. 75 | 76 | Required args: 77 | category (string): Product type. linear,option 78 | 79 | Returns: 80 | Request results as dictionary. 81 | 82 | Additional information: 83 | https://bybit-exchange.github.io/docs/v5/pre-upgrade/transaction-log 84 | """ 85 | return self._submit_request( 86 | method="GET", 87 | path=f"{self.endpoint}{PreUpgrade.GET_PRE_UPGRADE_TRANSACTION_LOG}", 88 | query=kwargs, 89 | auth=True, 90 | ) 91 | 92 | def get_pre_upgrade_option_delivery_record(self, **kwargs) -> dict: 93 | """ 94 | Query delivery records of Option before you upgraded the account to a 95 | Unified account, sorted by deliveryTime in descending order 96 | Required args: 97 | category (string): Product type. option 98 | 99 | Returns: 100 | Request results as dictionary. 101 | 102 | Additional information: 103 | https://bybit-exchange.github.io/docs/v5/pre-upgrade/delivery 104 | """ 105 | return self._submit_request( 106 | method="GET", 107 | path=f"{self.endpoint}{PreUpgrade.GET_PRE_UPGRADE_OPTION_DELIVERY_RECORD}", 108 | query=kwargs, 109 | auth=True, 110 | ) 111 | 112 | def get_pre_upgrade_usdc_session_settlement(self, **kwargs) -> dict: 113 | """ 114 | Required args: 115 | category (string): Product type. linear 116 | 117 | Returns: 118 | Request results as dictionary. 119 | 120 | Additional information: 121 | https://bybit-exchange.github.io/docs/v5/pre-upgrade/settlement 122 | """ 123 | return self._submit_request( 124 | method="GET", 125 | path=f"{self.endpoint}{PreUpgrade.GET_PRE_UPGRADE_USDC_SESSION_SETTLEMENT}", 126 | query=kwargs, 127 | auth=True, 128 | ) 129 | 130 | 131 | -------------------------------------------------------------------------------- /pybit/_v5_spot_leverage_token.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .spot_leverage_token import SpotLeverageToken 3 | 4 | 5 | class SpotLeverageHTTP(_V5HTTPManager): 6 | def get_leveraged_token_info(self, **kwargs): 7 | """Query leverage token information 8 | 9 | Returns: 10 | Request results as dictionary. 11 | 12 | Additional information: 13 | https://bybit-exchange.github.io/docs/v5/lt/leverage-token-info 14 | """ 15 | return self._submit_request( 16 | method="GET", 17 | path=f"{self.endpoint}{SpotLeverageToken.GET_LEVERAGED_TOKEN_INFO}", 18 | query=kwargs, 19 | ) 20 | 21 | def get_leveraged_token_market(self, **kwargs): 22 | """Get leverage token market information 23 | 24 | Required args: 25 | ltCoin (string): Abbreviation of the LT, such as BTC3L 26 | 27 | Returns: 28 | Request results as dictionary. 29 | 30 | Additional information: 31 | https://bybit-exchange.github.io/docs/v5/lt/leverage-token-reference 32 | """ 33 | return self._submit_request( 34 | method="GET", 35 | path=f"{self.endpoint}{SpotLeverageToken.GET_LEVERAGED_TOKEN_MARKET}", 36 | query=kwargs, 37 | ) 38 | 39 | def purchase_leveraged_token(self, **kwargs): 40 | """Purchase levearge token 41 | 42 | Required args: 43 | ltCoin (string): Abbreviation of the LT, such as BTC3L 44 | ltAmount (string): Purchase amount 45 | 46 | Returns: 47 | Request results as dictionary. 48 | 49 | Additional information: 50 | https://bybit-exchange.github.io/docs/v5/lt/purchase 51 | """ 52 | return self._submit_request( 53 | method="POST", 54 | path=f"{self.endpoint}{SpotLeverageToken.PURCHASE}", 55 | query=kwargs, 56 | auth=True, 57 | ) 58 | 59 | def redeem_leveraged_token(self, **kwargs): 60 | """Redeem leverage token 61 | 62 | Required args: 63 | ltCoin (string): Abbreviation of the LT, such as BTC3L 64 | quantity (string): Redeem quantity of LT 65 | 66 | Returns: 67 | Request results as dictionary. 68 | 69 | Additional information: 70 | https://bybit-exchange.github.io/docs/v5/lt/redeem 71 | """ 72 | return self._submit_request( 73 | method="POST", 74 | path=f"{self.endpoint}{SpotLeverageToken.REDEEM}", 75 | query=kwargs, 76 | auth=True, 77 | ) 78 | 79 | def get_purchase_redemption_records(self, **kwargs): 80 | """Get purchase or redeem history 81 | 82 | Required args: 83 | 84 | Returns: 85 | Request results as dictionary. 86 | 87 | Additional information: 88 | https://bybit-exchange.github.io/docs/v5/lt/order-record 89 | """ 90 | return self._submit_request( 91 | method="GET", 92 | path=f"{self.endpoint}{SpotLeverageToken.GET_PURCHASE_REDEMPTION_RECORDS}", 93 | query=kwargs, 94 | auth=True, 95 | ) 96 | -------------------------------------------------------------------------------- /pybit/_v5_spot_margin_trade.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .spot_margin_trade import SpotMarginTrade 3 | 4 | 5 | class SpotMarginTradeHTTP(_V5HTTPManager): 6 | def spot_margin_trade_get_vip_margin_data(self, **kwargs): 7 | """ 8 | Returns: 9 | Request results as dictionary. 10 | 11 | Additional information: 12 | https://bybit-exchange.github.io/docs/v5/spot-margin-uta/vip-margin 13 | """ 14 | return self._submit_request( 15 | method="GET", 16 | path=f"{self.endpoint}{SpotMarginTrade.VIP_MARGIN_DATA}", 17 | query=kwargs, 18 | ) 19 | 20 | def spot_margin_trade_toggle_margin_trade(self, **kwargs): 21 | """UTA only. Turn spot margin trade on / off. 22 | 23 | Required args: 24 | spotMarginMode (string): 1: on, 0: off 25 | 26 | Returns: 27 | Request results as dictionary. 28 | 29 | Additional information: 30 | https://bybit-exchange.github.io/docs/v5/spot-margin-uta/switch-mode 31 | """ 32 | return self._submit_request( 33 | method="POST", 34 | path=f"{self.endpoint}{SpotMarginTrade.TOGGLE_MARGIN_TRADE}", 35 | query=kwargs, 36 | auth=True, 37 | ) 38 | 39 | def spot_margin_trade_set_leverage(self, **kwargs): 40 | """UTA only. Set the user's maximum leverage in spot cross margin 41 | 42 | Required args: 43 | leverage (string): Leverage. [2, 5]. 44 | 45 | Returns: 46 | Request results as dictionary. 47 | 48 | Additional information: 49 | https://bybit-exchange.github.io/docs/v5/spot-margin-uta/set-leverage 50 | """ 51 | return self._submit_request( 52 | method="POST", 53 | path=f"{self.endpoint}{SpotMarginTrade.SET_LEVERAGE}", 54 | query=kwargs, 55 | auth=True, 56 | ) 57 | 58 | def spot_margin_trade_get_status_and_leverage(self): 59 | """ 60 | Returns: 61 | Request results as dictionary. 62 | 63 | Additional information: 64 | https://bybit-exchange.github.io/docs/v5/spot-margin-uta/status 65 | """ 66 | return self._submit_request( 67 | method="GET", 68 | path=f"{self.endpoint}{SpotMarginTrade.STATUS_AND_LEVERAGE}", 69 | auth=True, 70 | ) 71 | 72 | def spot_margin_trade_get_historical_interest_rate(self, **kwargs): 73 | """UTA only. Queries up to six months of borrowing interest rates. 74 | 75 | Required args: 76 | currency (string): Coin name, uppercase only. 77 | 78 | Returns: 79 | Request results as dictionary. 80 | 81 | Additional information: 82 | https://bybit-exchange.github.io/docs/v5/spot-margin-uta/historical-interest 83 | """ 84 | return self._submit_request( 85 | method="GET", 86 | path=f"{self.endpoint}{SpotMarginTrade.HISTORICAL_INTEREST}", 87 | query=kwargs, 88 | auth=True, 89 | ) 90 | 91 | def spot_margin_trade_normal_get_vip_margin_data(self, **kwargs): 92 | """ 93 | Returns: 94 | Request results as dictionary. 95 | 96 | Additional information: 97 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/vip-margin 98 | """ 99 | return self._submit_request( 100 | method="GET", 101 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_MARGIN_COIN_INFO}", 102 | query=kwargs, 103 | ) 104 | 105 | def spot_margin_trade_normal_get_margin_coin_info(self, **kwargs): 106 | """Normal (non-UTA) account only. Turn on / off spot margin trade 107 | 108 | Required args: 109 | switch (string): 1: on, 0: off 110 | 111 | Returns: 112 | Request results as dictionary. 113 | 114 | Additional information: 115 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/margin-data 116 | """ 117 | return self._submit_request( 118 | method="GET", 119 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_MARGIN_COIN_INFO}", 120 | query=kwargs, 121 | ) 122 | 123 | def spot_margin_trade_normal_get_borrowable_coin_info(self, **kwargs): 124 | """Normal (non-UTA) account only. 125 | 126 | Returns: 127 | Request results as dictionary. 128 | 129 | Additional information: 130 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/borrowable-data 131 | """ 132 | return self._submit_request( 133 | method="GET", 134 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_BORROWABLE_COIN_INFO}", 135 | query=kwargs, 136 | ) 137 | 138 | def spot_margin_trade_normal_get_interest_quota(self, **kwargs): 139 | """Normal (non-UTA) account only. 140 | 141 | Required args: 142 | coin (string): Coin name 143 | 144 | Returns: 145 | Request results as dictionary. 146 | 147 | Additional information: 148 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/interest-quota 149 | """ 150 | return self._submit_request( 151 | method="GET", 152 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_INTEREST_QUOTA}", 153 | query=kwargs, 154 | auth=True, 155 | ) 156 | 157 | def spot_margin_trade_normal_get_loan_account_info(self, **kwargs): 158 | """Normal (non-UTA) account only. 159 | 160 | Returns: 161 | Request results as dictionary. 162 | 163 | Additional information: 164 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/account-info 165 | """ 166 | return self._submit_request( 167 | method="GET", 168 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_LOAN_ACCOUNT_INFO}", 169 | query=kwargs, 170 | auth=True, 171 | ) 172 | 173 | def spot_margin_trade_normal_borrow(self, **kwargs): 174 | """Normal (non-UTA) account only. 175 | 176 | Required args: 177 | coin (string): Coin name 178 | qty (string): Amount to borrow 179 | 180 | Returns: 181 | Request results as dictionary. 182 | 183 | Additional information: 184 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/borrow 185 | """ 186 | return self._submit_request( 187 | method="POST", 188 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_BORROW}", 189 | query=kwargs, 190 | auth=True, 191 | ) 192 | 193 | def spot_margin_trade_normal_repay(self, **kwargs): 194 | """Normal (non-UTA) account only. 195 | 196 | Required args: 197 | coin (string): Coin name 198 | 199 | Returns: 200 | Request results as dictionary. 201 | 202 | Additional information: 203 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/repay 204 | """ 205 | return self._submit_request( 206 | method="POST", 207 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_REPAY}", 208 | query=kwargs, 209 | auth=True, 210 | ) 211 | 212 | def spot_margin_trade_normal_get_borrow_order_detail(self, **kwargs): 213 | """Normal (non-UTA) account only. 214 | 215 | Returns: 216 | Request results as dictionary. 217 | 218 | Additional information: 219 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/borrow-order 220 | """ 221 | return self._submit_request( 222 | method="GET", 223 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_BORROW_ORDER_DETAIL}", 224 | query=kwargs, 225 | auth=True, 226 | ) 227 | 228 | def spot_margin_trade_normal_get_repayment_order_detail(self, **kwargs): 229 | """Normal (non-UTA) account only. 230 | 231 | Returns: 232 | Request results as dictionary. 233 | 234 | Additional information: 235 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/repay-order 236 | """ 237 | return self._submit_request( 238 | method="GET", 239 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_GET_REPAYMENT_ORDER_DETAIL}", 240 | query=kwargs, 241 | auth=True, 242 | ) 243 | 244 | def spot_margin_trade_normal_toggle_margin_trade(self, **kwargs): 245 | """Normal (non-UTA) account only. 246 | 247 | Required args: 248 | switch (integer): 1: on, 0: off 249 | 250 | Returns: 251 | Request results as dictionary. 252 | 253 | Additional information: 254 | https://bybit-exchange.github.io/docs/v5/spot-margin-normal/switch-mode 255 | """ 256 | return self._submit_request( 257 | method="POST", 258 | path=f"{self.endpoint}{SpotMarginTrade.NORMAL_TOGGLE_MARGIN_TRADE}", 259 | query=kwargs, 260 | auth=True, 261 | ) 262 | -------------------------------------------------------------------------------- /pybit/_v5_spread.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from ._websocket_stream import _V5WebSocketManager 3 | from .spread import Spread 4 | 5 | 6 | WSS_NAME = "Spread Trading" 7 | PUBLIC_WSS = "wss://{SUBDOMAIN}.{DOMAIN}.com/v5/public/spread" 8 | 9 | 10 | class SpreadHTTP(_V5HTTPManager): 11 | def get_instruments_info(self, **kwargs) -> dict: 12 | """ 13 | Returns: 14 | Request results as dictionary. 15 | 16 | Additional information: 17 | https://bybit-exchange.github.io/docs/v5/spread/market/instrument 18 | """ 19 | request = self._submit_request( 20 | method="GET", 21 | path=f"{self.endpoint}{Spread.GET_INSTRUMENTS_INFO}", 22 | query=kwargs, 23 | ) 24 | return request 25 | 26 | def get_orderbook(self, **kwargs) -> dict: 27 | """ 28 | Returns: 29 | Request results as dictionary. 30 | 31 | Additional information: 32 | https://bybit-exchange.github.io/docs/v5/spread/market/orderbook 33 | """ 34 | return self._submit_request( 35 | method="GET", 36 | path=f"{self.endpoint}{Spread.GET_ORDERBOOK}", 37 | query=kwargs, 38 | ) 39 | 40 | def get_tickers(self, **kwargs) -> dict: 41 | """ 42 | Returns: 43 | Request results as dictionary. 44 | 45 | Additional information: 46 | https://bybit-exchange.github.io/docs/v5/spread/market/tickers 47 | """ 48 | return self._submit_request( 49 | method="GET", 50 | path=f"{self.endpoint}{Spread.GET_TICKERS}", 51 | query=kwargs, 52 | ) 53 | 54 | def get_public_trade_history(self, **kwargs) -> dict: 55 | """ 56 | Returns: 57 | Request results as dictionary. 58 | 59 | Additional information: 60 | https://bybit-exchange.github.io/docs/v5/spread/market/recent-trade 61 | """ 62 | return self._submit_request( 63 | method="GET", 64 | path=f"{self.endpoint}{Spread.GET_PUBLIC_TRADING_HISTORY}", 65 | query=kwargs, 66 | ) 67 | 68 | def place_order(self, **kwargs) -> dict: 69 | """ 70 | Required args: 71 | category (string): Product type Unified account: spot, linear, optionNormal account: linear, inverse. Please note that category is not involved with business logic 72 | symbol (string): Symbol name 73 | side (string): Buy, Sell 74 | orderType (string): Market, Limit 75 | qty (string): Order quantity 76 | Returns: 77 | Request results as dictionary. 78 | 79 | Additional information: 80 | https://bybit-exchange.github.io/docs/v5/spread/trade/create-order 81 | """ 82 | return self._submit_request( 83 | method="POST", 84 | path=f"{self.endpoint}{Spread.PLACE_ORDER}", 85 | query=kwargs, 86 | auth=True, 87 | ) 88 | 89 | def amend_order(self, **kwargs) -> dict: 90 | """ 91 | Required args: 92 | symbol (string): Spread combination symbol name 93 | 94 | Returns: 95 | Request results as dictionary. 96 | 97 | Additional information: 98 | https://bybit-exchange.github.io/docs/v5/spread/trade/amend-order 99 | """ 100 | return self._submit_request( 101 | method="POST", 102 | path=f"{self.endpoint}{Spread.AMEND_ORDER}", 103 | query=kwargs, 104 | auth=True, 105 | ) 106 | 107 | def cancel_order(self, **kwargs) -> dict: 108 | """ 109 | Returns: 110 | Request results as dictionary. 111 | 112 | Additional information: 113 | https://bybit-exchange.github.io/docs/v5/spread/trade/cancel-order 114 | """ 115 | return self._submit_request( 116 | method="POST", 117 | path=f"{self.endpoint}{Spread.CANCEL_ORDER}", 118 | query=kwargs, 119 | auth=True, 120 | ) 121 | 122 | def cancel_all_orders(self, **kwargs) -> dict: 123 | """ 124 | Returns: 125 | Request results as dictionary. 126 | 127 | Additional information: 128 | https://bybit-exchange.github.io/docs/v5/spread/trade/cancel-all 129 | """ 130 | return self._submit_request( 131 | method="POST", 132 | path=f"{self.endpoint}{Spread.CANCEL_ALL_ORDERS}", 133 | query=kwargs, 134 | auth=True, 135 | ) 136 | 137 | def get_open_orders(self, **kwargs) -> dict: 138 | """ 139 | Returns: 140 | Request results as dictionary. 141 | 142 | Additional information: 143 | https://bybit-exchange.github.io/docs/v5/spread/trade/open-order 144 | """ 145 | return self._submit_request( 146 | method="GET", 147 | path=f"{self.endpoint}{Spread.GET_OPEN_ORDERS}", 148 | query=kwargs, 149 | auth=True, 150 | ) 151 | 152 | def get_order_history(self, **kwargs) -> dict: 153 | """ 154 | Returns: 155 | Request results as dictionary. 156 | 157 | Additional information: 158 | https://bybit-exchange.github.io/docs/v5/spread/trade/order-history 159 | """ 160 | return self._submit_request( 161 | method="GET", 162 | path=f"{self.endpoint}{Spread.GET_ORDER_HISTORY}", 163 | query=kwargs, 164 | auth=True, 165 | ) 166 | 167 | def get_trade_history(self, **kwargs) -> dict: 168 | """ 169 | Returns: 170 | Request results as dictionary. 171 | 172 | Additional information: 173 | https://bybit-exchange.github.io/docs/v5/spread/trade/trade-history 174 | """ 175 | return self._submit_request( 176 | method="GET", 177 | path=f"{self.endpoint}{Spread.GET_TRADE_HISTORY}", 178 | query=kwargs, 179 | auth=True, 180 | ) 181 | 182 | 183 | class _V5WebSocketSpreadTrading(_V5WebSocketManager): 184 | def __init__( 185 | self, 186 | **kwargs, 187 | ): 188 | super().__init__(WSS_NAME, **kwargs) 189 | self.WS_URL = PUBLIC_WSS 190 | self._connect(self.WS_URL) 191 | -------------------------------------------------------------------------------- /pybit/_v5_trade.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .trade import Trade 3 | 4 | 5 | class TradeHTTP(_V5HTTPManager): 6 | def place_order(self, **kwargs): 7 | """This method supports to create the order for spot, spot margin, linear perpetual, inverse futures and options. 8 | 9 | Required args: 10 | category (string): Product type Unified account: spot, linear, optionNormal account: linear, inverse. Please note that category is not involved with business logic 11 | symbol (string): Symbol name 12 | side (string): Buy, Sell 13 | orderType (string): Market, Limit 14 | qty (string): Order quantity 15 | Returns: 16 | Request results as dictionary. 17 | 18 | Additional information: 19 | https://bybit-exchange.github.io/docs/v5/order/create-order 20 | """ 21 | return self._submit_request( 22 | method="POST", 23 | path=f"{self.endpoint}{Trade.PLACE_ORDER}", 24 | query=kwargs, 25 | auth=True, 26 | ) 27 | 28 | def amend_order(self, **kwargs): 29 | """Unified account covers: Linear contract / Options 30 | Normal account covers: USDT perpetual / Inverse perpetual / Inverse futures 31 | 32 | Required args: 33 | category (string): Product type Unified account: spot, linear, optionNormal account: linear, inverse. Please note that category is not involved with business logic 34 | symbol (string): Symbol name 35 | 36 | Returns: 37 | Request results as dictionary. 38 | 39 | Additional information: 40 | https://bybit-exchange.github.io/docs/v5/order/amend-order 41 | """ 42 | return self._submit_request( 43 | method="POST", 44 | path=f"{self.endpoint}{Trade.AMEND_ORDER}", 45 | query=kwargs, 46 | auth=True, 47 | ) 48 | 49 | def cancel_order(self, **kwargs): 50 | """Unified account covers: Spot / Linear contract / Options 51 | Normal account covers: USDT perpetual / Inverse perpetual / Inverse futures 52 | 53 | Required args: 54 | category (string): Product type Unified account: spot, linear, optionNormal account: linear, inverse. Please note that category is not involved with business logic 55 | symbol (string): Symbol name 56 | orderId (string): Order ID. Either orderId or orderLinkId is required 57 | orderLinkId (string): User customised order ID. Either orderId or orderLinkId is required 58 | 59 | Returns: 60 | Request results as dictionary. 61 | 62 | Additional information: 63 | https://bybit-exchange.github.io/docs/v5/order/cancel-order 64 | """ 65 | return self._submit_request( 66 | method="POST", 67 | path=f"{self.endpoint}{Trade.CANCEL_ORDER}", 68 | query=kwargs, 69 | auth=True, 70 | ) 71 | 72 | def get_open_orders(self, **kwargs): 73 | """Query unfilled or partially filled orders in real-time. To query older order records, please use the order history interface. 74 | 75 | Required args: 76 | category (string): Product type Unified account: spot, linear, optionNormal account: linear, inverse. Please note that category is not involved with business logic 77 | 78 | Returns: 79 | Request results as dictionary. 80 | 81 | Additional information: 82 | https://bybit-exchange.github.io/docs/v5/order/open-order 83 | """ 84 | return self._submit_request( 85 | method="GET", 86 | path=f"{self.endpoint}{Trade.GET_OPEN_ORDERS}", 87 | query=kwargs, 88 | auth=True, 89 | ) 90 | 91 | def cancel_all_orders(self, **kwargs): 92 | """Cancel all open orders 93 | 94 | Required args: 95 | category (string): Product type 96 | Unified account: spot, linear, option 97 | Normal account: linear, inverse. 98 | 99 | Please note that category is not involved with business logic. If cancel all by baseCoin, it will cancel all linear & inverse orders 100 | 101 | Returns: 102 | Request results as dictionary. 103 | 104 | Additional information: 105 | https://bybit-exchange.github.io/docs/v5/order/cancel-all 106 | """ 107 | return self._submit_request( 108 | method="POST", 109 | path=f"{self.endpoint}{Trade.CANCEL_ALL_ORDERS}", 110 | query=kwargs, 111 | auth=True, 112 | ) 113 | 114 | def get_order_history(self, **kwargs): 115 | """Query order history. As order creation/cancellation is asynchronous, the data returned from this endpoint may delay. 116 | If you want to get real-time order information, you could query this endpoint or rely on the websocket stream (recommended). 117 | 118 | Required args: 119 | category (string): Product type 120 | Unified account: spot, linear, option 121 | Normal account: linear, inverse. 122 | 123 | Please note that category is not involved with business logic 124 | 125 | Returns: 126 | Request results as dictionary. 127 | 128 | Additional information: 129 | https://bybit-exchange.github.io/docs/v5/order/order-list 130 | """ 131 | return self._submit_request( 132 | method="GET", 133 | path=f"{self.endpoint}{Trade.GET_ORDER_HISTORY}", 134 | query=kwargs, 135 | auth=True, 136 | ) 137 | 138 | def place_batch_order(self, **kwargs): 139 | """Covers: Option (Unified Account) 140 | 141 | Required args: 142 | category (string): Product type. option 143 | request (array): Object 144 | > symbol (string): Symbol name 145 | > side (string): Buy, Sell 146 | > orderType (string): Market, Limit 147 | > qty (string): Order quantity 148 | 149 | Returns: 150 | Request results as dictionary. 151 | 152 | Additional information: 153 | https://bybit-exchange.github.io/docs/v5/order/batch-place 154 | """ 155 | return self._submit_request( 156 | method="POST", 157 | path=f"{self.endpoint}{Trade.BATCH_PLACE_ORDER}", 158 | query=kwargs, 159 | auth=True, 160 | ) 161 | 162 | def amend_batch_order(self, **kwargs): 163 | """Covers: Option (Unified Account) 164 | 165 | Required args: 166 | category (string): Product type. option 167 | request (array): Object 168 | > symbol (string): Symbol name 169 | > orderId (string): Order ID. Either orderId or orderLinkId is required 170 | > orderLinkId (string): User customised order ID. Either orderId or orderLinkId is required 171 | 172 | Returns: 173 | Request results as dictionary. 174 | 175 | Additional information: 176 | https://bybit-exchange.github.io/docs/v5/order/batch-amend 177 | """ 178 | return self._submit_request( 179 | method="POST", 180 | path=f"{self.endpoint}{Trade.BATCH_AMEND_ORDER}", 181 | query=kwargs, 182 | auth=True, 183 | ) 184 | 185 | def cancel_batch_order(self, **kwargs): 186 | """This endpoint allows you to cancel more than one open order in a single request. 187 | 188 | Required args: 189 | category (string): Product type. option 190 | request (array): Object 191 | > symbol (string): Symbol name 192 | 193 | Returns: 194 | Request results as dictionary. 195 | 196 | Additional information: 197 | https://bybit-exchange.github.io/docs/v5/order/batch-cancel 198 | """ 199 | return self._submit_request( 200 | method="POST", 201 | path=f"{self.endpoint}{Trade.BATCH_CANCEL_ORDER}", 202 | query=kwargs, 203 | auth=True, 204 | ) 205 | 206 | def get_borrow_quota(self, **kwargs): 207 | """Query the qty and amount of borrowable coins in spot account. 208 | 209 | Required args: 210 | category (string): Product type. spot 211 | symbol (string): Symbol name 212 | side (string): Transaction side. Buy,Sell 213 | 214 | Returns: 215 | Request results as dictionary. 216 | 217 | Additional information: 218 | https://bybit-exchange.github.io/docs/v5/order/spot-borrow-quota 219 | """ 220 | return self._submit_request( 221 | method="GET", 222 | path=f"{self.endpoint}{Trade.GET_BORROW_QUOTA}", 223 | query=kwargs, 224 | auth=True, 225 | ) 226 | 227 | def set_dcp(self, **kwargs): 228 | """Covers: Option (Unified Account) 229 | 230 | Required args: 231 | timeWindow (integer): Disconnection timing window time. [10, 300], unit: second 232 | 233 | Returns: 234 | Request results as dictionary. 235 | 236 | Additional information: 237 | https://bybit-exchange.github.io/docs/v5/order/dcp 238 | """ 239 | return self._submit_request( 240 | method="POST", 241 | path=f"{self.endpoint}{Trade.SET_DCP}", 242 | query=kwargs, 243 | auth=True, 244 | ) 245 | -------------------------------------------------------------------------------- /pybit/_v5_user.py: -------------------------------------------------------------------------------- 1 | from ._http_manager import _V5HTTPManager 2 | from .user import User 3 | 4 | 5 | class UserHTTP(_V5HTTPManager): 6 | def create_sub_uid(self, **kwargs): 7 | """Create a new sub user id. Use master user's api key only. 8 | 9 | Required args: 10 | username (string): Give a username of the new sub user id. 6-16 characters, must include both numbers and letters.cannot be the same as the exist or deleted one. 11 | memberType (integer): 1: normal sub account, 6: custodial sub account 12 | 13 | Returns: 14 | Request results as dictionary. 15 | 16 | Additional information: 17 | https://bybit-exchange.github.io/docs/v5/user/create-subuid 18 | """ 19 | return self._submit_request( 20 | method="POST", 21 | path=f"{self.endpoint}{User.CREATE_SUB_UID}", 22 | query=kwargs, 23 | auth=True, 24 | ) 25 | 26 | def create_sub_api_key(self, **kwargs): 27 | """To create new API key for those newly created sub UID. Use master user's api key only. 28 | 29 | Required args: 30 | subuid (integer): Sub user Id 31 | readOnly (integer): 0: Read and Write. 1: Read only 32 | permissions (Object): Tick the types of permission. one of below types must be passed, otherwise the error is thrown 33 | 34 | Returns: 35 | Request results as dictionary. 36 | 37 | Additional information: 38 | https://bybit-exchange.github.io/docs/v5/user/create-subuid-apikey 39 | """ 40 | return self._submit_request( 41 | method="POST", 42 | path=f"{self.endpoint}{User.CREATE_SUB_API_KEY}", 43 | query=kwargs, 44 | auth=True, 45 | ) 46 | 47 | def get_sub_uid_list(self, **kwargs): 48 | """Get all sub uid of master account. Use master user's api key only. 49 | 50 | Returns: 51 | Request results as dictionary. 52 | 53 | Additional information: 54 | https://bybit-exchange.github.io/docs/v5/user/subuid-list 55 | """ 56 | return self._submit_request( 57 | method="GET", 58 | path=f"{self.endpoint}{User.GET_SUB_UID_LIST}", 59 | query=kwargs, 60 | auth=True, 61 | ) 62 | 63 | def freeze_sub_uid(self, **kwargs): 64 | """Froze sub uid. Use master user's api key only. 65 | 66 | Required args: 67 | subuid (integer): Sub user Id 68 | frozen (integer): 0: unfreeze, 1: freeze 69 | 70 | Returns: 71 | Request results as dictionary. 72 | 73 | Additional information: 74 | https://bybit-exchange.github.io/docs/v5/user/froze-subuid 75 | """ 76 | return self._submit_request( 77 | method="POST", 78 | path=f"{self.endpoint}{User.FREEZE_SUB_UID}", 79 | query=kwargs, 80 | auth=True, 81 | ) 82 | 83 | def get_api_key_information(self, **kwargs): 84 | """Get the information of the api key. Use the api key pending to be checked to call the endpoint. Both master and sub user's api key are applicable. 85 | 86 | Returns: 87 | Request results as dictionary. 88 | 89 | Additional information: 90 | https://bybit-exchange.github.io/docs/v5/user/apikey-info 91 | """ 92 | return self._submit_request( 93 | method="GET", 94 | path=f"{self.endpoint}{User.GET_API_KEY_INFORMATION}", 95 | query=kwargs, 96 | auth=True, 97 | ) 98 | 99 | def modify_master_api_key(self, **kwargs): 100 | """Modify the settings of master api key. Use the api key pending to be modified to call the endpoint. Use master user's api key only. 101 | 102 | Returns: 103 | Request results as dictionary. 104 | 105 | Additional information: 106 | https://bybit-exchange.github.io/docs/v5/user/modify-master-apikey 107 | """ 108 | return self._submit_request( 109 | method="POST", 110 | path=f"{self.endpoint}{User.MODIFY_MASTER_API_KEY}", 111 | query=kwargs, 112 | auth=True, 113 | ) 114 | 115 | def modify_sub_api_key(self, **kwargs): 116 | """Modify the settings of sub api key. Use the api key pending to be modified to call the endpoint. Use sub user's api key only. 117 | 118 | Returns: 119 | Request results as dictionary. 120 | 121 | Additional information: 122 | https://bybit-exchange.github.io/docs/v5/user/modify-sub-apikey 123 | """ 124 | return self._submit_request( 125 | method="POST", 126 | path=f"{self.endpoint}{User.MODIFY_SUB_API_KEY}", 127 | query=kwargs, 128 | auth=True, 129 | ) 130 | 131 | def delete_master_api_key(self, **kwargs): 132 | """Delete the api key of master account. Use the api key pending to be delete to call the endpoint. Use master user's api key only. 133 | 134 | Returns: 135 | Request results as dictionary. 136 | 137 | Additional information: 138 | https://bybit-exchange.github.io/docs/v5/user/rm-master-apikey 139 | """ 140 | return self._submit_request( 141 | method="POST", 142 | path=f"{self.endpoint}{User.DELETE_MASTER_API_KEY}", 143 | query=kwargs, 144 | auth=True, 145 | ) 146 | 147 | def delete_sub_api_key(self, **kwargs): 148 | """Delete the api key of sub account. Use the api key pending to be delete to call the endpoint. Use sub user's api key only. 149 | 150 | Returns: 151 | Request results as dictionary. 152 | 153 | Additional information: 154 | https://bybit-exchange.github.io/docs/v5/user/rm-sub-apikey 155 | """ 156 | return self._submit_request( 157 | method="POST", 158 | path=f"{self.endpoint}{User.DELETE_SUB_API_KEY}", 159 | query=kwargs, 160 | auth=True, 161 | ) 162 | 163 | def delete_sub_uid(self, **kwargs): 164 | """Delete a sub UID. Before deleting the sub UID, please make sure there is no asset. Use master user's api key only. 165 | 166 | Required args: 167 | subMemberId (integer): Sub UID 168 | 169 | Returns: 170 | Request results as dictionary. 171 | 172 | Additional information: 173 | https://bybit-exchange.github.io/docs/v5/user/rm-subuid 174 | """ 175 | return self._submit_request( 176 | method="POST", 177 | path=f"{self.endpoint}{User.DELETE_SUB_UID}", 178 | query=kwargs, 179 | auth=True, 180 | ) 181 | 182 | def get_all_sub_api_keys(self, **kwargs): 183 | """Query all api keys information of a sub UID. 184 | 185 | Required args: 186 | subMemberId (integer): Sub UID 187 | 188 | Returns: 189 | Request results as dictionary. 190 | 191 | Additional information: 192 | https://bybit-exchange.github.io/docs/v5/user/list-sub-apikeys 193 | """ 194 | return self._submit_request( 195 | method="GET", 196 | path=f"{self.endpoint}{User.GET_ALL_SUB_API_KEYS}", 197 | query=kwargs, 198 | auth=True, 199 | ) 200 | 201 | def get_affiliate_user_info(self, **kwargs): 202 | """This API is used for affiliate to get their users information 203 | 204 | Required args: 205 | uid (integer): The master account uid of affiliate's client 206 | 207 | Returns: 208 | Request results as dictionary. 209 | 210 | Additional information: 211 | https://bybit-exchange.github.io/docs/v5/user/affiliate-info 212 | """ 213 | return self._submit_request( 214 | method="GET", 215 | path=f"{self.endpoint}{User.GET_AFFILIATE_USER_INFO}", 216 | query=kwargs, 217 | auth=True, 218 | ) 219 | 220 | def get_uid_wallet_type(self, **kwargs): 221 | """Get available wallet types for the master account or sub account 222 | 223 | Returns: 224 | Request results as dictionary. 225 | 226 | Additional information: 227 | https://bybit-exchange.github.io/docs/v5/user/wallet-type 228 | """ 229 | return self._submit_request( 230 | method="GET", 231 | path=f"{self.endpoint}{User.GET_UID_WALLET_TYPE}", 232 | query=kwargs, 233 | auth=True, 234 | ) 235 | -------------------------------------------------------------------------------- /pybit/_websocket_stream.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | import threading 3 | import time 4 | import json 5 | from ._http_manager import generate_signature 6 | import logging 7 | import copy 8 | from uuid import uuid4 9 | from . import _helpers 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | SUBDOMAIN_TESTNET = "stream-testnet" 16 | SUBDOMAIN_MAINNET = "stream" 17 | DEMO_SUBDOMAIN_TESTNET = "stream-demo-testnet" 18 | DEMO_SUBDOMAIN_MAINNET = "stream-demo" 19 | DOMAIN_MAIN = "bybit" 20 | DOMAIN_ALT = "bytick" 21 | TLD_MAIN = "com" 22 | 23 | 24 | class _WebSocketManager: 25 | def __init__( 26 | self, 27 | _callback_function, 28 | ws_name, 29 | testnet, 30 | tld="", 31 | domain="", 32 | demo=False, 33 | rsa_authentication=False, 34 | api_key=None, 35 | api_secret=None, 36 | ping_interval=20, 37 | ping_timeout=10, 38 | retries=10, 39 | restart_on_error=True, 40 | trace_logging=False, 41 | private_auth_expire=1, 42 | ): 43 | self.testnet = testnet 44 | self.domain = domain 45 | self.tld = tld 46 | self.rsa_authentication = rsa_authentication 47 | self.demo = demo 48 | # Set API keys. 49 | self.api_key = api_key 50 | self.api_secret = api_secret 51 | 52 | self.callback = _callback_function 53 | self.ws_name = ws_name 54 | if api_key: 55 | self.ws_name += " (Auth)" 56 | 57 | # Delta time for private auth expiration in seconds 58 | self.private_auth_expire = private_auth_expire 59 | 60 | # Setup the callback directory following the format: 61 | # { 62 | # "topic_name": function 63 | # } 64 | self.callback_directory = {} 65 | 66 | # Record the subscriptions made so that we can resubscribe if the WSS 67 | # connection is broken. 68 | self.subscriptions = [] 69 | 70 | # Set ping settings. 71 | self.ping_interval = ping_interval 72 | self.ping_timeout = ping_timeout 73 | self.custom_ping_message = json.dumps({"op": "ping"}) 74 | self.retries = retries 75 | 76 | # Other optional data handling settings. 77 | self.handle_error = restart_on_error 78 | 79 | # Enable websocket-client's trace logging for extra debug information 80 | # on the websocket connection, including the raw sent & recv messages 81 | websocket.enableTrace(trace_logging) 82 | 83 | # Set initial state, initialize dictionary and connect. 84 | self._reset() 85 | self.attempting_connection = False 86 | 87 | def _on_open(self): 88 | """ 89 | Log WS open. 90 | """ 91 | logger.debug(f"WebSocket {self.ws_name} opened.") 92 | 93 | def _on_message(self, message): 94 | """ 95 | Parse incoming messages. 96 | """ 97 | message = json.loads(message) 98 | if self._is_custom_pong(message): 99 | return 100 | else: 101 | self.callback(message) 102 | 103 | def is_connected(self): 104 | try: 105 | if self.ws.sock.connected: 106 | return True 107 | else: 108 | return False 109 | except AttributeError: 110 | return False 111 | 112 | def _connect(self, url): 113 | """ 114 | Open websocket in a thread. 115 | """ 116 | 117 | def resubscribe_to_topics(): 118 | if not self.subscriptions: 119 | # There are no subscriptions to resubscribe to, probably 120 | # because this is a brand new WSS initialisation so there was 121 | # no previous WSS connection. 122 | return 123 | 124 | for req_id, subscription_message in self.subscriptions.items(): 125 | self.ws.send(subscription_message) 126 | 127 | self.attempting_connection = True 128 | 129 | # Set endpoint. 130 | subdomain = SUBDOMAIN_TESTNET if self.testnet else SUBDOMAIN_MAINNET 131 | domain = DOMAIN_MAIN if not self.domain else self.domain 132 | tld = TLD_MAIN if not self.tld else self.tld 133 | if self.demo: 134 | if self.testnet: 135 | subdomain = DEMO_SUBDOMAIN_TESTNET 136 | else: 137 | subdomain = DEMO_SUBDOMAIN_MAINNET 138 | url = url.format(SUBDOMAIN=subdomain, DOMAIN=domain, TLD=tld) 139 | self.endpoint = url 140 | 141 | # Attempt to connect for X seconds. 142 | retries = self.retries 143 | if retries == 0: 144 | infinitely_reconnect = True 145 | else: 146 | infinitely_reconnect = False 147 | 148 | while ( 149 | infinitely_reconnect or retries > 0 150 | ) and not self.is_connected(): 151 | logger.info(f"WebSocket {self.ws_name} attempting connection...") 152 | self.ws = websocket.WebSocketApp( 153 | url=url, 154 | on_message=lambda ws, msg: self._on_message(msg), 155 | on_close=lambda ws, *args: self._on_close(), 156 | on_open=lambda ws, *args: self._on_open(), 157 | on_error=lambda ws, err: self._on_error(err), 158 | on_pong=lambda ws, *args: self._on_pong(), 159 | ) 160 | 161 | # Setup the thread running WebSocketApp. 162 | self.wst = threading.Thread( 163 | target=lambda: self.ws.run_forever( 164 | ping_interval=self.ping_interval, 165 | ping_timeout=self.ping_timeout, 166 | ) 167 | ) 168 | 169 | # Configure as daemon; start. 170 | self.wst.daemon = True 171 | self.wst.start() 172 | 173 | retries -= 1 174 | while self.wst.is_alive(): 175 | if self.ws.sock and self.is_connected(): 176 | break 177 | 178 | # If connection was not successful, raise error. 179 | if not infinitely_reconnect and retries <= 0: 180 | self.exit() 181 | raise websocket.WebSocketTimeoutException( 182 | f"WebSocket {self.ws_name} ({self.endpoint}) connection " 183 | f"failed. Too many connection attempts. pybit will no " 184 | f"longer try to reconnect." 185 | ) 186 | 187 | logger.info(f"WebSocket {self.ws_name} connected") 188 | 189 | # If given an api_key, authenticate. 190 | if self.api_key and self.api_secret: 191 | self._auth() 192 | 193 | resubscribe_to_topics() 194 | self._send_initial_ping() 195 | 196 | self.attempting_connection = False 197 | 198 | def _auth(self): 199 | """ 200 | Prepares authentication signature per Bybit API specifications. 201 | """ 202 | 203 | expires = _helpers.generate_timestamp() + (self.private_auth_expire * 1000) 204 | 205 | param_str = f"GET/realtime{expires}" 206 | 207 | signature = generate_signature( 208 | self.rsa_authentication, self.api_secret, param_str 209 | ) 210 | 211 | # Authenticate with API. 212 | self.ws.send( 213 | json.dumps( 214 | {"op": "auth", "args": [self.api_key, expires, signature]} 215 | ) 216 | ) 217 | 218 | def _on_error(self, error): 219 | """ 220 | Exit on errors and raise exception, or attempt reconnect. 221 | """ 222 | if type(error).__name__ not in [ 223 | "WebSocketConnectionClosedException", 224 | "ConnectionResetError", 225 | "WebSocketTimeoutException", 226 | ]: 227 | # Raises errors not related to websocket disconnection. 228 | self.exit() 229 | raise error 230 | 231 | if not self.exited: 232 | logger.error( 233 | f"WebSocket {self.ws_name} ({self.endpoint}) " 234 | f"encountered error: {error}." 235 | ) 236 | self.exit() 237 | 238 | # Reconnect. 239 | if self.handle_error and not self.attempting_connection: 240 | self._reset() 241 | self._connect(self.endpoint) 242 | 243 | def _on_close(self): 244 | """ 245 | Log WS close. 246 | """ 247 | logger.debug(f"WebSocket {self.ws_name} closed.") 248 | 249 | def _on_pong(self): 250 | """ 251 | Sends a custom ping upon the receipt of the pong frame. 252 | 253 | The websocket library will automatically send ping frames. However, to 254 | ensure the connection to Bybit stays open, we need to send a custom 255 | ping message separately from this. When we receive the response to the 256 | ping frame, this method is called, and we will send the custom ping as 257 | a normal OPCODE_TEXT message and not an OPCODE_PING. 258 | """ 259 | self._send_custom_ping() 260 | 261 | def _send_custom_ping(self): 262 | self.ws.send(self.custom_ping_message) 263 | 264 | def _send_initial_ping(self): 265 | """https://github.com/bybit-exchange/pybit/issues/164""" 266 | timer = threading.Timer( 267 | self.ping_interval, self._send_custom_ping 268 | ) 269 | timer.start() 270 | 271 | @staticmethod 272 | def _is_custom_pong(message): 273 | """ 274 | Referring to OPCODE_TEXT pongs from Bybit, not OPCODE_PONG. 275 | """ 276 | if message.get("ret_msg") == "pong" or message.get("op") == "pong": 277 | return True 278 | 279 | def _reset(self): 280 | """ 281 | Set state booleans and initialize dictionary. 282 | """ 283 | self.exited = False 284 | self.auth = False 285 | self.data = {} 286 | 287 | def exit(self): 288 | """ 289 | Closes the websocket connection. 290 | """ 291 | 292 | self.ws.close() 293 | while self.ws.sock: 294 | continue 295 | self.exited = True 296 | 297 | 298 | class _V5WebSocketManager(_WebSocketManager): 299 | def __init__(self, ws_name, **kwargs): 300 | callback_function = ( 301 | kwargs.pop("callback_function") 302 | if kwargs.get("callback_function") 303 | else self._handle_incoming_message 304 | ) 305 | super().__init__(callback_function, ws_name, **kwargs) 306 | 307 | self.subscriptions = {} 308 | 309 | self.standard_private_topics = [ 310 | "position", 311 | "execution", 312 | "order", 313 | "wallet", 314 | "greeks", 315 | "spread.order", 316 | "spread.execution", 317 | ] 318 | 319 | self.other_private_topics = [ 320 | "execution.fast" 321 | ] 322 | 323 | def subscribe( 324 | self, 325 | topic: str, 326 | callback, 327 | symbol: (str, list) = False 328 | ): 329 | 330 | def prepare_subscription_args(list_of_symbols): 331 | """ 332 | Prepares the topic for subscription by formatting it with the 333 | desired symbols. 334 | """ 335 | 336 | if topic in self.standard_private_topics: 337 | # private topics do not support filters 338 | return [topic] 339 | 340 | topics = [] 341 | for single_symbol in list_of_symbols: 342 | topics.append(topic.format(symbol=single_symbol)) 343 | return topics 344 | 345 | if type(symbol) == str: 346 | symbol = [symbol] 347 | 348 | subscription_args = prepare_subscription_args(symbol) 349 | self._check_callback_directory(subscription_args) 350 | 351 | req_id = str(uuid4()) 352 | 353 | subscription_message = json.dumps( 354 | {"op": "subscribe", "req_id": req_id, "args": subscription_args} 355 | ) 356 | while not self.is_connected(): 357 | # Wait until the connection is open before subscribing. 358 | time.sleep(0.1) 359 | self.ws.send(subscription_message) 360 | self.subscriptions[req_id] = subscription_message 361 | for topic in subscription_args: 362 | self._set_callback(topic, callback) 363 | 364 | def _initialise_local_data(self, topic): 365 | # Create self.data 366 | try: 367 | self.data[topic] 368 | except KeyError: 369 | self.data[topic] = [] 370 | 371 | def _process_delta_orderbook(self, message, topic): 372 | self._initialise_local_data(topic) 373 | 374 | # Record the initial snapshot. 375 | if "snapshot" in message["type"]: 376 | self.data[topic] = message["data"] 377 | return 378 | 379 | # Make updates according to delta response. 380 | book_sides = {"b": message["data"]["b"], "a": message["data"]["a"]} 381 | self.data[topic]["u"]=message["data"]["u"] 382 | self.data[topic]["seq"]=message["data"]["seq"] 383 | 384 | for side, entries in book_sides.items(): 385 | for entry in entries: 386 | # Delete. 387 | if float(entry[1]) == 0: 388 | index = _helpers.find_index( 389 | self.data[topic][side], entry, 0 390 | ) 391 | self.data[topic][side].pop(index) 392 | continue 393 | 394 | # Insert. 395 | price_level_exists = entry[0] in [ 396 | level[0] for level in self.data[topic][side] 397 | ] 398 | if not price_level_exists: 399 | self.data[topic][side].append(entry) 400 | continue 401 | 402 | # Update. 403 | qty_changed = entry[1] != next( 404 | level[1] 405 | for level in self.data[topic][side] 406 | if level[0] == entry[0] 407 | ) 408 | if price_level_exists and qty_changed: 409 | index = _helpers.find_index( 410 | self.data[topic][side], entry, 0 411 | ) 412 | self.data[topic][side][index] = entry 413 | continue 414 | 415 | def _process_delta_ticker(self, message, topic): 416 | self._initialise_local_data(topic) 417 | 418 | # Record the initial snapshot. 419 | if "snapshot" in message["type"]: 420 | self.data[topic] = message["data"] 421 | 422 | # Make updates according to delta response. 423 | elif "delta" in message["type"]: 424 | for key, value in message["data"].items(): 425 | self.data[topic][key] = value 426 | 427 | def _process_auth_message(self, message): 428 | # If we get successful futures auth, notify user 429 | if message.get("success") is True: 430 | logger.debug(f"Authorization for {self.ws_name} successful.") 431 | self.auth = True 432 | # If we get unsuccessful auth, notify user. 433 | elif message.get("success") is False or message.get("type") == "error": 434 | raise Exception( 435 | f"Authorization for {self.ws_name} failed. Please check your " 436 | f"API keys and resync your system time. Raw error: {message}" 437 | ) 438 | 439 | def _process_subscription_message(self, message): 440 | if message.get("req_id"): 441 | sub = self.subscriptions[message["req_id"]] 442 | else: 443 | # if req_id is not supported, guess that the last subscription 444 | # sent was successful 445 | sub = json.loads(list(self.subscriptions.items())[0][1])["args"][0] 446 | 447 | # If we get successful futures subscription, notify user 448 | if message.get("success") is True: 449 | logger.debug(f"Subscription to {sub} successful.") 450 | # Futures subscription fail 451 | elif message.get("success") is False: 452 | response = message["ret_msg"] 453 | logger.error("Couldn't subscribe to topic." f"Error: {response}.") 454 | self._pop_callback(sub[0]) 455 | 456 | def _process_normal_message(self, message): 457 | topic = message["topic"] 458 | if "orderbook" in topic: 459 | self._process_delta_orderbook(message, topic) 460 | callback_data = copy.deepcopy(message) 461 | callback_data["type"] = "snapshot" 462 | callback_data["data"] = self.data[topic] 463 | elif "tickers" in topic: 464 | self._process_delta_ticker(message, topic) 465 | callback_data = copy.deepcopy(message) 466 | callback_data["type"] = "snapshot" 467 | callback_data["data"] = self.data[topic] 468 | else: 469 | callback_data = message 470 | callback_function = self._get_callback(topic) 471 | callback_function(callback_data) 472 | 473 | def _handle_incoming_message(self, message): 474 | def is_auth_message(): 475 | if ( 476 | message.get("op") == "auth" 477 | or message.get("type") == "AUTH_RESP" 478 | ): 479 | return True 480 | else: 481 | return False 482 | 483 | def is_subscription_message(): 484 | if ( 485 | message.get("op") == "subscribe" 486 | or message.get("type") == "COMMAND_RESP" 487 | ): 488 | return True 489 | else: 490 | return False 491 | 492 | if is_auth_message(): 493 | self._process_auth_message(message) 494 | elif is_subscription_message(): 495 | self._process_subscription_message(message) 496 | else: 497 | self._process_normal_message(message) 498 | 499 | def _check_callback_directory(self, topics): 500 | for topic in topics: 501 | if topic in self.callback_directory: 502 | raise Exception( 503 | f"You have already subscribed to this topic: " f"{topic}" 504 | ) 505 | 506 | def _set_callback(self, topic, callback_function): 507 | self.callback_directory[topic] = callback_function 508 | 509 | def _get_callback(self, topic): 510 | return self.callback_directory[topic] 511 | 512 | def _pop_callback(self, topic): 513 | self.callback_directory.pop(topic) 514 | -------------------------------------------------------------------------------- /pybit/_websocket_trading.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | import json 3 | import uuid 4 | import logging 5 | from ._websocket_stream import _WebSocketManager 6 | from . import _helpers 7 | 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | WSS_NAME = "WebSocket Trading" 13 | TRADE_WSS = "wss://{SUBDOMAIN}.{DOMAIN}.{TLD}/v5/trade" 14 | 15 | 16 | class _V5TradeWebSocketManager(_WebSocketManager): 17 | def __init__(self, recv_window, referral_id, **kwargs): 18 | super().__init__(self._handle_incoming_message, WSS_NAME, **kwargs) 19 | self.recv_window = recv_window 20 | self.referral_id = referral_id 21 | self._connect(TRADE_WSS) 22 | 23 | def _process_auth_message(self, message): 24 | # If we get successful auth, notify user 25 | if message.get("retCode") == 0: 26 | logger.debug(f"Authorization for {self.ws_name} successful.") 27 | self.auth = True 28 | # If we get unsuccessful auth, notify user. 29 | else: 30 | raise Exception( 31 | f"Authorization for {self.ws_name} failed. Please check your " 32 | f"API keys and resync your system time. Raw error: {message}" 33 | ) 34 | 35 | def _process_error_message(self, message): 36 | logger.error( 37 | f"WebSocket request {message['reqId']} hit an error. Enabling " 38 | f"traceLogging to reproduce the issue. Raw error: {message}" 39 | ) 40 | self._pop_callback(message["reqId"]) 41 | 42 | def _handle_incoming_message(self, message): 43 | def is_auth_message(): 44 | if message.get("op") == "auth": 45 | return True 46 | else: 47 | return False 48 | 49 | def is_error_message(): 50 | if message.get("retCode") != 0: 51 | return True 52 | else: 53 | return False 54 | 55 | if is_auth_message(): 56 | self._process_auth_message(message) 57 | elif is_error_message(): 58 | self._process_error_message(message) 59 | else: 60 | callback_function = self._pop_callback(message["reqId"]) 61 | callback_function(message) 62 | 63 | def _set_callback(self, topic, callback_function): 64 | self.callback_directory[topic] = callback_function 65 | 66 | def _pop_callback(self, topic): 67 | return self.callback_directory.pop(topic) 68 | 69 | def _send_order_operation(self, operation, callback, request): 70 | request_id = str(uuid.uuid4()) 71 | 72 | message = { 73 | "reqId": request_id, 74 | "header": { 75 | "X-BAPI-TIMESTAMP": _helpers.generate_timestamp(), 76 | }, 77 | "op": operation, 78 | "args": [ 79 | request 80 | ], 81 | } 82 | 83 | if self.recv_window: 84 | message["header"]["X-BAPI-RECV-WINDOW"] = self.recv_window 85 | if self.referral_id: 86 | message["header"]["Referer"] = self.referral_id 87 | 88 | self.ws.send(json.dumps(message)) 89 | self._set_callback(request_id, callback) 90 | -------------------------------------------------------------------------------- /pybit/account.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Account(str, Enum): 5 | GET_WALLET_BALANCE = "/v5/account/wallet-balance" 6 | GET_TRANSFERABLE_AMOUNT = "/v5/account/withdrawal" 7 | UPGRADE_TO_UNIFIED_ACCOUNT = "/v5/account/upgrade-to-uta" 8 | GET_BORROW_HISTORY = "/v5/account/borrow-history" 9 | REPAY_LIABILITY = "/v5/account/quick-repayment" 10 | GET_COLLATERAL_INFO = "/v5/account/collateral-info" 11 | SET_COLLATERAL_COIN = "/v5/account/set-collateral-switch" 12 | BATCH_SET_COLLATERAL_COIN = "/v5/account/set-collateral-switch-batch" 13 | GET_COIN_GREEKS = "/v5/asset/coin-greeks" 14 | GET_FEE_RATE = "/v5/account/fee-rate" 15 | GET_ACCOUNT_INFO = "/v5/account/info" 16 | GET_TRANSACTION_LOG = "/v5/account/transaction-log" 17 | GET_CONTRACT_TRANSACTION_LOG = "/v5/account/contract-transaction-log" 18 | SET_MARGIN_MODE = "/v5/account/set-margin-mode" 19 | SET_MMP = "/v5/account/mmp-modify" 20 | RESET_MMP = "/v5/account/mmp-reset" 21 | GET_MMP_STATE = "/v5/account/mmp-state" 22 | 23 | def __str__(self) -> str: 24 | return self.value 25 | -------------------------------------------------------------------------------- /pybit/asset.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Asset(str, Enum): 5 | GET_COIN_EXCHANGE_RECORDS = "/v5/asset/exchange/order-record" 6 | GET_OPTION_DELIVERY_RECORD = "/v5/asset/delivery-record" 7 | GET_USDC_CONTRACT_SETTLEMENT = "/v5/asset/settlement-record" 8 | GET_SPOT_ASSET_INFO = "/v5/asset/transfer/query-asset-info" 9 | GET_ALL_COINS_BALANCE = "/v5/asset/transfer/query-account-coins-balance" 10 | GET_SINGLE_COIN_BALANCE = "/v5/asset/transfer/query-account-coin-balance" 11 | GET_TRANSFERABLE_COIN = "/v5/asset/transfer/query-transfer-coin-list" 12 | CREATE_INTERNAL_TRANSFER = "/v5/asset/transfer/inter-transfer" 13 | GET_INTERNAL_TRANSFER_RECORDS = ( 14 | "/v5/asset/transfer/query-inter-transfer-list" 15 | ) 16 | GET_SUB_UID = "/v5/asset/transfer/query-sub-member-list" 17 | ENABLE_UT_FOR_SUB_UID = "/v5/asset/transfer/save-transfer-sub-member" 18 | CREATE_UNIVERSAL_TRANSFER = "/v5/asset/transfer/universal-transfer" 19 | GET_UNIVERSAL_TRANSFER_RECORDS = ( 20 | "/v5/asset/transfer/query-universal-transfer-list" 21 | ) 22 | GET_ALLOWED_DEPOSIT_COIN_INFO = "/v5/asset/deposit/query-allowed-list" 23 | SET_DEPOSIT_ACCOUNT = "/v5/asset/deposit/deposit-to-account" 24 | GET_DEPOSIT_RECORDS = "/v5/asset/deposit/query-record" 25 | GET_SUB_ACCOUNT_DEPOSIT_RECORDS = ( 26 | "/v5/asset/deposit/query-sub-member-record" 27 | ) 28 | GET_INTERNAL_DEPOSIT_RECORDS = "/v5/asset/deposit/query-internal-record" 29 | GET_MASTER_DEPOSIT_ADDRESS = "/v5/asset/deposit/query-address" 30 | GET_SUB_DEPOSIT_ADDRESS = "/v5/asset/deposit/query-sub-member-address" 31 | GET_COIN_INFO = "/v5/asset/coin/query-info" 32 | GET_WITHDRAWAL_RECORDS = "/v5/asset/withdraw/query-record" 33 | GET_WITHDRAWABLE_AMOUNT = "/v5/asset/withdraw/withdrawable-amount" 34 | WITHDRAW = "/v5/asset/withdraw/create" 35 | CANCEL_WITHDRAWAL = "/v5/asset/withdraw/cancel" 36 | # Convert 37 | GET_CONVERT_COIN_LIST = "/v5/asset/exchange/query-coin-list" 38 | REQUEST_A_QUOTE = "/v5/asset/exchange/quote-apply" 39 | CONFIRM_A_QUOTE = "/v5/asset/exchange/convert-execute" 40 | GET_CONVERT_STATUS = "/v5/asset/exchange/convert-result-query" 41 | GET_CONVERT_HISTORY = "/v5/asset/exchange/query-convert-history" 42 | 43 | def __str__(self) -> str: 44 | return self.value 45 | -------------------------------------------------------------------------------- /pybit/broker.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Broker(str, Enum): 5 | GET_BROKER_EARNINGS = "/v5/broker/earning-record" 6 | GET_EXCHANGE_BROKER_EARNINGS = "/v5/broker/earnings-info" 7 | 8 | def __str__(self) -> str: 9 | return self.value 10 | -------------------------------------------------------------------------------- /pybit/crypto_loan.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class CryptoLoan(str, Enum): 5 | GET_COLLATERAL_COINS = "/v5/crypto-loan/collateral-data" 6 | GET_BORROWABLE_COINS = "/v5/crypto-loan/loanable-data" 7 | GET_ACCOUNT_BORROWABLE_OR_COLLATERALIZABLE_LIMIT = "/v5/crypto-loan/borrowable-collateralisable-number" 8 | BORROW_CRYPTO_LOAN = "/v5/crypto-loan/borrow" 9 | REPAY_CRYPTO_LOAN = "/v5/crypto-loan/repay" 10 | GET_UNPAID_LOANS = "/v5/crypto-loan/ongoing-orders" 11 | GET_LOAN_REPAYMENT_HISTORY = "/v5/crypto-loan/repayment-history" 12 | GET_COMPLETED_LOAN_ORDER_HISTORY = "/v5/crypto-loan/borrow-history" 13 | GET_MAX_ALLOWED_COLLATERAL_REDUCTION_AMOUNT = "/v5/crypto-loan/max-collateral-amount" 14 | ADJUST_COLLATERAL_AMOUNT = "/v5/crypto-loan/adjust-ltv" 15 | GET_CRYPTO_LOAN_LTV_ADJUSTMENT_HISTORY = "/v5/crypto-loan/adjustment-history" 16 | 17 | def __str__(self) -> str: 18 | return self.value 19 | -------------------------------------------------------------------------------- /pybit/earn.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Earn(str, Enum): 5 | GET_EARN_PRODUCT_INFO = "/v5/earn/product" 6 | STAKE_OR_REDEEM = "/v5/earn/place-order" 7 | GET_STAKE_OR_REDEMPTION_HISTORY = "/v5/earn/order" 8 | GET_STAKED_POSITION = "/v5/earn/position" 9 | 10 | def __str__(self) -> str: 11 | return self.value 12 | -------------------------------------------------------------------------------- /pybit/exceptions.py: -------------------------------------------------------------------------------- 1 | class UnauthorizedExceptionError(Exception): 2 | pass 3 | 4 | 5 | class InvalidChannelTypeError(Exception): 6 | pass 7 | 8 | 9 | class TopicMismatchError(Exception): 10 | pass 11 | 12 | 13 | class FailedRequestError(Exception): 14 | """ 15 | Exception raised for failed requests. 16 | 17 | Attributes: 18 | request -- The original request that caused the error. 19 | message -- Explanation of the error. 20 | status_code -- The code number returned. 21 | time -- The time of the error. 22 | resp_headers -- The response headers from API. None, if the request caused an error locally. 23 | """ 24 | 25 | def __init__(self, request, message, status_code, time, resp_headers): 26 | self.request = request 27 | self.message = message 28 | self.status_code = status_code 29 | self.time = time 30 | self.resp_headers = resp_headers 31 | super().__init__( 32 | f"{message.capitalize()} (ErrCode: {status_code}) (ErrTime: {time})" 33 | f".\nRequest → {request}." 34 | ) 35 | 36 | 37 | class InvalidRequestError(Exception): 38 | """ 39 | Exception raised for returned Bybit errors. 40 | 41 | Attributes: 42 | request -- The original request that caused the error. 43 | message -- Explanation of the error. 44 | status_code -- The code number returned. 45 | time -- The time of the error. 46 | resp_headers -- The response headers from API. None, if the request caused an error locally. 47 | """ 48 | 49 | def __init__(self, request, message, status_code, time, resp_headers): 50 | self.request = request 51 | self.message = message 52 | self.status_code = status_code 53 | self.time = time 54 | self.resp_headers = resp_headers 55 | super().__init__( 56 | f"{message} (ErrCode: {status_code}) (ErrTime: {time})" 57 | f".\nRequest → {request}." 58 | ) 59 | -------------------------------------------------------------------------------- /pybit/helpers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def _opposite_side(side): 5 | if side == "Buy": 6 | return "Sell" 7 | else: 8 | return "Buy" 9 | 10 | 11 | class Helpers: 12 | def __init__(self, session): 13 | self.logger = logging.getLogger(__name__) 14 | self.session = session 15 | 16 | def close_position(self, category, symbol) -> list: 17 | """Market close the positions on a certain symbol. 18 | 19 | Required args: 20 | category (string): Product type: linear,inverse 21 | symbol (string): Symbol name 22 | 23 | Returns: 24 | Request results as list. 25 | 26 | Additional information: 27 | """ 28 | 29 | positions = self.session.get_positions(category=category, symbol=symbol) 30 | positions = positions["result"]["list"] 31 | 32 | responses = [] 33 | for position in positions: 34 | if position["side"] and float(position["size"]) != 0: 35 | response = self.session.place_order( 36 | category=category, 37 | symbol=symbol, 38 | side=_opposite_side(position["side"]), 39 | qty=position["size"], 40 | orderType="Market", 41 | positionIdx=position["positionIdx"], 42 | ) 43 | responses.append(response) 44 | 45 | if not responses: 46 | self.logger.error("Tried to close_position; no position detected.") 47 | 48 | return responses 49 | -------------------------------------------------------------------------------- /pybit/institutional_loan.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class InstitutionalLoan(str, Enum): 5 | GET_PRODUCT_INFO = "/v5/ins-loan/product-infos" 6 | GET_MARGIN_COIN_INFO = "/v5/ins-loan/ensure-tokens-convert" 7 | GET_LOAN_ORDERS = "/v5/ins-loan/loan-order" 8 | GET_REPAYMENT_ORDERS = "/v5/ins-loan/repaid-history" 9 | GET_LTV = "/v5/ins-loan/ltv-convert" 10 | BIND_OR_UNBIND_UID = "/v5/ins-loan/association-uid" 11 | 12 | def __str__(self) -> str: 13 | return self.value 14 | -------------------------------------------------------------------------------- /pybit/market.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Market(str, Enum): 5 | GET_SERVER_TIME = "/v5/market/time" 6 | GET_KLINE = "/v5/market/kline" 7 | GET_MARK_PRICE_KLINE = "/v5/market/mark-price-kline" 8 | GET_INDEX_PRICE_KLINE = "/v5/market/index-price-kline" 9 | GET_PREMIUM_INDEX_PRICE_KLINE = "/v5/market/premium-index-price-kline" 10 | GET_INSTRUMENTS_INFO = "/v5/market/instruments-info" 11 | GET_ORDERBOOK = "/v5/market/orderbook" 12 | GET_TICKERS = "/v5/market/tickers" 13 | GET_FUNDING_RATE_HISTORY = "/v5/market/funding/history" 14 | GET_PUBLIC_TRADING_HISTORY = "/v5/market/recent-trade" 15 | GET_OPEN_INTEREST = "/v5/market/open-interest" 16 | GET_HISTORICAL_VOLATILITY = "/v5/market/historical-volatility" 17 | GET_INSURANCE = "/v5/market/insurance" 18 | GET_RISK_LIMIT = "/v5/market/risk-limit" 19 | GET_OPTION_DELIVERY_PRICE = "/v5/market/delivery-price" 20 | GET_LONG_SHORT_RATIO = "/v5/market/account-ratio" 21 | 22 | def __str__(self) -> str: 23 | return self.value 24 | -------------------------------------------------------------------------------- /pybit/misc.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Misc(str, Enum): 5 | GET_ANNOUNCEMENT = "/v5/announcements/index" 6 | REQUEST_DEMO_TRADING_FUNDS = "/v5/account/demo-apply-money" 7 | 8 | def __str__(self) -> str: 9 | return self.value 10 | -------------------------------------------------------------------------------- /pybit/position.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Position(str, Enum): 5 | GET_POSITIONS = "/v5/position/list" 6 | SET_LEVERAGE = "/v5/position/set-leverage" 7 | SWITCH_MARGIN_MODE = "/v5/position/switch-isolated" 8 | SET_TP_SL_MODE = "/v5/position/set-tpsl-mode" 9 | SWITCH_POSITION_MODE = "/v5/position/switch-mode" 10 | SET_RISK_LIMIT = "/v5/position/set-risk-limit" 11 | SET_TRADING_STOP = "/v5/position/trading-stop" 12 | SET_AUTO_ADD_MARGIN = "/v5/position/set-auto-add-margin" 13 | ADD_MARGIN = "/v5/position/add-margin" 14 | GET_EXECUTIONS = "/v5/execution/list" 15 | GET_CLOSED_PNL = "/v5/position/closed-pnl" 16 | 17 | def __str__(self) -> str: 18 | return self.value 19 | -------------------------------------------------------------------------------- /pybit/pre_upgrade.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class PreUpgrade(str, Enum): 5 | GET_PRE_UPGRADE_ORDER_HISTORY = "/v5/pre-upgrade/order/history" 6 | GET_PRE_UPGRADE_TRADE_HISTORY = "/v5/pre-upgrade/execution/list" 7 | GET_PRE_UPGRADE_CLOSED_PNL = "/v5/pre-upgrade/position/closed-pnl" 8 | GET_PRE_UPGRADE_TRANSACTION_LOG = "/v5/pre-upgrade/account/transaction-log" 9 | GET_PRE_UPGRADE_OPTION_DELIVERY_RECORD = "/v5/pre-upgrade/asset/delivery-record" 10 | GET_PRE_UPGRADE_USDC_SESSION_SETTLEMENT = "/v5/pre-upgrade/asset/settlement-record" 11 | 12 | def __str__(self) -> str: 13 | return self.value 14 | -------------------------------------------------------------------------------- /pybit/spot_leverage_token.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class SpotLeverageToken(str, Enum): 5 | GET_LEVERAGED_TOKEN_INFO = "/v5/spot-lever-token/info" 6 | GET_LEVERAGED_TOKEN_MARKET = "/v5/spot-lever-token/reference" 7 | PURCHASE = "/v5/spot-lever-token/purchase" 8 | REDEEM = "/v5/spot-lever-token/redeem" 9 | GET_PURCHASE_REDEMPTION_RECORDS = "/v5/spot-lever-token/order-record" 10 | 11 | def __str__(self) -> str: 12 | return self.value 13 | -------------------------------------------------------------------------------- /pybit/spot_margin_trade.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class SpotMarginTrade(str, Enum): 5 | # UTA endpoints 6 | TOGGLE_MARGIN_TRADE = "/v5/spot-margin-trade/switch-mode" 7 | SET_LEVERAGE = "/v5/spot-margin-trade/set-leverage" 8 | VIP_MARGIN_DATA = "/v5/spot-margin-trade/data" 9 | STATUS_AND_LEVERAGE = "/v5/spot-margin-trade/state" 10 | HISTORICAL_INTEREST = "/v5/spot-margin-trade/interest-rate-history" 11 | # normal mode (non-UTA) endpoints 12 | NORMAL_GET_VIP_MARGIN_DATA = "/v5/spot-cross-margin-trade/data" 13 | NORMAL_GET_MARGIN_COIN_INFO = "/v5/spot-cross-margin-trade/pledge-token" 14 | NORMAL_GET_BORROWABLE_COIN_INFO = "/v5/spot-cross-margin-trade/borrow-token" 15 | NORMAL_GET_INTEREST_QUOTA = "/v5/spot-cross-margin-trade/loan-info" 16 | NORMAL_GET_LOAN_ACCOUNT_INFO = "/v5/spot-cross-margin-trade/account" 17 | NORMAL_BORROW = "/v5/spot-cross-margin-trade/loan" 18 | NORMAL_REPAY = "/v5/spot-cross-margin-trade/repay" 19 | NORMAL_GET_BORROW_ORDER_DETAIL = "/v5/spot-cross-margin-trade/orders" 20 | NORMAL_GET_REPAYMENT_ORDER_DETAIL = "/v5/spot-cross-margin-trade/repay-history" 21 | NORMAL_TOGGLE_MARGIN_TRADE = "/v5/spot-cross-margin-trade/switch" 22 | 23 | def __str__(self) -> str: 24 | return self.value 25 | -------------------------------------------------------------------------------- /pybit/spread.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Spread(str, Enum): 5 | GET_INSTRUMENTS_INFO = "/v5/spread/instrument" 6 | GET_ORDERBOOK = "/v5/spread/orderbook" 7 | GET_TICKERS = "/v5/spread/tickers" 8 | GET_PUBLIC_TRADING_HISTORY = "/v5/spread/recent-trade" 9 | PLACE_ORDER = "/v5/spread/order/create" 10 | AMEND_ORDER = "/v5/spread/order/amend" 11 | CANCEL_ORDER = "/v5/spread/order/cancel" 12 | CANCEL_ALL_ORDERS = "/v5/spread/order/cancel-all" 13 | GET_OPEN_ORDERS = "/v5/spread/order/realtime" 14 | GET_ORDER_HISTORY = "/v5/spread/order/history" 15 | GET_TRADE_HISTORY = "/v5/spread/execution/list" 16 | 17 | def __str__(self) -> str: 18 | return self.value 19 | -------------------------------------------------------------------------------- /pybit/trade.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Trade(str, Enum): 5 | PLACE_ORDER = "/v5/order/create" 6 | AMEND_ORDER = "/v5/order/amend" 7 | CANCEL_ORDER = "/v5/order/cancel" 8 | GET_OPEN_ORDERS = "/v5/order/realtime" 9 | CANCEL_ALL_ORDERS = "/v5/order/cancel-all" 10 | GET_ORDER_HISTORY = "/v5/order/history" 11 | BATCH_PLACE_ORDER = "/v5/order/create-batch" 12 | BATCH_AMEND_ORDER = "/v5/order/amend-batch" 13 | BATCH_CANCEL_ORDER = "/v5/order/cancel-batch" 14 | GET_BORROW_QUOTA = "/v5/order/spot-borrow-check" 15 | SET_DCP = "/v5/order/disconnected-cancel-all" 16 | 17 | def __str__(self) -> str: 18 | return self.value 19 | -------------------------------------------------------------------------------- /pybit/unified_trading.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from pybit.exceptions import ( 4 | InvalidChannelTypeError, 5 | TopicMismatchError, 6 | UnauthorizedExceptionError, 7 | ) 8 | from ._v5_misc import MiscHTTP 9 | from ._v5_market import MarketHTTP 10 | from ._v5_trade import TradeHTTP 11 | from ._v5_account import AccountHTTP 12 | from ._v5_asset import AssetHTTP 13 | from ._v5_position import PositionHTTP 14 | from ._v5_pre_upgrade import PreUpgradeHTTP 15 | from ._v5_spot_leverage_token import SpotLeverageHTTP 16 | from ._v5_spot_margin_trade import SpotMarginTradeHTTP 17 | from ._v5_user import UserHTTP 18 | from ._v5_broker import BrokerHTTP 19 | from ._v5_institutional_loan import InstitutionalLoanHTTP 20 | from ._v5_crypto_loan import CryptoLoanHTTP 21 | from ._v5_earn import EarnHTTP 22 | from ._websocket_stream import _V5WebSocketManager 23 | from ._websocket_trading import _V5TradeWebSocketManager 24 | from ._v5_spread import ( 25 | SpreadHTTP, 26 | _V5WebSocketSpreadTrading, 27 | ) 28 | 29 | 30 | logger = logging.getLogger(__name__) 31 | 32 | WSS_NAME = "Unified V5" 33 | PRIVATE_WSS = "wss://{SUBDOMAIN}.{DOMAIN}.com/v5/private" 34 | PUBLIC_WSS = "wss://{SUBDOMAIN}.{DOMAIN}.com/v5/public/{CHANNEL_TYPE}" 35 | AVAILABLE_CHANNEL_TYPES = [ 36 | "inverse", 37 | "linear", 38 | "spot", 39 | "option", 40 | "private", 41 | ] 42 | 43 | 44 | @dataclass 45 | class HTTP( 46 | MiscHTTP, 47 | MarketHTTP, 48 | TradeHTTP, 49 | AccountHTTP, 50 | AssetHTTP, 51 | PositionHTTP, 52 | PreUpgradeHTTP, 53 | SpotLeverageHTTP, 54 | SpotMarginTradeHTTP, 55 | UserHTTP, 56 | BrokerHTTP, 57 | InstitutionalLoanHTTP, 58 | CryptoLoanHTTP, 59 | EarnHTTP, 60 | ): 61 | def __init__(self, **args): 62 | super().__init__(**args) 63 | 64 | 65 | class WebSocket(_V5WebSocketManager): 66 | def _validate_public_topic(self): 67 | if "/v5/public" not in self.WS_URL: 68 | raise TopicMismatchError( 69 | "Requested topic does not match channel_type" 70 | ) 71 | 72 | def _validate_private_topic(self): 73 | if not self.WS_URL.endswith("/private"): 74 | raise TopicMismatchError( 75 | "Requested topic does not match channel_type" 76 | ) 77 | 78 | def __init__( 79 | self, 80 | channel_type: str, 81 | **kwargs, 82 | ): 83 | super().__init__(WSS_NAME, **kwargs) 84 | if channel_type not in AVAILABLE_CHANNEL_TYPES: 85 | raise InvalidChannelTypeError( 86 | f"Channel type is not correct. Available: {AVAILABLE_CHANNEL_TYPES}" 87 | ) 88 | 89 | if channel_type == "private": 90 | self.WS_URL = PRIVATE_WSS 91 | else: 92 | self.WS_URL = PUBLIC_WSS.replace("{CHANNEL_TYPE}", channel_type) 93 | # Do not pass keys and attempt authentication on a public connection 94 | self.api_key = None 95 | self.api_secret = None 96 | 97 | if ( 98 | self.api_key is None or self.api_secret is None 99 | ) and channel_type == "private": 100 | raise UnauthorizedExceptionError( 101 | "API_KEY or API_SECRET is not set. They both are needed in order to access private topics" 102 | ) 103 | 104 | self._connect(self.WS_URL) 105 | 106 | # Private topics 107 | 108 | def position_stream(self, callback): 109 | """Subscribe to the position stream to see changes to your position data in real-time. 110 | 111 | Push frequency: real-time 112 | 113 | Additional information: 114 | https://bybit-exchange.github.io/docs/v5/websocket/private/position 115 | """ 116 | self._validate_private_topic() 117 | topic = "position" 118 | self.subscribe(topic, callback) 119 | 120 | def order_stream(self, callback): 121 | """Subscribe to the order stream to see changes to your orders in real-time. 122 | 123 | Push frequency: real-time 124 | 125 | Additional information: 126 | https://bybit-exchange.github.io/docs/v5/websocket/private/order 127 | """ 128 | self._validate_private_topic() 129 | topic = "order" 130 | self.subscribe(topic, callback) 131 | 132 | def execution_stream(self, callback): 133 | """Subscribe to the execution stream to see your executions in real-time. 134 | 135 | Push frequency: real-time 136 | 137 | Additional information: 138 | https://bybit-exchange.github.io/docs/v5/websocket/private/execution 139 | """ 140 | self._validate_private_topic() 141 | topic = "execution" 142 | self.subscribe(topic, callback) 143 | 144 | def fast_execution_stream(self, callback, categorised_topic=""): 145 | """Fast execution stream significantly reduces data latency compared 146 | original "execution" stream. However, it pushes limited execution type 147 | of trades, and fewer data fields. 148 | Use categorised_topic as a filter for a certain `category`. See docs. 149 | 150 | Push frequency: real-time 151 | 152 | Additional information: 153 | https://bybit-exchange.github.io/docs/v5/websocket/private/fast-execution 154 | """ 155 | self._validate_private_topic() 156 | topic = "execution.fast" 157 | if categorised_topic: 158 | topic += "." + categorised_topic 159 | self.subscribe(topic, callback) 160 | 161 | def wallet_stream(self, callback): 162 | """Subscribe to the wallet stream to see changes to your wallet in real-time. 163 | 164 | Push frequency: real-time 165 | 166 | Additional information: 167 | https://bybit-exchange.github.io/docs/v5/websocket/private/wallet 168 | """ 169 | self._validate_private_topic() 170 | topic = "wallet" 171 | self.subscribe(topic, callback) 172 | 173 | def greek_stream(self, callback): 174 | """Subscribe to the greeks stream to see changes to your greeks data in real-time. option only. 175 | 176 | Push frequency: real-time 177 | 178 | Additional information: 179 | https://bybit-exchange.github.io/docs/v5/websocket/private/greek 180 | """ 181 | self._validate_private_topic() 182 | topic = "greeks" 183 | self.subscribe(topic, callback) 184 | 185 | def spread_order_stream(self, callback): 186 | """Subscribe to the spread trading order stream to see changes to your orders in real-time. 187 | 188 | Push frequency: real-time 189 | 190 | Additional information: 191 | https://bybit-exchange.github.io/docs/v5/spread/websocket/private/order 192 | """ 193 | self._validate_private_topic() 194 | topic = "spread.order" 195 | self.subscribe(topic, callback) 196 | 197 | def spread_execution_stream(self, callback): 198 | """Subscribe to the spread trading execution stream to see your executions in real-time. 199 | 200 | Push frequency: real-time 201 | 202 | Additional information: 203 | https://bybit-exchange.github.io/docs/v5/spread/websocket/private/execution 204 | """ 205 | self._validate_private_topic() 206 | topic = "spread.execution" 207 | self.subscribe(topic, callback) 208 | 209 | # Public topics 210 | 211 | def orderbook_stream(self, depth: int, symbol: (str, list), callback): 212 | """Subscribe to the orderbook stream. Supports different depths. 213 | 214 | Linear & inverse: 215 | Level 1 data, push frequency: 10ms 216 | Level 50 data, push frequency: 20ms 217 | Level 200 data, push frequency: 100ms 218 | Level 500 data, push frequency: 100ms 219 | 220 | Spot: 221 | Level 1 data, push frequency: 10ms 222 | Level 50 data, push frequency: 20ms 223 | 224 | Option: 225 | Level 25 data, push frequency: 20ms 226 | Level 100 data, push frequency: 100ms 227 | 228 | Required args: 229 | symbol (string/list): Symbol name(s) 230 | depth (int): Orderbook depth 231 | 232 | Additional information: 233 | https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook 234 | """ 235 | self._validate_public_topic() 236 | topic = f"orderbook.{depth}." + "{symbol}" 237 | self.subscribe(topic, callback, symbol) 238 | 239 | def trade_stream(self, symbol: (str, list), callback): 240 | """ 241 | Subscribe to the recent trades stream. 242 | After subscription, you will be pushed trade messages in real-time. 243 | 244 | Push frequency: real-time 245 | 246 | Required args: 247 | symbol (string/list): Symbol name(s) 248 | 249 | Additional information: 250 | https://bybit-exchange.github.io/docs/v5/websocket/public/trade 251 | """ 252 | self._validate_public_topic() 253 | topic = f"publicTrade." + "{symbol}" 254 | self.subscribe(topic, callback, symbol) 255 | 256 | def ticker_stream(self, symbol: (str, list), callback): 257 | """Subscribe to the ticker stream. 258 | 259 | Push frequency: 100ms 260 | 261 | Required args: 262 | symbol (string/list): Symbol name(s) 263 | 264 | Additional information: 265 | https://bybit-exchange.github.io/docs/v5/websocket/public/ticker 266 | """ 267 | self._validate_public_topic() 268 | topic = "tickers.{symbol}" 269 | self.subscribe(topic, callback, symbol) 270 | 271 | def kline_stream(self, interval: int, symbol: (str, list), callback): 272 | """Subscribe to the klines stream. 273 | 274 | Push frequency: 1-60s 275 | 276 | Required args: 277 | symbol (string/list): Symbol name(s) 278 | interval (int): Kline interval 279 | 280 | Additional information: 281 | https://bybit-exchange.github.io/docs/v5/websocket/public/kline 282 | """ 283 | self._validate_public_topic() 284 | topic = f"kline.{interval}." + "{symbol}" 285 | self.subscribe(topic, callback, symbol) 286 | 287 | def liquidation_stream(self, symbol: (str, list), callback): 288 | """ 289 | Pushes at most one order per second per symbol. 290 | As such, this feed does not push all liquidations that occur on Bybit. 291 | 292 | Push frequency: 1s 293 | 294 | Required args: 295 | symbol (string/list): Symbol name(s) 296 | 297 | Additional information: 298 | https://bybit-exchange.github.io/docs/v5/websocket/public/liquidation 299 | """ 300 | logger.warning("liquidation_stream() is deprecated. Please use " 301 | "all_liquidation_stream().") 302 | self._validate_public_topic() 303 | topic = "liquidation.{symbol}" 304 | self.subscribe(topic, callback, symbol) 305 | 306 | def all_liquidation_stream(self, symbol: (str, list), callback): 307 | """Subscribe to the liquidation stream, push all liquidations that 308 | occur on Bybit. 309 | 310 | Push frequency: 500ms 311 | 312 | Required args: 313 | symbol (string/list): Symbol name(s) 314 | 315 | Additional information: 316 | https://bybit-exchange.github.io/docs/v5/websocket/public/all-liquidation 317 | """ 318 | self._validate_public_topic() 319 | topic = "allLiquidation.{symbol}" 320 | self.subscribe(topic, callback, symbol) 321 | 322 | def lt_kline_stream(self, interval: int, symbol: (str, list), callback): 323 | """Subscribe to the leveraged token kline stream. 324 | 325 | Push frequency: 1-60s 326 | 327 | Required args: 328 | symbol (string/list): Symbol name(s) 329 | interval (int): Leveraged token Kline stream interval 330 | 331 | Additional information: 332 | https://bybit-exchange.github.io/docs/v5/websocket/public/etp-kline 333 | """ 334 | self._validate_public_topic() 335 | topic = f"kline_lt.{interval}." + "{symbol}" 336 | self.subscribe(topic, callback, symbol) 337 | 338 | def lt_ticker_stream(self, symbol: (str, list), callback): 339 | """Subscribe to the leveraged token ticker stream. 340 | 341 | Push frequency: 300ms 342 | 343 | Required args: 344 | symbol (string/list): Symbol name(s) 345 | 346 | Additional information: 347 | https://bybit-exchange.github.io/docs/v5/websocket/public/etp-ticker 348 | """ 349 | self._validate_public_topic() 350 | topic = "tickers_lt.{symbol}" 351 | self.subscribe(topic, callback, symbol) 352 | 353 | def lt_nav_stream(self, symbol: (str, list), callback): 354 | """Subscribe to the leveraged token nav stream. 355 | 356 | Push frequency: 300ms 357 | 358 | Required args: 359 | symbol (string/list): Symbol name(s) 360 | 361 | Additional information: 362 | https://bybit-exchange.github.io/docs/v5/websocket/public/etp-nav 363 | """ 364 | self._validate_public_topic() 365 | topic = "lt.{symbol}" 366 | self.subscribe(topic, callback, symbol) 367 | 368 | 369 | class WebSocketTrading(_V5TradeWebSocketManager): 370 | def __init__(self, recv_window=0, referral_id="", **kwargs): 371 | super().__init__(recv_window, referral_id, **kwargs) 372 | 373 | def place_order(self, callback, **kwargs): 374 | operation = "order.create" 375 | self._send_order_operation(operation, callback, kwargs) 376 | 377 | def amend_order(self, callback, **kwargs): 378 | operation = "order.amend" 379 | self._send_order_operation(operation, callback, kwargs) 380 | 381 | def cancel_order(self, callback, **kwargs): 382 | operation = "order.cancel" 383 | self._send_order_operation(operation, callback, kwargs) 384 | 385 | def place_batch_order(self, callback, **kwargs): 386 | operation = "order.create-batch" 387 | self._send_order_operation(operation, callback, kwargs) 388 | 389 | def amend_batch_order(self, callback, **kwargs): 390 | operation = "order.amend-batch" 391 | self._send_order_operation(operation, callback, kwargs) 392 | 393 | def cancel_batch_order(self, callback, **kwargs): 394 | operation = "order.cancel-batch" 395 | self._send_order_operation(operation, callback, kwargs) 396 | 397 | 398 | class WebsocketSpreadTrading(_V5WebSocketSpreadTrading): 399 | def __init__(self, **kwargs): 400 | super().__init__(**kwargs) 401 | 402 | def orderbook_stream(self, depth: int, symbol: (str, list), callback): 403 | """Subscribe to the orderbook stream. Supports different depths. 404 | 405 | Level 25 data, push frequency: 20ms 406 | 407 | Required args: 408 | symbol (string/list): Symbol name(s) 409 | depth (int): Orderbook depth 410 | 411 | Additional information: 412 | https://bybit-exchange.github.io/docs/v5/spread/websocket/public/orderbook 413 | """ 414 | topic = f"orderbook.{depth}." + "{symbol}" 415 | self.subscribe(topic, callback, symbol) 416 | 417 | def trade_stream(self, symbol: (str, list), callback): 418 | """ 419 | Subscribe to the recent trades stream. 420 | After subscription, you will be pushed trade messages in real-time. 421 | 422 | Push frequency: real-time 423 | 424 | Required args: 425 | symbol (string/list): Symbol name(s) 426 | 427 | Additional information: 428 | https://bybit-exchange.github.io/docs/v5/spread/websocket/public/trade 429 | """ 430 | topic = f"publicTrade." + "{symbol}" 431 | self.subscribe(topic, callback, symbol) 432 | 433 | def ticker_stream(self, symbol: (str, list), callback): 434 | """Subscribe to the ticker stream. 435 | 436 | Push frequency: 100ms 437 | 438 | Required args: 439 | symbol (string/list): Symbol name(s) 440 | 441 | Additional information: 442 | https://bybit-exchange.github.io/docs/v5/spread/websocket/public/ticker 443 | """ 444 | topic = "tickers.{symbol}" 445 | self.subscribe(topic, callback, symbol) 446 | -------------------------------------------------------------------------------- /pybit/user.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class User(str, Enum): 5 | CREATE_SUB_UID = "/v5/user/create-sub-member" 6 | CREATE_SUB_API_KEY = "/v5/user/create-sub-api" 7 | GET_SUB_UID_LIST = "/v5/user/query-sub-members" 8 | FREEZE_SUB_UID = "/v5/user/frozen-sub-member" 9 | GET_API_KEY_INFORMATION = "/v5/user/query-api" 10 | MODIFY_MASTER_API_KEY = "/v5/user/update-api" 11 | MODIFY_SUB_API_KEY = "/v5/user/update-sub-api" 12 | DELETE_MASTER_API_KEY = "/v5/user/delete-api" 13 | DELETE_SUB_API_KEY = "/v5/user/delete-sub-api" 14 | GET_AFFILIATE_USER_INFO = "/v5/user/aff-customer-info" 15 | GET_UID_WALLET_TYPE = "/v5/user/get-member-type" 16 | DELETE_SUB_UID = "/v5/user/del-submember" 17 | GET_ALL_SUB_API_KEYS = "/v5/user/sub-apikeys" 18 | 19 | def __str__(self) -> str: 20 | return self.value 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.22.0 2 | websocket-client==1.5.0 3 | pycryptodome==3.20.0 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = LICENSE 3 | 4 | [options] 5 | zip_safe = False 6 | 7 | [bdist_wheel] 8 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from os import path 3 | 4 | here = path.abspath(path.dirname(__file__)) 5 | 6 | with open(path.join(here, "README.md"), encoding="utf-8") as f: 7 | long_description = f.read() 8 | 9 | setup( 10 | name='pybit', 11 | version='5.11.0', 12 | description='Python3 Bybit HTTP/WebSocket API Connector', 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/bybit-exchange/pybit", 16 | license="MIT License", 17 | author="Dexter Dickinson", 18 | author_email="dexter.dickinson@bybit.com", 19 | classifiers=[ 20 | "Development Status :: 4 - Beta", 21 | "Intended Audience :: Developers", 22 | "Topic :: Software Development :: Libraries :: Python Modules", 23 | "License :: OSI Approved :: MIT License", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | ], 27 | keywords="bybit api connector", 28 | packages=["pybit"], 29 | python_requires=">=3.6", 30 | install_requires=[ 31 | "requests", 32 | "websocket-client", 33 | "pycryptodome", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bybit-exchange/pybit/63cb8922ccae000cae0394c094ada3c07723b371/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_pybit.py: -------------------------------------------------------------------------------- 1 | import time 2 | import unittest 3 | 4 | from pybit.exceptions import InvalidChannelTypeError, TopicMismatchError 5 | from pybit.unified_trading import HTTP, WebSocket 6 | 7 | # session uses Bybit's mainnet endpoint 8 | session = HTTP() 9 | ws = WebSocket( 10 | channel_type="spot", 11 | testnet=False, 12 | ) 13 | 14 | 15 | class HTTPTest(unittest.TestCase): 16 | def test_orderbook(self): 17 | self.assertEqual( 18 | session.get_orderbook(category="spot", symbol="BTCUSDT")["retMsg"], 19 | "OK", 20 | ) 21 | 22 | def test_query_kline(self): 23 | self.assertEqual( 24 | ( 25 | session.get_kline( 26 | symbol="BTCUSDT", 27 | interval="1", 28 | from_time=int(time.time()) - 60 * 60, 29 | )["retMsg"] 30 | ), 31 | "OK", 32 | ) 33 | 34 | def test_symbol_information(self): 35 | self.assertEqual( 36 | session.get_instruments_info(category="spot", symbol="BTCUSDT")[ 37 | "retMsg" 38 | ], 39 | "OK", 40 | ) 41 | 42 | # We can't really test authenticated endpoints without keys, but we 43 | # can make sure it raises a PermissionError. 44 | def test_place_active_order(self): 45 | with self.assertRaises(PermissionError): 46 | session.place_order( 47 | symbol="BTCUSD", 48 | order_type="Market", 49 | side="Buy", 50 | qty=1, 51 | category="spot", 52 | ) 53 | 54 | 55 | class WebSocketTest(unittest.TestCase): 56 | # A very simple test to ensure we're getting something from WS. 57 | def _callback_function(msg): 58 | print(msg) 59 | 60 | def test_websocket(self): 61 | self.assertNotEqual( 62 | ws.orderbook_stream( 63 | depth=1, 64 | symbol="BTCUSDT", 65 | callback=self._callback_function, 66 | ), 67 | [], 68 | ) 69 | 70 | def test_invalid_category(self): 71 | with self.assertRaises(InvalidChannelTypeError): 72 | WebSocket( 73 | channel_type="not_exists", 74 | testnet=False, 75 | ) 76 | 77 | def test_topic_category_mismatch(self): 78 | with self.assertRaises(TopicMismatchError): 79 | ws = WebSocket( 80 | channel_type="linear", 81 | testnet=False, 82 | ) 83 | 84 | ws.order_stream(callback=self._callback_function) 85 | 86 | class PrivateWebSocketTest(unittest.TestCase): 87 | # Connect to private websocket and see if we can auth. 88 | def _callback_function(msg): 89 | print(msg) 90 | 91 | def test_private_websocket_connect(self): 92 | ws_private = WebSocket( 93 | testnet=True, 94 | channel_type="private", 95 | api_key="...", 96 | api_secret="...", 97 | trace_logging=True, 98 | #private_auth_expire=10 99 | ) 100 | 101 | ws_private.position_stream(callback=self._callback_function) 102 | #time.sleep(10) 103 | --------------------------------------------------------------------------------