Market Making Bot in Python: Complete Crypto Guide
Step-by-step guide to building a Python crypto market making bot. Covers Binance API, spread logic, order placement, inventory risk, and real working code examples.
Step-by-step guide to building a Python crypto market making bot. Covers Binance API, spread logic, order placement, inventory risk, and real working code examples.
Market making is one of the oldest and most consistently profitable strategies in financial markets. Firms like Citadel and Virtu built empires on it. In crypto, the same principle applies — but the playing field is more accessible, the APIs are open, and a skilled developer with Python can build a functional market making bot in a weekend. Here is how it works and how to build one.
A market maker provides liquidity to an exchange by placing simultaneous buy (bid) and sell (ask) limit orders around the current mid-market price. The difference between those two prices — the spread — is where the profit comes from. Every time a market order from another trader hits your bid or ask, you capture that spread as gross profit. On Binance, for instance, the BTC/USDT spread might be just $1–$5 at any given moment. That sounds trivial, but a bot processing hundreds of fills per day compounds those gains significantly. The key insight: you are not betting on price direction. You profit from trading activity itself. The risk, however, is inventory risk. If BTC drops 3% before your ask order fills, you are sitting on an unrealized loss from the long position you accumulated on the bid side. Managing that exposure is what separates functional market making bots from strategies that blow up their accounts.
Before writing a single line of code, understand what your bot needs to do. A complete market making system has five moving parts that all need to work together.
Most amateur bots only implement the first three and ignore inventory and risk controls — which is exactly how they lose money. A production-grade market making bot treats inventory management as a first-class concern, not an afterthought.
The quickest path to a working crypto market making bot in Python is the CCXT library (CryptoCurrency eXchange Trading). It provides a unified API interface for over 100 exchanges including Binance, Bybit, OKX, and Bitget — letting you write exchange-agnostic code that can be retargeted with a single config change. You'll also need python-dotenv to keep API credentials out of your source code.
# Create isolated environment and install dependencies
python3 -m venv venv
source venv/bin/activate
pip install ccxt python-dotenv pandas numpy
Generate API keys from your exchange's account settings. On Binance, go to API Management and create a key with spot trading permissions enabled. Store credentials in a .env file at the project root — never hardcode them in the script.
import ccxt
import os
from dotenv import load_dotenv
load_dotenv()
# Swap 'binance' for 'bybit', 'okx', or 'bitget' to change exchange
exchange = ccxt.binance({
'apiKey': os.getenv('BINANCE_API_KEY'),
'secret': os.getenv('BINANCE_SECRET'),
'enableRateLimit': True,
'options': {
'defaultType': 'spot', # use 'future' for perpetuals
}
})
def get_order_book(symbol='BTC/USDT', depth=5):
book = exchange.fetch_order_book(symbol, limit=depth)
best_bid = book['bids'][0][0] # highest buy price
best_ask = book['asks'][0][0] # lowest sell price
mid_price = (best_bid + best_ask) / 2
spread_pct = (best_ask - best_bid) / mid_price * 100
return {
'bid': best_bid,
'ask': best_ask,
'mid': mid_price,
'spread_pct': spread_pct
}
book = get_order_book()
print(f"Mid: {book['mid']:.2f} | Market spread: {book['spread_pct']:.4f}%")
Always enable enableRateLimit: True in CCXT. Binance will temporarily ban your IP for excessive REST requests. During active markets your bot will hit limits far faster than you expect — the library handles pacing automatically when this flag is set.
The core logic is straightforward: calculate where to place your bid and ask, place them, wait for fills, then cancel and reprice. The complexity is in how you calculate those prices. The most common approach is a symmetric spread around the mid-price. If ETH/USDT is trading at $3,000 and you want a 0.2% total spread, you place a bid at $2,997 and an ask at $3,003. That $6 gap is your gross profit per round trip — one buy fill and one sell fill. More sophisticated bots skew their quotes based on inventory. If you are holding too much ETH, you shade your ask price lower to sell faster and your bid price lower to slow further accumulation. This inventory-aware quoting is what professional market makers do and what separates consistently profitable bots from neutral ones that leak money in trending conditions.
import time
CONFIG = {
'symbol': 'ETH/USDT',
'spread_pct': 0.002, # 0.2% total spread
'order_size': 0.05, # ETH per side
'max_inventory': 0.5, # max ETH to hold long
'inventory_skew': 0.5, # quote skew aggressiveness (0-1)
'refresh_interval': 5, # seconds between requotes
}
def calculate_quotes(mid_price, inventory, config):
half_spread = mid_price * config['spread_pct'] / 2
# Positive inventory → skew both quotes down to sell faster
max_inv = config['max_inventory']
skew = (inventory / max_inv) * config['inventory_skew'] * half_spread
bid = round(mid_price - half_spread - skew, 2)
ask = round(mid_price + half_spread - skew, 2)
return bid, ask
def run_market_maker():
open_orders = []
inventory = 0.0
while True:
try:
# Cancel existing quotes before repricing
for order in open_orders:
try:
exchange.cancel_order(order['id'], CONFIG['symbol'])
except Exception:
pass
open_orders = []
book = get_order_book(CONFIG['symbol'])
bid_price, ask_price = calculate_quotes(
book['mid'], inventory, CONFIG
)
bid_order = exchange.create_limit_buy_order(
CONFIG['symbol'], CONFIG['order_size'], bid_price
)
ask_order = exchange.create_limit_sell_order(
CONFIG['symbol'], CONFIG['order_size'], ask_price
)
open_orders = [bid_order, ask_order]
print(
f"Bid: {bid_price} | Ask: {ask_price} "
f"| Spread: {ask_price - bid_price:.2f} "
f"| Inv: {inventory:.4f}"
)
time.sleep(CONFIG['refresh_interval'])
except KeyboardInterrupt:
print("Shutting down — cancelling open orders...")
for order in open_orders:
exchange.cancel_order(order['id'], CONFIG['symbol'])
break
except Exception as e:
print(f"Error: {e} — retrying in 10s")
time.sleep(10)
# run_market_maker() # Uncomment to start
This bot cancels and replaces orders every 5 seconds. That is a reasonable default for most liquid pairs on Binance or OKX — frequent enough to stay competitive, slow enough to avoid rate limit bans. On perpetual futures on Bybit, you can tighten the refresh to 1–2 seconds and switch to WebSocket feeds instead of REST polling for lower latency order book data.
Inventory management determines whether your market making bot is profitable or just busy. The danger is one-sided fills: price drops sharply, your bids fill but your asks do not, and now you are holding a losing long position with no automated response. There are two complementary approaches. Hard limits stop placing bids when inventory exceeds a threshold, preventing further accumulation. Dynamic skewing, already shown above, continuously adjusts quote prices to incentivize fills on the heavy side before limits are hit. For real capital you need both. Hard limits prevent catastrophic exposure. Skewing reduces average inventory and improves fill balance under normal conditions.
def get_inventory_status(symbol, config):
"""Fetch live balance and compute inventory exposure fraction."""
balance = exchange.fetch_balance()
base_asset = symbol.split('/')[0] # 'ETH' from 'ETH/USDT'
base_free = balance[base_asset]['free']
inv_pct = base_free / config['max_inventory']
return {
'base': base_free,
'inv_pct': inv_pct,
'at_limit': inv_pct >= 1.0,
'nearly_empty': base_free < 0.001,
}
def should_quote(side, inv):
"""Returns False if this side should be suppressed."""
if side == 'buy' and inv['at_limit']:
return False # maxed out — stop accumulating
if side == 'sell' and inv['nearly_empty']:
return False # nothing to sell
return True
# In the main loop, replace hardcoded order placement with:
# inv = get_inventory_status(CONFIG['symbol'], CONFIG)
# if should_quote('buy', inv):
# bid_order = exchange.create_limit_buy_order(...)
# if should_quote('sell', inv):
# ask_order = exchange.create_limit_sell_order(...)
Add a daily loss limit kill switch. Check cumulative P&L at the top of every loop iteration and halt the bot if losses exceed your threshold — for example 2% of allocated capital. A malfunctioning bot in a fast market can lose more in minutes than you expect.
For broader market context while your bot is running, VoiceOfChain provides real-time sentiment signals and price alerts that tell you when a trending move is underway. Market making works best in sideways, range-bound conditions. Pausing your bot during a confirmed breakout or high-volatility event prevents accumulating inventory into a move that will not reverse quickly.
Not every exchange and pair is a good candidate for a Python market making bot. You want sufficient volume so orders fill regularly, but not so much that you are competing against professional HFT firms with latency and colocation advantages you cannot match. Binance spot is the deepest market for BTC/USDT and ETH/USDT, but the competition is intense. More accessible targets for a retail Python bot are mid-tier pairs on Binance — SOL/USDT, AVAX/USDT — or moving to Gate.io and Bitget where smaller pairs have meaningful volume with far less sophisticated competition. Fee structure matters enormously. Most exchanges use a maker-taker model where limit orders that add liquidity pay a lower maker fee. On Bybit and OKX perpetuals, maker fees are actually negative — the exchange pays you a rebate for providing liquidity. Running a maker-only strategy on those platforms means collecting the spread plus a fee rebate on every single fill. That fundamentally changes the profitability math compared to spot markets.
| Exchange | Spot Maker Fee | Perp Maker Fee | Notes |
|---|---|---|---|
| Binance | 0.10% | 0.02% | Lower with BNB discount |
| Bybit | 0.10% | -0.01% | Rebate on perp makers |
| OKX | 0.08% | -0.02% | Rebate on perp makers |
| Gate.io | 0.20% | 0.015% | Good mid-cap spot pairs |
| Bitget | 0.10% | -0.016% | Rebate on futures makers |
If your bot targets perpetual futures, Bybit and OKX are hard to beat on fee structure alone. A maker-only strategy collecting both the spread and a rebate means your break-even threshold is dramatically lower than on spot markets with positive taker fees on both sides.
A Python crypto market making bot is one of the most teachable algorithmic trading strategies — the mechanics are clear, the code is accessible, and the profitability logic is transparent. The hard part is not writing the code. It is the operational discipline: running it reliably around the clock, managing inventory under pressure, choosing the right pairs on the right exchanges, and knowing exactly when to shut it down. Start on Binance testnet. Study your fill patterns and inventory curves for at least a week before deploying real capital. Layer in exchange-specific fee optimizations — maker rebates on Bybit or OKX can turn a marginally profitable strategy into a solid one. Use market signal platforms like VoiceOfChain to stay aware of macro conditions so you are not quoting into a trending breakout when you should be sitting flat. Market making rewards patience and iteration over cleverness. Ship the simplest working version first, measure it, and improve from real data.