QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

Conformance replay + native strategy benchmark

One research contract. Two tests of six engines.

Benchmark A asks six accounting engines to replay one invariant-checked portfolio path. Benchmark B gives them only adjusted data and the same research contract, then requires each engine to compute indicators, decisions, execution and NAV independently. Agreement and divergence now answer two different questions.

1 · contractShared research input

One OHLCV hash, calendar, warmup, timing and initial capital.

2 · benchmark AConformance replay

One canonical target matrix tests accounting agreement.

3 · benchmark BNative strategies

Each engine computes signals, state, weights and rebalances.

4 · evidenceAttributed differences

Path error, first divergence, indicator semantics and rounding.

5 benchmark strategies 6 measured engines 30 conformance + 30 native runs 2016–2024 evaluation

What we tested

We compare QJ with free, open-source and commercial backtesting tools used for Python research, event-driven simulation, cloud trading workflows and no-code portfolio testing.

# Tool Access Model Strongest at Research trade-off
1 QJ BacktesterOpen-source / PythonPython-first portfolio research engineSignal, factor, allocation and order-aware validation with generated reportsPortfolio evidence, diagnostics and inspectable research packets
2 vectorbtOpen-sourceVectorized Python research libraryFast array research, parameter sweeps and signal experimentsExcellent speed; execution semantics and reports are workflow-dependent
3 pmorissette/btOpen-sourcePortfolio backtesting libraryClean allocation experiments and rebalancing workflowsCompact for simple portfolios; less focused on execution and generated evidence
4 BacktraderOpen-sourcePython event-driven engineOrder-based strategies, broker-style simulation, stops and limitsStrong event model; portfolio research code grows with ranking and sizing logic
5 Zipline ReloadedOpen-sourcePython event-driven research engineScheduled algorithms, daily equities and pipeline-style researchClear research model; setup, bundles and data history calls add overhead
6 QuantConnect / LEANCloud + open-source engineCloud platform plus C#/Python engineData, broker integrations, optimization and live deployment workflowPowerful when deployment matters; heavier for small local portfolio research tests
7 NautilusTraderOpen-source core; separate Pro/Cloud servicesEvent-driven trading engineResearch-to-live parity, multi-asset execution, venue adapters and order lifecycle realismStrong execution stack; heavier than needed for simple daily portfolio evidence
8 QuantRocketCommercial platformSelf-hosted data, research and trading platformMarket data, Jupyter workflow, Zipline/Moonshot/Pipeline support and broker integrationPlatform infrastructure first; less focused on lightweight standalone research packets
9 Portfolio123Commercial / no-codeStock ranking and portfolio simulation platformEquity screens, ranking systems, buy/sell rules and point-in-time portfolio simulationsAccessible and data-rich; less inspectable than Python source-controlled research
10 TradingViewCommercial / chart-nativeCharting platform with strategy testingVisual chart research, Pine Script strategies, alerts and trader-facing workflowsExcellent for chart-native tests; less suited to auditable multi-engine portfolio validation
11 ComposerCommercial / no-codeNo-code strategy builder and automated brokerage workflowAI-assisted strategy creation, backtests, conditional logic and automated rebalancingVery accessible; less open and programmable than local Python research
12 QlibOpen-sourceAI-oriented quantitative research platformML model research, alpha modeling, data pipelines and experiment workflowsModel-research first; portfolio accounting and report packets are not the main surface
13 FinRLOpen-sourceDeep reinforcement learning trading frameworkRL environments, agents, tutorials and portfolio allocation experimentsRL-focused research stack; not a general-purpose portfolio evidence engine
14 PyBrokerOpen-sourcePython algorithmic trading and ML backtesting libraryML-assisted strategy testing, ranking, walk-forward style experiments and Python workflowsGood for strategy research; less focused on generated institutional review packets

Which backtester fits your workflow?

The same engine can be excellent in one workflow and awkward in another. This matrix summarizes fit by research task, using capability labels instead of binary checkmarks.

Workflow QJ BacktestervectorbtbtBacktraderZiplineLEAN
Fast signal research Indicators, signal sweeps and daily portfolio experiments. Strong Native Strong Custom Custom Heavy
Portfolio weights / allocation Target weights, rebalance schedules and allocation diagnostics. Native Strong Native Custom Custom Native
Order lifecycle realism Stops, limits, fills, cash and positions; full broker-style lifecycle state varies by engine. Native Strong Limited Native Strong Native
Walk-forward validation OOS folds, training windows and overfit evidence. Native Built-in splits Custom Custom Custom Custom
Crisis / regime diagnostics Stress periods, market regimes and behavior under drawdown. Native Custom Limited Custom Custom Custom
Generated research reports Shareable reports with metrics, plots and engine-specific metadata. Native Custom Limited Custom Custom Native
Local Python workflow Small local loop: edit strategy, run, inspect artifacts. Native Native Native Native Heavy Heavy
Live trading / broker path Moving from research into live venue or brokerage integration. Limited Limited Limited Strong Limited Native

Native first-class workflow. Strong good fit with normal setup. Built-in splits chronological split utilities; end-to-end fold execution remains workflow-dependent. Custom possible through user code. Heavy powerful but larger stack. Limited not the main surface.

Compare Strategy Results

We run two complementary benchmarks. They answer different questions, so identical results mean something different in each one. The measured engines are QJ Backtester, VectorBT, pmorissette/bt, Zipline, Backtrader and QuantConnect LEAN.

BenchmarkWhat is sharedWhat each engine doesWhat identical results mean
A · Conformance replay One canonical decision and execution matrix, validated before every run. Replays the same targets through its own accounting path. Accounting agrees; identity is the pass criterion.
B · Native strategies Only adjusted OHLCV data and the explicit research contract. Computes indicators, signals, state, weights and rebalances itself. Identity is not required; differences reveal real engine semantics.

Most public comparisons rewrite a strategy in every API and then mistake semantic drift for an engine difference. Benchmark A removes that drift entirely. Benchmark B measures it deliberately and reports the first divergent decision date with an attribution.

Benchmark A — conformance replay

One invariant-checked target matrix is mapped into six accounting engines. The goal is exact path agreement, not independent signal generation. If the outputs differ beyond tolerance, the conformance test fails.

Benchmark strategies

We test different strategy types because each one exposes a different engine assumption: signal timing, rebalance calendars, ranking, volatility sizing or cash switching.

# Strategy What it tests
1 SMA crossover without costs Indicator alignment, daily signal application, close-to-close assumptions
2 RSI mean reversion Indicator definition, warmup behavior, threshold timing, path dependency
3 Monthly rebalance portfolio Calendar semantics, weight drift, rebalance schedule, rounding
4 Top-N momentum rotation with vol target Ranking, lookback windows, volatility estimate, partial allocation
5 Dual momentum with cash switching Absolute and relative momentum, asset-vs-cash switching, simultaneous allocation

Canonical equity paths with engine checkpoints

Every engine reproduces the same full NAV path. A neutral line shows that path; colored, staggered markers sample actual observations from each engine so all six remain visible instead of the last rendered line hiding the other five.

Five canonical NAV paths with sampled checkpoints from six independent backtesting engines
The marker dates are staggered only for legibility; values are not offset or shifted. The complete daily path—not merely the displayed checkpoints—is validated for QJ Backtester, VectorBT, pmorissette/bt, Zipline, Backtrader and LEAN.

Conformance replay final NAV

Validated July 11, 2026: $100,000 initial capital, 2015 warmup, 2016-01-04 to 2024-12-31 evaluation, adjusted OHLCV, close(t) decisions, close(t+1) execution, 0.1% cash buffer and zero benchmark costs. Every engine receives the same validated portfolio decisions; QJ is highlighted for easier scanning.

Strategy QJ Backtester VectorBT pmorissette/bt Zipline Backtrader QC LEAN
1. SMA 50/200 $1,596,086 $1,596,086$1,596,086$1,596,086$1,596,086$1,596,086
2. RSI Reversion $307,971 $307,971$307,971$307,971$307,971$307,971
3. Monthly EW Rebal $1,673,524 $1,673,524$1,673,524$1,673,524$1,673,524$1,673,524
4. Momentum+Vol $657,808 $657,808$657,808$657,808$657,808$657,808
5. Dual Momentum $3,706,895 $3,706,895$3,706,895$3,706,895$3,706,895$3,706,895

What conformance replay establishes

  • Conformance result: All 30 engine-strategy pairs pass the strict protocol: matching protocol hash, calendar, NAV path and final result within tolerance.
  • Path agreement: Daily-return correlation is 1.000 across every pair. The largest observed relative NAV difference is 2.05 × 10⁻¹⁰, caused by floating-point and linearly scaled whole-share adapters.
  • What was normalized: Adjusted OHLCV, warmup, decision weights, execution weights, close(t+1) lag, cash buffer, position cap, zero costs, initial capital and metric definitions are shared.
  • What QJ proves here: QJ native weight accounting reproduces the same portfolio path as five independent engines while keeping explicit weights, cash, holdings and report-ready state.
  • Where QJ is stronger: The benchmark only tests conformance. QJ adds one Python research surface for weight and order modes, generated evidence packets, walk-forward tooling and multi-asset examples; those product capabilities are not credited in the NAV table.
  • What this does not prove: This compact five-stock fixture is an engine-semantics test, not an alpha contest, capacity study or claim that these strategies are investment-ready.

Compute time is reported in the native benchmark below, where every engine computes the strategy itself. Replaying a prevalidated decision matrix measures adapter plumbing rather than engine speed, so the conformance section makes no timing claims.

Benchmark B · native strategy implementations

Each engine computes the strategy itself

Only adjusted OHLCV data and the research contract are shared. Every engine computes its own indicators, signals, state, weights, rebalance decisions and portfolio path. Identical output is not required here: differences expose the engines’ real semantics.

Validated July 21, 2026. Three consecutive complete runs changed 0 of 150 metric cells and the maximum final-NAV delta between runs was $0.00. The shared contract uses AAPL, MSFT, NVDA, GOOGL and AMZN; adjusted OHLCV; a 2015 warmup; 2016-01-04 through 2024-12-31 evaluation (2,264 sessions); $100,000 initial capital; close(t) decisions; close(t+1) execution; a 0.1% cash buffer; and zero costs.

Five native NAV paths with sampled checkpoints from six independently implemented backtesting engines
Each engine's checkpoints sample its own independently computed NAV path; marker dates are staggered only for legibility and values are never shifted. On RSI mean reversion the VectorBT markers visibly trace a different path — its documented rolling-RSI semantics — while the whole-share engines sit within rounding distance of the fractional line.

Final NAV

Strategy QJVectorBTpm/btZiplineBacktraderLEAN
SMA 50/200 $1,596,086$1,596,086$1,596,086$1,578,890$1,595,709$1,577,961
RSI Reversion $307,971$592,949 †$307,971$308,352$307,658$308,255
Monthly EW $1,673,524$1,673,524$1,673,524$1,668,082$1,673,349$1,666,680
Momentum + Vol $657,808$657,808$657,808$653,919$657,422$653,709
Dual Momentum $3,706,895$3,706,895$3,706,895$3,706,200$3,706,560$3,705,550

CAGR

Strategy QJVectorBTpm/btZiplineBacktraderLEAN
SMA 50/200 36.13%36.13%36.13%35.97%36.13%35.96%
RSI Reversion 13.34%21.92% †13.34%13.36%13.33%13.36%
Monthly EW 36.85%36.85%36.85%36.81%36.85%36.79%
Momentum + Vol 23.34%23.34%23.34%23.26%23.33%23.25%
Dual Momentum 49.53%49.53%49.53%49.52%49.53%49.52%

Sharpe

Strategy QJVectorBTpm/btZiplineBacktraderLEAN
SMA 50/200 1.41471.41471.41471.40971.41461.4100
RSI Reversion 0.62560.9190 †0.62560.62640.62520.6265
Monthly EW 1.27451.27451.27451.27331.27461.2734
Momentum + Vol 1.29181.29181.29181.28951.29131.2900
Dual Momentum 1.35921.35921.35921.35911.35911.3591

Max drawdown

Strategy QJVectorBTpm/btZiplineBacktraderLEAN
SMA 50/200 −28.41%−28.41%−28.41%−28.52%−28.42%−28.50%
RSI Reversion −47.41%−30.44% †−47.41%−47.32%−47.44%−47.29%
Monthly EW −41.65%−41.65%−41.65%−41.79%−41.64%−41.76%
Momentum + Vol −23.05%−23.05%−23.05%−23.30%−23.06%−23.27%
Dual Momentum −38.86%−38.86%−38.86%−38.62%−38.86%−38.62%

† VectorBT RSI semantics: by default, its native implementation uses a rolling mean of gains and losses; the other five engines use Wilder smoothing. We deliberately did not normalize this away—it is the kind of semantic difference this benchmark is designed to expose.

Decision divergence audit

Target weights from every native implementation are compared with QJ on all comparable dates. The audit records the first divergence and attributes its cause instead of treating every different NAV as an unexplained engine gap.

StrategyEngineDiffering decision rowsFirst divergenceAttribution
All fiveZipline / Backtrader / LEAN / pm-bt0——
SMA, Monthly, Momentum, DualVectorBT0——
RSI ReversionVectorBT1,646 / 2,2642016-01-07Rolling RSI vs Wilder RSI
Fractional accounting

Why three engines agree to the last digit

QJ, VectorBT and pm/bt use fractional target-weight accounting with zero costs. Under those conditions the NAV path is a pure function of weights and close prices:

NAV(t+1) = NAV(t) × (1 + Σ w·r)

Independent engines evaluating the same function on the same decisions must agree. Across the complete nine-year paths, the largest pairwise difference is $0.000000007 (about 3×10⁻¹⁵ relative): ordinary double-precision rounding. Bit-identical copied output—not close independent arithmetic—would be the red flag.

Whole-share execution

Why the remaining paths differ

Zipline, Backtrader and LEAN place integer-share orders through their own execution paths. Rounding down leaves a small, price-dependent cash residue on every rebalance.

  1. Final NAV ends 0.02–1.1% below the fractional result as uninvested cash compounds over nine years.
  2. Max drawdown can be shallower because the same residue drags during rallies and cushions declines.

For Dual Momentum, every engine finds the same 2018-10-01 peak and 2018-12-24 trough. The whole-share book was about 0.08% behind at the peak and 0.31% ahead at the trough—an attributable path effect, not a different signal.

Native compute time (fair core seconds; lower is better)

Strategy computation and engine accounting only; data fetch, report rendering, exports and process startup are excluded. QJ reports its calculation core. LEAN reports its internal algorithm timer because container parse time is unstable between runs. Values are medians of three consecutive complete runs. Timing varies between runs; the metrics above do not.

Strategy QJVectorBTpm/btZiplineBacktraderLEAN
SMA 50/200 0.73 s0.16 s1.35 s6.59 s1.23 s1.94 s
RSI Reversion 2.21 s †1.99 s †3.28 s1.88 s0.88 s1.20 s
Monthly EW 0.16 s0.11 s0.58 s1.06 s0.74 s0.94 s
Momentum + Vol 0.36 s0.18 s0.53 s1.45 s0.72 s0.81 s
Dual Momentum 0.23 s0.16 s0.44 s1.20 s0.71 s0.78 s

† RSI timing: QJ and VectorBT both spend most of this time in the identical Python state-machine loop defined by the strategy. Path-dependent entry/exit state cannot be fully vectorized; the underlying accounting pass runs in milliseconds.

Native benchmark compute time by strategy and engine
Figure 2. Native benchmark core time by engine; strategy logic plus engine accounting, lower bars are faster.
Vectorized accounting with complete state

QJ’s weight-ledger accounting is fully vectorized while retaining explicit cash, holdings, margin history and an accounting-identity assertion on every bar. The vectorized path was verified bit-identical: maximum equity difference $0.000000000000 across all 11,320 daily observations, with the complete 967-test suite green.

Speed on a five-ticker fixture is a workflow signal, not a universal leaderboard. Data access, caching, universe size and execution model dominate real research time.

What the two benchmarks establish

  • Conformance (A): all engines reproduce one canonical path within 10⁻¹⁰; accounting agrees.
  • Independence (B): with only data and contract shared, five engines converge on identical decisions everywhere except VectorBT’s documented RSI semantics. NAV differences are attributed to whole shares or the indicator definition, with the first divergent date reported.
  • Determinism: three consecutive complete 30-backtest runs produced no changed metric cells and a maximum final-NAV delta of $0.00.
  • What this does not prove: this is an engine-semantics test on a compact fixture—not an alpha contest, capacity study or claim that these strategies are investment-ready.

Full disclosure

  • The three fractional adapters share the research contract—data, constants and rebalance-calendar helpers—but each computes signals in its own code and runs its own accounting: the QJ ledger, vbt.Portfolio.from_orders, or bt WeighTarget/Rebalance. Their agreement is mathematically forced once independently computed decisions agree.
  • LEAN runs the official Docker image. Its five native strategies share one algorithm base class with per-strategy logic selected at runtime.
  • Every native result JSON records the data SHA-256, contract version and engine mode. A single runner regenerates the artifacts and gates publication on 30/30 results, one data hash, one calendar and attributed divergences only.

Conformance adapter methodology: strategy logic runs once and produces the invariant-checked decision and execution artifacts. The excerpts below come from the strict adapters actually used; they show only how each engine consumes those same artifacts. No adapter recalculates signals, changes the 25% cap, renormalizes cash or applies a different lag.

One decision matrix. Six adapter surfaces.

QJ consumes canonical decision weights through its native one-bar lag and portfolio accounting path. VectorBT, pmorissette/bt and Backtrader receive the resulting execution targets; Zipline and LEAN receive equivalent scaled quantities where their accounting models require them. This is adapter code, not six different strategy definitions.

QJ Backtester

Decision weights through the native lag and rebalance path

class CanonicalWeightsBacktester(LocalBacktester):
    def __init__(self, *, canonical_weights, **kwargs):
        self._canonical_weights = canonical_weights.copy()
        super().__init__(**kwargs)

    def _compute_weights(self):
        return self._canonical_weights.reindex(
            self.data.close.index
        ).fillna(0.0)

bundle = load_bundle(strategy_key)
strategy = CanonicalWeightsBacktester(
    canonical_weights=bundle["decision_weights"],
    rebalance_policy=RebalancePolicy(
        frequency=None,
        calendar_dates=execution_dates,
    ),
)

VectorBT

Canonical targets through target-percent orders

size = pd.DataFrame(
    np.nan, index=close.index, columns=close.columns
)
size.loc[execution_flags] = execution_weights.loc[execution_flags]

pf = vbt.Portfolio.from_orders(
    close,
    size=size,
    size_type="targetpercent",
    init_cash=100_000,
    fees=0.0,
    slippage=0.0,
    group_by=True,
    cash_sharing=True,
    call_seq="auto",
)

pmorissette/bt

The same targets on the same execution dates

strategy = bt.Strategy("canonical", [
    bt.algos.RunOnDate(*execution_dates),
    bt.algos.WeighTarget(execution_weights),
    bt.algos.Rebalance(),
])

backtest = bt.Backtest(
    strategy,
    close,
    initial_capital=100_000,
    integer_positions=False,
)

Zipline

Canonical quantities mapped to next-close fills

SUBMISSION_SHARES[decision_date] = (
    execution_shares.loc[execution_date]
)

def handle_data(context, data):
    if session_date(data.current_dt) not in SUBMISSION_DATES:
        return
    for ticker, asset in context.assets.items():
        desired = scaled_quantity(ticker)
        delta = desired - current_quantity(asset)
        if delta:
            order(asset, delta)

perf = run_algorithm(
    start=pd.Timestamp("2015-12-31"),
    end=pd.Timestamp("2024-12-31"),
    handle_data=handle_data,
    capital_base=100_000 * MICRO_SHARE_SCALE,
    bundle="qj_compare",
)

Backtrader

Target-percent orders with close execution

class CanonicalTargetStrategy(bt.Strategy):
    params = dict(targets=None, execution_dates=None)

    def next(self):
        date = self.datas[0].datetime.date(0)
        if date not in self.p.execution_dates:
            return
        row = self.p.targets.loc[pd.Timestamp(date)]
        nav = float(self.broker.getvalue())
        orders = []
        for data in self.datas:
            target = float(row[data._name])
            current = self.getposition(data).size * data.close[0]
            orders.append((target * nav - current, data, target))
        orders.sort(key=lambda item: item[0])
        for _delta, data, target in orders:
            self.order_target_percent(data=data, target=target)

cerebro.broker.set_coc(True)

QuantConnect LEAN

Scaled canonical quantities at the closing auction

class CanonicalWeightsAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2015, 12, 31)
        self.set_end_date(2024, 12, 31)
        self.set_cash(100_000 * MICRO_SHARE_SCALE)
        self.execution_quantities = load_canonical_quantities()
        self.schedule.on(
            self.date_rules.every_day(self.calendar_symbol),
            self.time_rules.before_market_close(self.calendar_symbol, 20),
            self.submit_close_targets,
        )

    def submit_close_targets(self):
        desired = self.execution_quantities.get(self.time.date())
        for delta, symbol in sell_before_buy_deltas(desired):
            self.market_on_close_order(symbol, delta)

Detailed engine comparisons

Use this chapter for the practical trade-offs between QJ and the individual engines below.

vs vectorbt

QuantJourney vs vectorbt

vectorbt is excellent for fast vectorized experimentation and parameter sweeps across NumPy arrays. It is the right choice when iteration speed is the primary objective. QuantJourney is designed for the next step: moving from fast signal research to portfolio evidence.

QuestionvectorbtQuantJourney
Can I test many signals quickly?YesYes
Can I work with target portfolio weights?YesNative
Separate signals, weights, orders, fills, cash, positions and NAV?Available; explicit state separation is workflow-dependentNative design goal
Walk-forward validation built in?Built-in rolling/range splits; fold execution remains workflow-definedNative
Crisis diagnostics and scenario testing?CustomNative
PDF research packet from one run?CustomNative
Use vectorbt when

Speed and sweeps are the goal

High-speed parameter experimentation across NumPy/pandas arrays is where vectorbt is unmatched.

Use QuantJourney when

The backtest must become portfolio evidence

When results need to be explainable, auditable and shareable — with accounting behind them.

vs Backtrader

QuantJourney vs Backtrader

Backtrader is a mature Python framework for event-driven strategy simulation. It is useful when the strategy is naturally order-based with explicit entry/exit logic. QuantJourney is built around weight-based systematic research where accounting assumptions, diagnostics and research output matter as much as entry logic.

QuestionBacktraderQuantJourney
Event-driven strategy simulation?YesYes / growing
Order simulation?YesYes
Portfolio-weight research as first-class workflow?CustomNative
Walk-forward, crisis diagnostics and research packets built in?CustomNative
Modern data integration?CustomNative
Use Backtrader when

Classic event-driven simulation

Entry/exit logic, stops, limits and broker simulation are the core of the strategy.

Use QuantJourney when

Portfolio-weight research and evidence

Weight-based systematic strategies that need accounting discipline and reportable output.

vs LEAN

QuantJourney vs LEAN

LEAN is a powerful open-source engine for research, optimization and live trading deployment across venues. It is strongest when the workflow needs to move from algorithm design into live brokerage execution. QuantJourney is strongest earlier in the process: portfolio research, local accounting, validation evidence and a shareable research packet.

QuestionLEANQuantJourney
Backtesting?YesYes
Live trading deployment?NativeOptional / not primary
Multi-asset engine?NativeYes / depends on data
Portfolio research reports and diagnostics?Native report generation; extensibleNative
Local private research workflow?Yes / CLINative
Parameter optimization?Native cloud/CLI optimizer; Optuna via custom wiringNative (Optuna TPE with walk-forward)
Use LEAN when

Live trading deployment is the goal

Moving from algorithm to live deployment across brokers and venues is where LEAN excels.

Use QuantJourney when

Research evidence and accounting clarity

Portfolio-level validation, diagnostics and research packets before a strategy becomes a production workflow.

Independent validation services

Matching equity curves prove conformance. They do not prove the research.

QuantJourney Validation Services audits the assumptions behind the curve—from data lineage, look-ahead and survivorship bias through signal timing, rebalancing, execution, costs, parameter selection and out-of-sample stability. You receive a reproducible evidence pack with the verdict, limitations and next actions, while sensitive strategy code can remain inside your infrastructure.