Building a Grid Trading Bot with Technical Indicators
Implement a grid trading strategy that uses technical indicators for dynamic grid placement, range detection, and risk management via the TickAtlas API.
What Is Grid Trading?
Grid trading places buy and sell orders at regular price intervals above and below a center price. As price oscillates, orders are filled on both sides, capturing profit from the natural back-and-forth movement. The strategy works best in ranging markets.
Grid Structure (EURUSD, center = 1.0850):
SELL @ 1.0890 [Grid +4]
SELL @ 1.0880 [Grid +3]
SELL @ 1.0870 [Grid +2]
SELL @ 1.0860 [Grid +1]
--- CENTER --- 1.0850
BUY @ 1.0840 [Grid -1]
BUY @ 1.0830 [Grid -2]
BUY @ 1.0820 [Grid -3]
BUY @ 1.0810 [Grid -4]
Using ATR for Grid Spacing
Fixed-pip grids fail when volatility changes. ATR-based grids adapt: wider spacing in volatile markets, tighter spacing in quiet markets.
import requests
from dataclasses import dataclass
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"
@dataclass
class GridLevel:
price: float
side: str # "buy" or "sell"
level: int
filled: bool = False
class SmartGrid:
def __init__(self, symbol: str, num_levels: int = 5,
atr_spacing_factor: float = 0.5):
self.symbol = symbol
self.num_levels = num_levels
self.atr_factor = atr_spacing_factor
self.levels: list[GridLevel] = []
def calculate_grid(self, timeframe: str = "H4") -> list[GridLevel]:
"""Build grid levels using ATR for spacing."""
# Fetch ATR for dynamic spacing
atr_resp = requests.get(
f"{BASE_URL}/indicator",
headers={"X-API-Key": API_KEY},
params={"symbol": self.symbol, "indicator": "ATR_14",
"timeframe": timeframe},
)
atr_data = atr_resp.json()["data"]
atr = atr_data["values"]["atr"]
center = atr_data["ohlcv"]["close"]
spacing = atr * self.atr_factor
self.levels = []
for i in range(1, self.num_levels + 1):
# Buy levels below center
self.levels.append(GridLevel(
price=round(center - (spacing * i), 5),
side="buy", level=-i,
))
# Sell levels above center
self.levels.append(GridLevel(
price=round(center + (spacing * i), 5),
side="sell", level=i,
))
self.levels.sort(key=lambda x: x.price, reverse=True)
return self.levels
# Example
grid = SmartGrid("EURUSD", num_levels=5, atr_spacing_factor=0.5)
levels = grid.calculate_grid("H4")
for level in levels:
print(f" {level.side.upper()} @ {level.price} [Level {level.level}]") Range Detection with Bollinger Bands
Grid trading is dangerous in trending markets. Use Bollinger Bands to detect whether the market is ranging (suitable for grids) or trending (avoid grids).
def is_ranging_market(symbol: str, timeframe: str = "D1") -> dict:
"""Detect if market is in a range using Bollinger Band width."""
bb_resp = requests.get(
f"{BASE_URL}/indicator",
headers={"X-API-Key": API_KEY},
params={"symbol": symbol, "indicator": "BOLLINGER_BANDS",
"timeframe": timeframe},
)
data = bb_resp.json()["data"]["values"]
upper = data["upper"]
lower = data["lower"]
middle = data["middle"]
# Band width as percentage of middle
width_pct = ((upper - lower) / middle) * 100
# Narrow bands suggest ranging market
is_ranging = width_pct < 2.0 # Adjust threshold per symbol
return {
"symbol": symbol,
"is_ranging": is_ranging,
"band_width_pct": round(width_pct, 3),
"upper": upper,
"lower": lower,
"grid_suitable": is_ranging,
}
# Only run grid bot on ranging pairs
for sym in ["EURUSD", "GBPUSD", "USDJPY", "AUDUSD"]:
result = is_ranging_market(sym)
if result["grid_suitable"]:
print(f"{sym}: RANGING (width={result['band_width_pct']}%) -- grid OK")
else:
print(f"{sym}: TRENDING (width={result['band_width_pct']}%) -- skip") Grid Execution Loop
import time
def run_grid_bot(symbol: str, timeframe: str = "H4"):
"""Main grid bot loop."""
grid = SmartGrid(symbol, num_levels=5, atr_spacing_factor=0.5)
levels = grid.calculate_grid(timeframe)
print(f"Grid initialized for {symbol}")
for level in levels:
print(f" {level.side.upper()} @ {level.price}")
while True:
# Check if market is still ranging
range_check = is_ranging_market(symbol, "D1")
if not range_check["grid_suitable"]:
print(f"Market trending -- pausing grid for {symbol}")
time.sleep(3600)
continue
# Get current price
price_resp = requests.get(
f"{BASE_URL}/indicator",
headers={"X-API-Key": API_KEY},
params={"symbol": symbol, "indicator": "RSI_14",
"timeframe": "M5"},
)
current = price_resp.json()["data"]["ohlcv"]["close"]
# Check if any grid levels are hit
for level in levels:
if level.filled:
continue
if level.side == "buy" and current <= level.price:
print(f"BUY triggered @ {level.price} (current: {current})")
level.filled = True
# Place buy order via broker API
elif level.side == "sell" and current >= level.price:
print(f"SELL triggered @ {level.price} (current: {current})")
level.filled = True
# Place sell order via broker API
# Recalculate grid every 4 hours
time.sleep(300) # Check every 5 minutes Risk Controls for Grid Trading
Maximum open positions
Cap the total number of open grid positions (e.g., 10). If all levels fill on one side, the market is trending and you are accumulating directional risk.
Hard stop-loss outside the grid
Place a hard stop beyond the outermost grid level. If price breaks through the entire grid, close everything at a defined maximum loss.
Trend filter override
If ADX rises above 30 or Bollinger Bands expand significantly, the market is trending. Shut down the grid and switch to a trend-following strategy or stand aside.
Related Reading
Try this with live data
Every account gets $2.50 in free PAYG credits. No card required — paste your API key and run the code above against live broker data.
Keep reading
All articles- Tutorial 11 min read
24/7 Crypto Monitoring: Building Always-On Analysis Systems
Build a monitoring system that watches crypto markets around the clock, detects significant moves, and sends alerts when conditions match your criteria.
March 28, 2026
- Tutorial 12 min read
How to Build an AI Market Analyst That Runs 24/7
Build a production-ready AI market analyst that monitors forex and crypto markets around the clock, generates daily briefings, and alerts you to opportunities via Telegram.
March 28, 2026
- Tutorial 10 min read
Using ATR for Dynamic Stop-Loss Placement
Learn how to use Average True Range (ATR) to set volatility-adjusted stop losses that adapt to market conditions, with full code examples.
March 28, 2026