DEV Community

Walid Koussa
Walid Koussa

Posted on Originally published at edgoro.com Fully Autonomous

Grid trading bot backtest: a fresh Bitcoin grid every month for 58 months

Grid bots are sold on a simple promise: price goes up and down, the bot buys low and sells high on every swing, and "grid profit" ticks up all day. The number on the bot's screen is real. It's also only half the account: it counts the completed grids and leaves out the coin the bot is holding.

We wanted to know how both halves add up on real prices. So we started a fresh grid on Bitcoin at the beginning of every month from January 2022 to October 2026, 58 months in all, replayed each one on 1-minute candles, and compared it with simply buying Bitcoin with the same money.

The short version: the grid finished positive in 64% of months against 55% for holding, with half the swings. But its average month was −0.39% against +2.03% for holding. It lost almost as much in the crashes and missed most of the rallies.

The test

Setting Value
Market Binance spot BTCUSDT, 1-minute candles, January 2022 to October 2026 (58 months), files checked against Binance's SHA-256 checksums
Grid Starts at the month's first open; range ±10% around it; 20 geometric grids, 1.01% apart
Sizing The same amount of Bitcoin per grid, as in the grid trading calculator: the grids above the price start with coin bought at the open, the others with a buy order
Capital $10,000, fresh every month
Fees 0.1% per fill, the regular spot rate on Binance
Fills A limit order fills when the candle's path touches its line: open, low, high, close on rising candles; open, high, low, close on falling ones
End of month Coin valued at the month's last close, against buying Bitcoin with $10,000 at the same open and holding it (no fee)

We fixed these rules before running anything. We also ran three other settings, all shown further down.

The results

Monthly result Grid Hold Bitcoin
Average month −0.39% +2.03%
Median month +3.15% +2.20%
Positive months 64% 55%
Best month +8.01% +43.57%
Worst month −34.04% −37.29%
Standard deviation 8.00% 15.41%
Sum of the 58 monthly results −$2,283 +$11,779

The grid beat holding in 57% of months and was usually in profit. A typical month completed 46 grids (median), and the realized grid profit averaged $232.52 a month, the figure a bot screen shows. Yet the account averaged a loss, because the coin the grid held lost more than the grids earned.

Where the money went

Month type Months Grid Hold
Bitcoin fell more than 10% 13 −13.08% −17.23%
Bitcoin within ±5% 15 +2.89% −0.77%
Bitcoin rose more than 10% 16 +4.70% +20.37%

In a sideways month the grid did exactly what it promises. In a falling month, every buy order filled on the way down and the grid ended almost fully in Bitcoin, so it lost nearly as much as holding. In a rising month it sold its coins one line at a time and was all in cash by the top of the range, so it kept a small part of the rally.

The range itself rarely held. Price stayed inside ±10% for the whole month in only 12 of the 58 months. It broke below the range in 15, above it in 24, and both ways in 7.

Three months show the pattern:

  • February 2023, Bitcoin +0.07% (23,125 to 23,142): grid +3.49%, with 75 completed grids.
  • February 2024, Bitcoin +43.57% (42,580 to 61,131): grid +3.05%, 12 completed grids, then all in cash.
  • June 2022, Bitcoin −37.29% (31,801 to 19,942): grid −34.04%. Every buy filled.

Do the settings change the picture?

Setting Average month Median month Positive months Worst month Sum of 58 months
±5%, 10 grids −0.82% +2.53% 64% −34.83% −$4,753
±10%, 20 grids (main) −0.39% +3.15% 64% −34.04% −$2,283
±10%, 50 grids −1.44% +2.50% 64% −34.56% −$8,351
±20%, 40 grids +0.11% +2.59% 64% −32.30% +$648
Hold Bitcoin +2.03% +2.20% 55% −37.29% +$11,779
  • A narrow range (±5%) never held for a whole month: price left it in all 58.
  • More grids in the same range completed far more trades (a median of 304 a month at 50 grids, against 46 at 20) but each one earned less and paid two fees, so the result got worse.
  • A wide range (±20%) stayed intact in 41 months and came out slightly positive, still far behind holding over a period in which Bitcoin went from 46,217 to 77,582.

That last point matters for reading the whole test: Bitcoin rose 68% over these 58 months. Holding is hard to beat in a rising market, and a different period, flat or falling for years, would tilt the comparison toward the grid. What doesn't depend on the period is the shape: the grid trades a share of the rallies for a steadier month, and gives that steadiness back in a crash.

The simulator

This is the function we ran, in Python 3.14 with numpy 2.5.4. o, h, l and c are the month's 1-minute opens, highs, lows and closes; it returns the month's result and the counts used above.

def grid_month(o, h, l, c, width, grids, invest=10_000.0, fee=0.001):
    p0 = o[0]
    lower, upper = p0 * (1 - width), p0 * (1 + width)
    lines = [lower * (upper / lower) ** (k / grids) for k in range(grids + 1)]
    holds = [lines[i] >= p0 for i in range(grids)]  # True: grid holds coin, waits to sell at lines[i+1]
    q = invest / (sum(holds) * p0 + sum(lines[i] for i in range(grids) if not holds[i]))
    cash, coin, fees = invest, 0.0, 0.0
    n0 = sum(holds)
    cash -= n0 * q * p0 * (1 + fee)
    fees += n0 * q * p0 * fee
    coin += n0 * q
    completed, grid_profit = 0, 0.0
    from_grid = [False] * grids  # coin of this grid was bought by the grid's own buy order
    buy_px = [p0] * grids

    def move(a, b):
        nonlocal cash, coin, fees, completed, grid_profit
        if b < a:  # falling: buy lines in [b, a)
            lo, hi = bisect.bisect_left(lines, b), bisect.bisect_left(lines, a)
            for i in range(min(hi, grids) - 1, lo - 1, -1):
                if not holds[i]:
                    px = lines[i]
                    cash -= q * px * (1 + fee)
                    fees += q * px * fee
                    coin += q
                    holds[i], from_grid[i], buy_px[i] = True, True, px
        elif b > a:  # rising: sell lines in (a, b]
            lo, hi = bisect.bisect_right(lines, a), bisect.bisect_right(lines, b)
            for k in range(max(lo, 1), hi):
                i = k - 1
                if holds[i]:
                    px = lines[k]
                    cash += q * px * (1 - fee)
                    fees += q * px * fee
                    coin -= q
                    if from_grid[i]:
                        completed += 1
                        grid_profit += q * (px - buy_px[i]) - fee * q * (px + buy_px[i])
                    holds[i], from_grid[i] = False, False

    for oo, hh, ll, cc in zip(o, h, l, c):
        first, second = (ll, hh) if cc >= oo else (hh, ll)
        move(oo, first)
        move(first, second)
        move(second, cc)
    end = c[-1]
    equity = cash + coin * end
    return dict(
        p0=p0, end=end, ret=(equity / invest - 1) * 100, hold=(end / p0 - 1) * 100, completed=completed, grid_profit=grid_profit,
        fees=fees, exit_low=float(np.min(l)) < lower, exit_high=float(np.max(h)) > upper, low=float(np.min(l)), high=float(np.max(h)),
    )
Enter fullscreen mode Exit fullscreen mode

It needs import bisect and import numpy as np. The monthly files come from Binance's public data site (data.binance.vision, spot, klines, BTCUSDT, 1m), and all four settings run in under ten seconds. As a check, we ran it on a price moving straight down, then straight up, through a $10,000 grid of 20 geometric lines from 90 to 110 started at 100: it ended exactly where the calculator's formulas put it, −$841.63 at 89 and +$255.08 at 111.

Limits of this test

  • Touch fills are generous. A limit order touched once may not fill in a real order book. The test fills it.
  • One-minute candles hide moves inside the minute. Price can cross a line and come back within a minute; the test doesn't see that grid, so it can also undercount completed grids.
  • One coin, one period, one rule. Trailing grids, grids with a stop-loss and futures grids with leverage behave differently, and platforms size their grids in different ways.
  • No funding, no slippage. Spot only, so no funding; slippage would lower every result a little.

What to take from it

  • Grid profit on the screen is not your profit. Look at the whole account, coin included.
  • A long spot grid is a bet that price stays in the range, plus a position that grows as price falls. Before starting one, work out what you hold at the lower bound with the grid trading calculator, and only use a range you'd be happy to hold at the bottom of.
  • Narrow grids pay more fees. A gap under two fees loses money on every completed grid; the trading fee calculator shows how costs scale with trade size.
  • 58 months is a small sample for monthly results. The backtest sample size calculator shows how wide the uncertainty stays.

Sources

Test your own idea with Edgoro

The Backtest Lab tests crypto strategies on real exchange data with fees and slippage. When the rules hold up, the crypto bot builder writes the Python bot, with closed candles, exchange limits and state saved between restarts. To write a grid bot yourself, start from a Python crypto trading bot with ccxt.

This study is for education and describes the past on one market. It isn't a recommendation to use or avoid grid bots. Crypto trading can lose money quickly; test any bot on a testnet or paper account first.


Originally published on edgoro.com.

Top comments (0)