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

Frequently asked questions

Is grid trading profitable?

A grid profits from oscillation in range-bound markets without predicting direction, but it keeps adding to a losing side in a sustained trend. Bounding the ladder and testing across both ranging and trending years is what separates a working grid from a blow-up.

What happens to a grid in a strong trend?

A one-way move is the grid's worst case: the ladder accumulates against the trend and drawdown grows. Always inspect the max drawdown and the worst sustained move, not just the total return.

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