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["value"]
center = atr_data["bid"] # current bid as the grid centre
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}/indicators",
headers={"X-API-Key": API_KEY},
params={"symbol": symbol, "timeframe": timeframe},
)
data = bb_resp.json()["data"]["indicators"]
upper = data["BB_upper"]
lower = data["BB_lower"]
middle = data["BB_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.
Run this against live data.
Every account starts pay-as-you-go with $2.50 of credit and no card. Paste the key into the samples above and the requests work unchanged.