Bybit Execution
Implement trade execution on Bybit using their unified trading API supporting spot, linear perpetual, and inverse perpetual contract markets with proper API authentication, position mode management, and real-time order status WebSocket streaming.
Execute Trades on Bybit
1. Overview
This notebook demonstrates how to execute trades programmatically on Bybit using the pybit library. It covers authentication, market data retrieval, fee lookup, order placement, and order management.
What You Will Learn
This section outlines the key topics covered, providing a structured learning path for interacting with the Bybit exchange programmatically. Each step is designed to build upon the previous one, giving a comprehensive understanding of automated trading.
| Step | Description |
|---|---|
| Authentication | Connect to Bybit via API key and secret |
| Market Data | Fetch ticker prices and order book depth |
| Fee Lookup | Retrieve trading fee rates for an instrument |
| Market Order | Place an immediate buy/sell order |
| Limit Order | Place a price-controlled buy/sell order |
| Order Status | Query and display order fill details |
| Cancel Order | Cancel an open limit order |
Note: This notebook uses the Bybit testnet by default. Set
testnet=Falseand supply live API keys to trade on the real exchange. The testnet is an invaluable environment for developing and testing trading strategies without financial risk.
Dummy Mode: Live API calls in this notebook are commented out and replaced with realistic dummy data so it can run end-to-end without a live connection or trading funds. Uncomment the real calls and remove the dummy assignments to run against a real account. This approach ensures that the notebook is immediately runnable and provides clear examples of expected API responses, even when not connected to the live exchange.
2. Dependency Imports
Install and import the required libraries. pybit is the official Bybit Python SDK for both REST and WebSocket APIs. This section ensures that your environment is set up correctly, providing all the necessary tools to interact with the Bybit platform. It's crucial to have the right libraries installed to avoid runtime errors and ensure smooth execution of the trading logic.
# Install the Bybit SDK if not already present.
!pip install pybit --quiet[?25l [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m0.0/59.2 kB[0m [31m?[0m eta [36m-:--:--[0m [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m59.2/59.2 kB[0m [31m2.8 MB/s[0m eta [36m0:00:00[0m [?25h[?25l [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m0.0/2.3 MB[0m [31m?[0m eta [36m-:--:--[0m [2K [91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m[91m╸[0m [32m2.3/2.3 MB[0m [31m88.3 MB/s[0m eta [36m0:00:01[0m [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m2.3/2.3 MB[0m [31m40.8 MB/s[0m eta [36m0:00:00[0m [?25h
from pybit.unified_trading import HTTP
import warnings
# Suppress all warnings for cleaner output in a demonstration context.
warnings.filterwarnings("ignore")3. Authentication
3.1. create_client Function
Bybit uses an API key and secret for secure access. The testnet flag routes requests to the Bybit testnet environment — no real funds are at risk. This is a critical first step for any programmatic interaction, establishing a secure and verified connection to your Bybit account. Always keep your API keys and secrets confidential and manage them securely, especially when dealing with live trading accounts.
def create_client(api_key: str, api_secret: str, testnet: bool = True) -> HTTP:
"""Create and return an authenticated Bybit HTTP client.
Parameters
----------
api_key : Your Bybit API key.
api_secret : Your Bybit API secret.
testnet : If True, connects to the Bybit testnet (safe for learning).
Returns
-------
HTTP: Authenticated Bybit Unified Trading HTTP client.
"""
# Bybit's unified_trading.HTTP handles both linear and derivatives.
client = HTTP(
demo = testnet,
api_key = api_key,
api_secret = api_secret,
)
return client# ── Replace with your own keys ──────────────────────────────────────────────
API_KEY = "FDWUIcA8pcbJrOX0Cf"
API_SECRET = "hUe8ElOdfFNcTSf177o4elDV8ndHqY47R4ES"
# ────────────────────────────────────────────────────────────────────────────
# Create the client (testnet = True by default).
client = create_client(API_KEY, API_SECRET, testnet=True)
print("Bybit client created successfully.")Bybit client created successfully.
4. Market Data Functions
Market data is the lifeblood of any trading strategy. These functions allow you to retrieve real-time information about financial instruments, which is essential for making informed trading decisions.
4.1. get_ticker_price Function
Fetches the latest best bid and ask prices for a Bybit instrument. The bid price represents the highest price a buyer is willing to pay, and the ask price represents the lowest price a seller is willing to accept. The difference between these two is the spread, which is an important liquidity indicator. Understanding the current ticker price is fundamental for evaluating entry and exit points for trades.
def get_ticker_price(client: HTTP, symbol: str, category: str = "linear") -> dict:
"""Return the best bid and ask price for a symbol.
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
category : Market category: 'linear', 'linear', 'inverse', or 'option'.
Returns
-------
dict: Contains 'bid' and 'ask' as floats.
"""
# get_tickers returns a list; we take the first match for our symbol.
response = client.get_tickers(category=category, symbol=symbol)
data = response["result"]["list"][0]
return {
"bid": float(data["bid1Price"]),
"ask": float(data["ask1Price"]),
}# Fetch the current BTCUSDT linear ticker.
# Commented out to run locally with dummy data
# ticker = get_ticker_price(client, "BTCUSDT", category="linear")
ticker = {"bid": 60000.00, "ask": 60005.00} # Dummy data
print(f"BTCUSDT — Bid: {ticker['bid']:,.2f} | Ask: {ticker['ask']:,.2f}")BTCUSDT — Bid: 60,000.00 | Ask: 60,005.00
4.2. get_order_book Function
Retrieves the top N levels of bids and asks for a given Bybit instrument. The order book provides a detailed view of market depth, showing the quantity of buy and sell orders at various price levels. This information is invaluable for assessing market sentiment, identifying support and resistance levels, and anticipating price movements. A deep order book generally indicates good liquidity, while a shallow one might suggest higher volatility. The depth parameter allows you to control how many price levels you want to observe, from a shallow glance to a more comprehensive view.
def get_order_book(client: HTTP, symbol: str, category: str = "linear", depth: int = 5) -> dict:
"""Return the top N bid and ask levels from the order book.
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
category : Market category: 'linear', 'linear', etc.
depth : Number of price levels to retrieve (default: 5).
Returns
-------
dict: Contains 'bids' and 'asks' as lists of [price, quantity].
"""
# get_orderbook returns bids and asks; each entry is [price, qty].
response = client.get_orderbook(category=category, symbol=symbol, limit=depth)
data = response["result"]
return {
"bids": [[float(b[0]), float(b[1])] for b in data["b"][:depth]],
"asks": [[float(a[0]), float(a[1])] for a in data["a"][:depth]],
}# Display the top 5 levels of the BTCUSDT order book.
# Commented out to run locally with dummy data
# book = get_order_book(client, "BTCUSDT", category="linear", depth=5)
book = { # Dummy data
"bids": [[59999.5, 0.512], [59999.0, 1.204], [59998.5, 0.880], [59998.0, 2.001], [59997.5, 0.330]],
"asks": [[60000.5, 0.470], [60001.0, 1.150], [60001.5, 0.905], [60002.0, 1.760], [60002.5, 0.290]],
}
print("Top 5 Asks (sell side):")
for price, qty in reversed(book["asks"]):
print(f" {price:>12,.2f} | {qty:.6f} BTC")
print("-" * 35)
print("Top 5 Bids (buy side):")
for price, qty in book["bids"]:
print(f" {price:>12,.2f} | {qty:.6f} BTC")
Top 5 Asks (sell side):
60,002.50 | 0.290000 BTC
60,002.00 | 1.760000 BTC
60,001.50 | 0.905000 BTC
60,001.00 | 1.150000 BTC
60,000.50 | 0.470000 BTC
-----------------------------------
Top 5 Bids (buy side):
59,999.50 | 0.512000 BTC
59,999.00 | 1.204000 BTC
59,998.50 | 0.880000 BTC
59,998.00 | 2.001000 BTC
59,997.50 | 0.330000 BTC
5. Fee Information
Understanding trading fees is paramount for profitability, as they directly impact your net returns. This section provides tools to retrieve and analyze the fee structure applicable to your account.
5.1. get_trade_fee Function
Queries your account's maker and taker fee rates for a given instrument. Bybit fees decrease as your VIP tier increases. Maker fees are typically lower (or even rebates) for orders that add liquidity to the order book (e.g., limit orders that are not immediately filled). Taker fees are charged for orders that remove liquidity from the order book (e.g., market orders or limit orders that are immediately filled against existing orders). Being aware of these rates helps in optimizing trading strategies and cost management.
def get_trade_fee(client: HTTP, symbol: str, category: str = "linear") -> dict:
"""Return maker and taker fee rates for a symbol.
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
category : Market category: 'linear', 'linear', etc.
Returns
-------
dict: Contains 'maker_fee' and 'taker_fee' as floats.
Note: The /v5/account/fee-rate endpoint is NOT supported on Bybit
Demo Trading accounts and will return ErrCode 10005 regardless of
API key permissions. It works normally on real (mainnet) accounts.
"""
# get_fee_rates returns the account's actual fee schedule.
response = client.get_fee_rates(category=category, symbol=symbol)
data = response["result"]["list"][0]
return {
"maker_fee": float(data["makerFeeRate"]),
"taker_fee": float(data["takerFeeRate"]),
}
# Display linear fee rates for BTCUSDT.
# Commented out: not supported on demo accounts (ErrCode 10005)
# fees = get_trade_fee(client, "BTCUSDT", category="linear")
fees = {"maker_fee": 0.0001, "taker_fee": 0.0006} # Dummy data (VIP0 default rates)
print(f"BTCUSDT Fees — Maker: {fees['maker_fee']*100:.4f}% | Taker: {fees['taker_fee']*100:.4f}%")
BTCUSDT Fees — Maker: 0.0100% | Taker: 0.0600%
6. Order Execution Functions
These functions are the core of automated trading, allowing you to send instructions to the exchange to buy or sell assets. Choosing the correct order type is crucial for controlling execution and managing risk.
6.1. place_market_order Function
Places a market order on Bybit. Executes immediately at the best available price. Use when execution certainty matters more than price precision. Market orders are ideal when you need to enter or exit a position quickly, regardless of minor price fluctuations. However, be mindful of slippage, especially in volatile or illiquid markets, where the execution price might deviate significantly from the expected price.
def place_market_order(
client: HTTP, symbol: str, side: str, qty: float, category: str = "linear"
) -> dict:
"""Place a market order on Bybit.
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
side : 'Buy' or 'Sell' (Bybit uses title case).
qty : Order quantity in base currency.
category : Market category: 'linear', 'linear', etc.
Returns
-------
dict: Bybit order response including orderId.
"""
# place_order with orderType='Market' for immediate execution.
response = client.place_order(
category = category,
symbol = symbol,
side = side.capitalize(),
orderType = "Market",
qty = str(qty),
)
return response["result"]# Place a market BUY order for 0.001 BTC on the testnet/demo.
# Commented out to run locally with dummy data
# market_order = place_market_order(
# client, symbol="BTCUSDT", side="Buy", qty=0.001, category="linear"
# )
market_order = {"orderId": "dummy-market-0001", "orderLinkId": "dummy-link-0001"} # Dummy data
print("Market Order Placed:")
print(f" Order ID : {market_order['orderId']}")
print(f" Order Link ID: {market_order.get('orderLinkId', 'N/A')}")
Market Order Placed: Order ID : dummy-market-0001 Order Link ID: dummy-link-0001
6.2. place_limit_order Function
Places a limit order on Bybit. The order rests in the book until the market reaches your specified price. Use when price control matters. Limit orders allow you to specify the maximum price you're willing to pay for a buy order or the minimum price you're willing to accept for a sell order. They are excellent for precise entry/exit strategies and can even earn maker rebates by adding liquidity to the market, but there's no guarantee of immediate execution.
def place_limit_order(
client: HTTP, symbol: str, side: str, qty: float, price: float, category: str = "linear"
) -> dict:
"""Place a limit order on Bybit (Good-Till-Cancelled by default).
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
side : 'Buy' or 'Sell' (Bybit uses title case).
qty : Order quantity in base currency.
price : Limit price at which to execute.
category : Market category: 'linear', 'linear', etc.
Returns
-------
dict: Bybit order response including orderId.
"""
# place_order with orderType='Limit' rests until filled or cancelled.
response = client.place_order(
category = category,
symbol = symbol,
side = side.capitalize(),
orderType = "Limit",
qty = str(qty),
price = str(price),
timeInForce = "GTC",
)
return response["result"]# Place a passive limit BUY 1% below the current ask.
# Commented out to run locally with dummy data
# current_ask = get_ticker_price(client, "BTCUSDT", category="linear")["ask"]
current_ask = ticker["ask"] # reuse dummy ticker from earlier
limit_price = round(current_ask * 0.99, 2)
# limit_order = place_limit_order(
# client, symbol="BTCUSDT", side="Buy", qty=0.001, price=limit_price, category="linear"
# )
limit_order = {"orderId": "dummy-limit-0001", "orderLinkId": "dummy-link-0002"} # Dummy data
print("Limit Order Placed:")
print(f" Order ID : {limit_order['orderId']}")
print(f" Limit Price: {limit_price:,.2f} USDT")
Limit Order Placed: Order ID : dummy-limit-0001 Limit Price: 59,404.95 USDT
7. Order Management Functions
Once an order is placed, it's essential to monitor its status and have the ability to modify or cancel it. These functions provide the necessary tools for active order management.
7.1. get_order_status Function
Retrieves the current status and fill details for an existing order using its order ID. This function is vital for tracking the lifecycle of your trades. An order can have various statuses such as 'New' (placed but not filled), 'PartiallyFilled', 'Filled', 'Cancelled', or 'Rejected'. Monitoring these statuses allows you to understand how your orders are progressing and take corrective actions if needed, such as cancelling a lingering limit order or adjusting a strategy based on execution details.
def get_order_status(client: HTTP, symbol: str, order_id: str, category: str = "linear") -> dict:
"""Return the status and fill details of an existing order.
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
order_id : The order ID string returned when the order was placed.
category : Market category: 'linear', 'linear', etc.
Returns
-------
dict: Contains 'order_status', 'cum_exec_qty', and 'avg_price'.
"""
# get_open_orders and get_order_history can both be used; history for closed orders.
response = client.get_open_orders(category=category, symbol=symbol, orderId=order_id)
orders = response["result"]["list"]
if not orders:
# Order may be closed — check order history.
response = client.get_order_history(category=category, symbol=symbol, orderId=order_id)
orders = response["result"]["list"]
data = orders[0]
return {
"order_status": data["orderStatus"],
"cum_exec_qty": float(data["cumExecQty"]),
"avg_price" : float(data["avgPrice"]) if data["avgPrice"] else 0.0,
}# Check the status of the limit order we placed.
# Commented out to run locally with dummy data
# status = get_order_status(client, "BTCUSDT", limit_order["orderId"], category="linear")
status = {"order_status": "New", "cum_exec_qty": 0.0, "avg_price": 0.0} # Dummy data
print("Limit Order Status:")
print(f" Status : {status['order_status']}")
print(f" Executed Qty : {status['cum_exec_qty']} BTC")
print(f" Avg Fill Price: {status['avg_price']:,.2f} USDT")
Limit Order Status: Status : New Executed Qty : 0.0 BTC Avg Fill Price: 0.00 USDT
7.2. cancel_order Function
Cancels an open order on Bybit. Only orders with status New or PartiallyFilled can be cancelled. This function is crucial for managing risk and adapting to changing market conditions. If a limit order is not getting filled or market conditions have shifted, cancelling it allows you to reposition or avoid unwanted executions. Attempting to cancel an already filled or cancelled order will result in an error, so it's good practice to check the order status before attempting to cancel.
def cancel_order(client: HTTP, symbol: str, order_id: str, category: str = "linear") -> dict:
"""Cancel an open order on Bybit.
Parameters
----------
client : Authenticated Bybit HTTP client.
symbol : Instrument symbol, e.g. 'BTCUSDT'.
order_id : The order ID string to cancel.
category : Market category: 'linear', 'linear', etc.
Returns
-------
dict: Cancellation confirmation from Bybit.
"""
# cancel_order raises an error if the order is already filled or cancelled.
response = client.cancel_order(category=category, symbol=symbol, orderId=order_id)
return response["result"]# Cancel the limit order if it is still open.
# Commented out to run locally with dummy data
# cancel_result = cancel_order(client, "BTCUSDT", limit_order["orderId"], category="linear")
cancel_result = {"orderId": limit_order["orderId"], "orderLinkId": limit_order.get("orderLinkId", "N/A")} # Dummy data
print("Order Cancelled:")
print(f" Order ID : {cancel_result['orderId']}")
print(f" Order Link ID: {cancel_result.get('orderLinkId', 'N/A')}")
Order Cancelled: Order ID : dummy-limit-0001 Order Link ID: dummy-link-0002
8. Execution Decision Framework
Choosing the right order type on Bybit depends on your execution goal: This section synthesizes the knowledge gained from the previous sections into practical advice on when to use market versus limit orders, based on your trading objectives and market conditions.
When to Use a Market Order
- You need to enter or exit immediately (e.g., stop-loss, rapid signal decay). This prioritizes speed over precise pricing.
- Order size is small relative to book depth (< 0.5% of visible liquidity). This minimizes the impact of slippage.
- Speed matters more than the exact fill price. Ideal for urgent actions in fast-moving markets.
When to Use a Limit Order
- You want price certainty and can wait for the market to come to you. This ensures execution only at your desired price or better.
- Order size is large (> 1% of book depth) — avoids eating through multiple levels. This helps in minimizing market impact and achieving better average fill prices for large orders.
- Market is volatile (spread > 5 bps) — anchor your execution price. In choppy markets, limit orders can prevent unfavorable executions.
- You want to qualify for maker rebates by adding liquidity to the book. This can reduce overall trading costs or even generate a small income.
Bybit-Specific Notes
- Default fee tier (VIP0): Taker 0.06% / Maker 0.01% (USDT perpetuals). Always be aware of the fee structure relevant to your trading products.
- Bybit uses title-case for side:
'Buy'and'Sell'(not all-caps). This is a common API nuance to remember. - The
categoryparameter distinguishes linear ('linear') from linear perps ('linear'). Ensure you're targeting the correct market type. - Set
testnet=Trueto point the client athttps://api-testnet.bybit.com. Always start with the testnet for development and testing to prevent accidental loss of funds.
9. Conclusion
This notebook has provided a comprehensive guide to programmatically interacting with the Bybit exchange using the pybit library. We've covered the essential steps for automated trading, from initial setup to order management.
Key takeaways include:
- Authentication: Securely connecting to Bybit using API keys and secrets, emphasizing the importance of the testnet for risk-free development.
- Market Data Retrieval: Functions for fetching real-time ticker prices (
get_ticker_price) and in-depth order book data (get_order_book), which are crucial for market analysis and informed decision-making. - Fee Awareness: Understanding how to retrieve trade fee rates (
get_trade_fee), distinguishing between maker and taker fees, and their impact on trading profitability. - Order Execution: Implementing both market orders (
place_market_order) for immediate execution and limit orders (place_limit_order) for price control, along with a detailed framework for choosing the appropriate order type based on trading goals and market conditions. - Order Management: Tools for tracking order status (
get_order_status) and cancelling open orders (cancel_order), vital for adapting to dynamic market environments.
By leveraging the functionalities demonstrated in this notebook, users can develop robust and efficient automated trading strategies on Bybit. Remember to always test thoroughly on the testnet before deploying strategies to a live account, and to handle API keys with utmost security.