Grid Trading Bot Python Tutorial: Build Your First Bot
Learn to build a crypto grid trading bot in Python from scratch. Connect to Binance API, set up grid logic, place automated orders, and run it in live markets.
Learn to build a crypto grid trading bot in Python from scratch. Connect to Binance API, set up grid logic, place automated orders, and run it in live markets.
Grid trading is one of the most battle-tested strategies for ranging crypto markets — and one of the most satisfying to automate. The idea is simple: place a ladder of buy orders below the current price and sell orders above it. When Bitcoin oscillates between $60,000 and $65,000 for two weeks, your bot quietly collects profit on every swing while you do other things. This tutorial walks through building a functional grid trading bot in Python, connected to Binance via the CCXT library, with real order placement logic you can test on testnet before risking a cent.
A grid bot divides a price range into equal intervals — the grid levels. It places a buy limit order at each level below the current price and a sell limit order at each level above. When a buy order fills, the bot immediately places a sell order one grid step higher. When that sell fills, it drops a new buy one step lower. Every completed buy-sell round trip earns the grid spread minus trading fees. Repeat a hundred times in a choppy market and the returns add up fast.
The strategy thrives in sideways markets and suffers in strong trends. If ETH breaks out sharply upward through your entire grid, your buy orders never fill and you miss the move holding USDT. If it crashes through your lower boundary, you accumulate a position at progressively lower prices. This is why experienced traders on Bybit and OKX pair grid bots with a trend filter — if price is moving strongly in one direction, the bot pauses and waits. Platforms like VoiceOfChain provide real-time momentum and market regime signals via API, which your Python bot can query before activating the grid: trending signal means stay flat, ranging signal means deploy the grid.
Grid trading works best on assets with high intraday volatility but no strong directional bias. BTC/USDT and ETH/USDT during consolidation phases are the most common pairs. Avoid thin mid-cap alts until you've verified the logic works on liquid pairs first.
The most practical library for connecting a Python bot to crypto exchanges is CCXT — it supports over 100 exchanges including Binance, Bybit, OKX, KuCoin, Bitget, and Gate.io with a unified API. You write the same order placement code once and swap the exchange with a single line. Install the required packages:
# Create an isolated virtual environment
python -m venv gridbot-env
source gridbot-env/bin/activate # Windows: gridbot-env\Scripts\activate
# Install dependencies
pip install ccxt python-dotenv pandas
# Verify ccxt installed correctly
python -c "import ccxt; print(ccxt.__version__)"
Store your API credentials in a .env file — never hardcode them in the script. Binance offers a testnet at testnet.binance.vision where you can generate test API keys and place real orders against a simulated order book. Always develop against testnet first.
import ccxt
import os
from dotenv import load_dotenv
load_dotenv() # Reads from .env file in the same directory
# Connect to Binance — swap 'binance' for 'bybit', 'okx', 'kucoin', 'bitget', etc.
exchange = ccxt.binance({
'apiKey': os.getenv('BINANCE_API_KEY'),
'secret': os.getenv('BINANCE_SECRET'),
'options': {
'defaultType': 'spot', # Use 'future' for USDT-M perpetuals
},
})
# Enable testnet for safe development — remove this line for live trading
exchange.set_sandbox_mode(True)
# Verify connection and fetch current price
balance = exchange.fetch_balance()
print(f"USDT available: {balance['USDT']['free']}")
ticker = exchange.fetch_ticker('BTC/USDT')
current_price = ticker['last']
print(f"BTC/USDT current price: ${current_price:,.2f}")
The core of the bot is the grid calculator — a class that takes your configuration parameters (price range, number of levels, total capital) and generates the exact order prices and sizes. Here's a clean, readable implementation:
class GridBot:
def __init__(self, exchange, symbol, lower_price, upper_price, grid_levels, total_investment):
self.exchange = exchange
self.symbol = symbol
self.lower = lower_price
self.upper = upper_price
self.levels = grid_levels
self.investment = total_investment
self.active_orders = {} # order_id -> {side, price, size}
def calculate_grid(self):
"""Generate evenly spaced grid price levels."""
step = (self.upper - self.lower) / self.levels
return [round(self.lower + i * step, 2) for i in range(self.levels + 1)]
def order_size_at(self, price):
"""Divide investment evenly across all grid levels."""
usdt_per_level = self.investment / self.levels
return round(usdt_per_level / price, 6)
def place_initial_orders(self):
ticker = self.exchange.fetch_ticker(self.symbol)
current_price = ticker['last']
grid = self.calculate_grid()
for price in grid:
size = self.order_size_at(price)
if size * price < 10: # Binance minimum order is $10
print(f"Skipping {price} — order size too small")
continue
if price < current_price:
order = self.exchange.create_limit_buy_order(self.symbol, size, price)
self.active_orders[order['id']] = {'side': 'buy', 'price': price, 'size': size}
print(f" BUY {size:.6f} BTC @ ${price:,.2f}")
elif price > current_price:
order = self.exchange.create_limit_sell_order(self.symbol, size, price)
self.active_orders[order['id']] = {'side': 'sell', 'price': price, 'size': size}
print(f" SELL {size:.6f} BTC @ ${price:,.2f}")
# Configure and deploy
bot = GridBot(
exchange=exchange,
symbol='BTC/USDT',
lower_price=59000,
upper_price=66000,
grid_levels=10,
total_investment=1000 # $1,000 USDT split across 10 levels
)
print("Placing initial grid orders...")
bot.place_initial_orders()
print(f"Grid active: {len(bot.active_orders)} orders placed")
With 10 levels across a $7,000 range, each grid step is $700. Your $1,000 investment gets split into $100 per level, giving you roughly 0.0016 BTC per order at $60,000. Every time BTC oscillates across a grid line, that $100 slice earns the $700 spread minus the 0.1% Binance maker fee. Small per trade, but in an active sideways market you might see 20-40 fills per day — that compounds quickly.
Placing the initial grid is step one. The bot also needs a heartbeat loop that watches for filled orders and posts counter-orders — when a buy fills, post a sell one step higher; when a sell fills, post a buy one step lower. This keeps the grid self-replenishing:
import time
def run_grid_loop(bot, check_interval=15):
"""
Poll for filled orders and place counter-orders.
check_interval: seconds between REST polls (use WebSockets in production)
"""
grid = bot.calculate_grid()
step = grid[1] - grid[0] # grid spacing in USD
filled_count = 0
total_profit = 0.0
print(f"Grid monitoring started. Step size: ${step:.2f}")
while True:
try:
open_orders = bot.exchange.fetch_open_orders(bot.symbol)
open_ids = {o['id'] for o in open_orders}
for order_id, data in list(bot.active_orders.items()):
if order_id not in open_ids:
# Order was filled — place counter-order
side = data['side']
price = data['price']
size = data['size']
if side == 'buy':
counter_price = round(price + step, 2)
new_order = bot.exchange.create_limit_sell_order(
bot.symbol, size, counter_price
)
profit_estimate = size * step * 0.998 # rough after-fee profit
print(f"[FILL] BUY @ ${price:,.2f} → SELL posted @ ${counter_price:,.2f} | est. profit: ${profit_estimate:.4f}")
else:
counter_price = round(price - step, 2)
new_order = bot.exchange.create_limit_buy_order(
bot.symbol, size, counter_price
)
profit_estimate = size * step * 0.998
print(f"[FILL] SELL @ ${price:,.2f} → BUY posted @ ${counter_price:,.2f} | est. profit: ${profit_estimate:.4f}")
filled_count += 1
total_profit += profit_estimate
del bot.active_orders[order_id]
bot.active_orders[new_order['id']] = {
'side': 'sell' if side == 'buy' else 'buy',
'price': counter_price,
'size': size
}
if filled_count % 10 == 0 and filled_count > 0:
print(f"--- Total fills: {filled_count} | Estimated profit: ${total_profit:.4f} ---")
except ccxt.NetworkError as e:
print(f"Network error, retrying: {e}")
except ccxt.ExchangeError as e:
print(f"Exchange error: {e}")
break # Stop on hard exchange errors
time.sleep(check_interval)
# Start the loop
run_grid_loop(bot)
In production, switch from REST polling to WebSocket order feeds. CCXT Pro supports WebSocket streams with the same unified API — use watchOrders() instead of fetch_open_orders(). REST polling at 15-second intervals is fine for testing but will approach Binance rate limits if you run multiple pairs simultaneously.
A grid bot without guardrails will fail silently. These are the non-negotiable safety features before running any real capital:
For signal-based activation, VoiceOfChain exposes a real-time API with trend and momentum data. Your bot can query the current regime before deploying the grid: if the signal shows a strong directional move in either direction, the bot holds off and polls again in 30 minutes. This single filter dramatically improves grid bot performance by avoiding the classic trap of deploying into a breakout.
| Market Condition | Grid Levels | Range Width | Best Exchanges |
|---|---|---|---|
| Low volatility range | 5-8 | ±3-5% | Binance, Bybit |
| Medium volatility range | 10-15 | ±8-12% | OKX, Binance |
| High volatility chop | 15-20 | ±15-25% | Bybit, Bitget |
| Strong trend detected | Pause bot | Wait | Resume after consolidation |
Run your bot on Binance testnet for at least one full week before touching real capital. Watch how it handles fills in rapid succession, verify the counter-order logic fires correctly every time, and check that your balance tracking matches exchange balances exactly. When you move live, start with a small allocation — $300-$500 — on a liquid pair like BTC/USDT or ETH/USDT on Binance or Bybit. Illiquid pairs on Gate.io or smaller exchanges introduce slippage that the simple grid math above doesn't account for.
The code in this guide is a working foundation, not a finished product. Production-grade bots add WebSocket streams to eliminate polling latency, persistent state storage so the bot survives server restarts, Telegram webhook alerts on every fill and error, and integration with signal platforms like VoiceOfChain for market regime detection before grid activation. Build each piece incrementally. The hardest part — the grid logic and order management — is already done.