Grid trading in Python
Grid trading scales into a position as price moves against you, here is a grid strategy in Python, backtested with real costs.
What is grid trading?
Grid trading places a ladder of orders at preset price levels, buying more as price falls and trimming as it rises. It profits from oscillation in range-bound markets without predicting direction, the position simply grows as the market dips and shrinks as it recovers.
Its weakness is a sustained one-way move: the grid keeps adding to a losing side. Bounding the ladder and choosing range-bound markets are what separate a working grid from a blow-up.
The logic
We anchor the grid to a rolling z-score against the 100-bar mean, so the 'levels' are statistical rather than fixed prices that go stale as the market drifts.
Position size steps up as price falls further below the mean: a third of full size below the mean, two thirds below -1σ, full size below -2σ, and flat above the mean. That is a bounded long-only grid below fair value.
grid trading in Python
Here is the full strategy in the Manifold-BT expression DSL. It imports manifoldbt, builds the signal, configures realistic execution, and runs the backtest against Rust.
import manifoldbt as mbt
from manifoldbt.indicators import close
from manifoldbt.helpers import time_range, Slippage, Interval
# Grid anchored to a rolling z-score of price vs its 100-bar mean
z = close.zscore(100)
# Ladder into longs as price stretches further below the mean
strategy = (
mbt.Strategy.create("grid")
.signal("z", z)
.size(
mbt.when(z < -2.0, 1.0,
mbt.when(z < -1.0, 0.66,
mbt.when(z < 0.0, 0.33, 0.0)))
)
)
start, end = time_range("2021-01-01", "2026-01-01")
config = mbt.BacktestConfig(
universe={"binance": ["ETHUSDT"]},
time_range_start=start,
time_range_end=end,
bar_interval=Interval.hours(1),
initial_capital=10_000,
fees=mbt.FeeConfig.binance_perps(),
slippage=Slippage.fixed_bps(2),
warmup_bars=100,
)
store = mbt.ingest(provider="binance", symbol="ETHUSDT", symbol_id=1,
interval="1h", start="2021-01-01T00:00:00Z",
end="2026-01-01T00:00:00Z")
result = mbt.run(strategy, config, store)
print(result.summary())
mbt.plot.tearsheet(result, show=True)Backtest it with Manifold-BT
Hourly ETH bars give the grid plenty of oscillation to work with. The laddered sizing means the strategy is rarely all-in, which smooths the equity curve but caps upside.
Grids look fantastic until the regime turns trending, so always inspect the max drawdown and the worst sustained move, not just the total return. Test it across both a ranging and a strongly trending year.
| Universe | {"binance": ["ETHUSDT"]} |
| Bar interval | 1h |
| Fees | FeeConfig.binance_perps() |
| Slippage | fixed 2 bps |
| Warmup | 100 bars |
| Total return | +27.9% |
| Sharpe | 1.08 |
| Max drawdown | -18.9% |
| Win rate | 61% |
| Trades | 536 |
Keep reading
Run your first backtest
Install Manifold-BT and reproduce the backtest above in seconds. The Rust core runs years of bars sub-second so you can sweep parameters instead of waiting.