Grid Trading Bot Development
A grid bot places a grid of limit orders above and below the current price and automatically repositions them when executed. It profits on sideways market movement: price "jumps" up and down, bot buys on drops and sells on rises, locking in profit at each grid step.
Types of Grid Bots
Arithmetic Grid (Uniform Spacing)
Orders placed at fixed distance in USDT:
$51,000 SELL
$50,500 SELL
$50,000 — current price
$49,500 BUY
$49,000 BUY
Step = $500 (fixed). Simple to understand, but % return per level varies.
Geometric Grid (Percentage Spacing)
Orders placed at fixed % distance:
$51,020 SELL (+2%)
$50,020 SELL (+2% from previous)
$49,040 BUY (-2%)
$48,060 BUY (-2%)
Step = 2% (fixed percentage). More correct: % profit is equal at each step.
Implementation
Grid Initialization
from decimal import Decimal
import math
class GridBot:
def __init__(self, config: GridConfig, exchange_client):
self.config = config
self.exchange = exchange_client
self.active_orders: dict[str, GridOrder] = {}
self.realized_pnl = Decimal(0)
def calculate_grid_levels(self) -> list[Decimal]:
"""Calculate grid price levels"""
lower = self.config.lower_price
upper = self.config.upper_price
num_grids = self.config.grid_count
levels = []
if self.config.grid_type == 'arithmetic':
step = (upper - lower) / num_grids
for i in range(num_grids + 1):
levels.append(lower + step * i)
elif self.config.grid_type == 'geometric':
# Geometric progression
ratio = (upper / lower) ** (Decimal(1) / num_grids)
for i in range(num_grids + 1):
levels.append(lower * (ratio ** i))
return levels
async def initialize_grid(self, current_price: Decimal):
levels = self.calculate_grid_levels()
investment_per_grid = self.config.total_investment / self.config.grid_count
for i in range(len(levels) - 1):
lower_level = levels[i]
upper_level = levels[i + 1]
mid_level = (lower_level + upper_level) / 2
if mid_level < current_price:
# Below current price — BUY order
quantity = investment_per_grid / lower_level
order = await self.exchange.place_limit_order(
side='buy',
price=lower_level,
quantity=quantity
)
self.active_orders[order.id] = GridOrder(
order_id=order.id,
side='buy',
price=lower_level,
quantity=quantity,
grid_index=i
)
else:
# Above current price — SELL order (if position exists)
# On startup without base asset — only BUY orders
pass
Processing Filled Orders
async def on_order_filled(self, order_id: str, fill_price: Decimal):
grid_order = self.active_orders.pop(order_id, None)
if not grid_order:
return
levels = self.calculate_grid_levels()
step_profit = Decimal(0)
if grid_order.side == 'buy':
# Buy order executed → place SELL at next level up
sell_price = levels[grid_order.grid_index + 1]
sell_order = await self.exchange.place_limit_order(
side='sell',
price=sell_price,
quantity=grid_order.quantity
)
self.active_orders[sell_order.id] = GridOrder(
order_id=sell_order.id,
side='sell',
price=sell_price,
quantity=grid_order.quantity,
grid_index=grid_order.grid_index + 1,
buy_price=fill_price # remember buy price
)
elif grid_order.side == 'sell':
# Sell order executed → place BUY back
buy_price = levels[grid_order.grid_index - 1]
# Lock in profit
step_profit = (grid_order.price - grid_order.buy_price) * grid_order.quantity
self.realized_pnl += step_profit
buy_order = await self.exchange.place_limit_order(
side='buy',
price=buy_price,
quantity=grid_order.quantity
)
self.active_orders[buy_order.id] = GridOrder(
order_id=buy_order.id,
side='buy',
price=buy_price,
quantity=grid_order.quantity,
grid_index=grid_order.grid_index - 1
)
logger.info(f"Grid step profit: {step_profit:.4f} USDT, Total realized: {self.realized_pnl:.4f}")
Parameters and Configuration
Key Parameters
| Parameter | Description | Typical Value |
|---|---|---|
lower_price |
Grid lower bound | -15% from current |
upper_price |
Grid upper bound | +15% from current |
grid_count |
Number of levels | 10-50 |
total_investment |
Total budget | Per balance |
grid_type |
arithmetic / geometric | geometric |
stop_loss |
Stop when exiting bounds | Optional |
take_profit |
Close at target PnL | Optional |
Parameter Optimization
Optimal grid step depends on asset volatility:
def suggest_grid_step(volatility_daily_percent: float, holding_days: int = 30) -> float:
"""
If asset moves X% per day on average,
grid step should be sufficient to capture movement.
Roughly: 0.5x - 1x daily volatility.
"""
suggested_step = volatility_daily_percent * 0.7
return max(0.5, min(5.0, suggested_step)) # from 0.5% to 5%
BTC with 3%/day volatility → step ~2%. ETH (5%/day) → step ~3.5%.
Risks and Limitations
Trending market — the main enemy of grid bot. On strong downtrend: BUY orders execute one after another, SELLs don't execute, losing position accumulates in base asset.
Management: limiting open BUY orders, automatic stop when price exits grid bounds, trailing grid (grid follows price).
Fees: with small grid step and high fees, profit per step may be less than fees. Formula: min_grid_step = 2 × fee_rate × 1.2 (buffer). At 0.1% fee: minimum step = 0.24%.
Grid bot works ideally on sideways market and stablecoin pairs (asset/stablecoin pairs with low volatility over long horizons). On rising market it earns less than simply holding.







