Performance

Benchmarks — reamer_py vs. Backtrader

Real measurements, not extrapolations — every number on this page comes from actually running both engines on the same machine, on the same data, on functionally equivalent strategies. Nothing here is simulated or estimated.

reamer_py v1.0.0  ·  Backtrader 1.9.78.123  ·  single-threaded, single-core measurements

Test system

CPU
Intel Core i5-8250U @ 1.60GHz (4 cores / 8 threads, up to 3.4GHz turbo) — an 8th-gen ultrabook chip, not a workstation or server part
RAM
16 GB
OS
Debian (testing)
reamer_py
v1.0.0
Backtrader
1.9.78.123

Reamer's execution core is single-threaded by design — these results reflect one CPU core, not a multi-core advantage.

Why Backtrader

There is no certified, industry-standard benchmark suite for backtesting engines. Backtrader is the long-standing standard for retail Python backtesting — the tool an entire generation of retail/indie quants learned on, and still one of the most widely-used. It's been in long-term maintenance mode since 2023 (no major new features, last major commit in 2020), and current community consensus doesn't recommend it for brand-new projects in 2026 — but that doesn't change what it does when it runs.

Maintenance status affects future feature velocity, not the runtime behavior of the version actually benchmarked here, and it remains — like reamer_py — an event-driven engine: both step through the timeline one bar at a time via a per-bar Python callback (next() vs. on_bar()), rather than processing the entire history as a single batch array operation the way a purely vectorized backtester (e.g. VectorBT) does.

That's the axis that actually matters for a fair comparison — not "vectorized vs. not," since reamer_py is itself numpy-vectorized throughout: each on_bar call hands the strategy zero-copy numpy array views (per-ticker lookback windows, or 2D cross-sectional arrays across tickers for multi-asset strategies), a single call can return multiple orders at once via orders(*items), and numpy is a hard dependency because the core engine relies on vectorized execution internally, not just Python object loops.

What reamer_py and Backtrader share, and VectorBT doesn't, is the per-bar event loop — the thing that makes path-dependent state, realistic order management, and tick-level fills possible in the first place. Both engines ask the strategy the same question, once per bar, and the numbers below are what Backtrader actually does today, not a claim about its future.

What "minimal" and "realistic" mean

Two workloads were run against both engines, to separate two different questions.

minimal — pure per-bar callback cost

The strategy reads one price and does nothing else — never submits an order:

# reamer_py
def on_bar(self, data):
    _ = data["TICKER"].close[-1]
    return None

# Backtrader
def next(self):
    _ = self.data.close[0]

This isolates the fixed overhead of crossing into the strategy once per bar, with nothing else in the way — the fastest either engine could possibly go for the given bar count, upper-bounded only by interpreter/FFI overhead.

realistic — moving-average crossover with real order flow

A 5-period fast moving average vs. a 20-period slow moving average on close price, submitting real market orders through each engine's own order management and fill-matching pipeline:

# reamer_py
def on_bar(self, data):
    tv = data["TICKER"]
    fast, slow = tv.close[-5:].mean(), tv.close.mean()
    if fast > slow and tv.position.side <= 0:
        return buy_market(1.0, ticker="TICKER")
    if fast < slow and tv.position.side >= 0:
        return sell_market(1.0, ticker="TICKER")

# Backtrader
def next(self):
    pos = self.position.size
    if self.fast_ma[0] > self.slow_ma[0] and pos <= 0:
        self.buy(size=1.0 - pos)
    elif self.fast_ma[0] < self.slow_ma[0] and pos >= 0:
        self.sell(size=1.0 + pos)

A position reversal is submitted as a single order sized to flip the net position directly on both sides (not close-then-reopen as two separate orders), so trade counts between the two engines land within a few percent of each other rather than differing by a fixed 2x purely from a counting-convention mismatch. This workload exercises indicator computation, order submission, and fill matching — the actual cost profile of a strategy that trades, not just reads data.

Important: "realistic" describes the strategy's logic — a real signal, real order flow, real fills — not realistic transaction costs. Both engines ran with zero commission, zero slippage, and zero spread. These are pure throughput numbers, not cost-adjusted P&L numbers.

Data & methodology

A synthetic 5-minute-bar OHLCV dataset built from a real historical crypto price series, extended to arbitrary length by tiling it end-to-end with continuous re-stamped timestamps — real price action, not fabricated from scratch. For the 50-ticker portfolio test, each ticker is an independently price-scaled, time-offset slice of that same base series, all sharing one timeline so the two engines' union alignment treats them as fully overlapping — approximating 50 different instruments traded over the same 1-year window.

  • Each engine run happens in its own isolated process, so peak-memory measurements are clean per engine, not inflated by whatever the other engine already had loaded.
  • Timing wraps only the actual backtest call, excluding one-time setup (data loading into the engine, strategy/indicator object construction).
  • Peak memory measured via each process's own resident-set-size high-water mark.

Single ticker, 500,000 bars

~4.75 years of continuous 5-min bars.

Strategyreamer_pyBacktraderreamer_py advantage
minimal0.81s (617,103 bars/s)71.01s (7,041 bars/s)87.6x
realistic5.69s (87,832 bars/s), 32,066 trades84.89s (5,890 bars/s), 33,505 trades14.9x

Single ticker, 5,000,000 bars

~47.5 years of continuous 5-min bars.

Strategyreamer_pyBacktraderreamer_py advantage
minimal7.62s (655,942 bars/s)744.31s (6,718 bars/s)97.6x
realistic81.14s (61,623 bars/s), 319,529 trades1014.32s (4,929 bars/s), 333,794 trades12.5x

50-ticker portfolio

105,192 bars/ticker (1 year of continuous 5-min bars each, ~5.26M total ticker-bar-instances).

Strategyreamer_pyBacktraderreamer_py advantage
minimal2.94s (35,818 steps/s)812.12s (130 steps/s)276.5x
realistic42.68s (2,464 steps/s), 266,318 trades1107.83s (95 steps/s), 351,982 trades26.0x

"steps/s" = aligned timesteps/second, not ticker-bars/second — reamer_py's on_bar fires once per aligned step across all 50 tickers simultaneously, not once per ticker-bar, so this isn't directly comparable to the single-ticker bars/s figures above.

Reading these numbers honestly

  • The gap is consistently far larger on minimal (87-277x) than realistic (12-26x) — expected, since minimal isolates pure per-bar interpreter overhead (where a compiled C++ core vs. pure-Python architecture is starkest), while realistic adds order-matching and bookkeeping work both engines have to do regardless of implementation language.
  • The portfolio test shows the largest gap of all (276.5x on minimal) — Backtrader's per-bar synchronization cost across 50 simultaneous data feeds appears to scale worse than linearly with ticker count, not just proportionally.
  • These are throughput numbers on zero-cost execution config, run on an ultrabook laptop CPU — not a benchmark of P&L realism, and not a comparison against a compiled-core competitor. Backtrader was chosen specifically because it's the long-standing pure-Python standard in this space — what a large fraction of retail/indie quants already know or learned on — not as a broader claim about every backtesting tool on the market.
Reproducibility: every figure above is generated by the same benchmark harness reamer_py ships internally — no hand-tuned scenarios, no cherry-picked runs.