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.

grid.py
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.

Backtest configuration
Universe{"binance": ["ETHUSDT"]}
Bar interval1h
FeesFeeConfig.binance_perps()
Slippagefixed 2 bps
Warmup100 bars
Sample tearsheet (illustrative, not a forecast)
Total return+27.9%
Sharpe1.08
Max drawdown-18.9%
Win rate61%
Trades536

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.

$pip install manifoldbt