Event-Driven vs Vectorized Backtesting
Vectorised backtests compute signals and returns for all dates at once using arrays. Learn how they work, a pandas example, their speed advantages and their traps.
A vectorised backtest calculates a strategy's signals, positions and returns for every date at once, using array operations instead of looping through time one bar at a time. In Python, libraries such as NumPy and pandas make this very fast: you can test thousands of variations in seconds. Vectorised backtests are ideal for research and quick screening, but their speed comes with traps, especially look ahead bias and oversimplified execution. Event driven backtests are slower but closer to live trading.
How it works#
- Load price data into an array or DataFrame.
- Compute indicators for all dates (moving averages, z scores).
- Compute signals as arrays of positions (+1, 0, minus 1).
- Shift positions by one bar so trades use only past information.
- Multiply positions by returns to get strategy returns.
- Subtract costs when positions change.
- Compute performance metrics.
A pandas example#
import pandas as pd
import numpy as np
df = pd.read_csv("prices.csv", index_col="date", parse_dates=True)
df["ret"] = df["close"].pct_change()
fast = df["close"].rolling(20).mean()
slow = df["close"].rolling(100).mean()
df["signal"] = np.where(fast > slow, 1, 0)
df["position"] = df["signal"].shift(1) # trade on the next bar
cost_per_trade = 0.0005 # 5 basis points per change
df["trades"] = df["position"].diff().abs()
df["strat_ret"] = df["position"] * df["ret"] - df["trades"] * cost_per_trade
equity = (1 + df["strat_ret"].fillna(0)).cumprod()
sharpe = df["strat_ret"].mean() / df["strat_ret"].std() * np.sqrt(252)
print(round(equity.iloc[-1], 2), round(sharpe, 2))
The shift(1) line is essential: without it, the backtest would use today's close to decide today's position. See Look-Ahead Bias.
Advantages#
| Advantage | Why it matters |
|---|---|
| Speed | Test many parameters and markets quickly. See Parameter Optimization |
| Simplicity | Short, readable code |
| Easy analysis | Results fit naturally into DataFrames for statistics and plots |
| Good for signal research | Quick check of whether an idea has any edge |
Limitations and traps#
| Trap | Explanation |
|---|---|
| Look ahead bias | All data is present at once, making future leaks easy |
| Simplified execution | Assumes fills at close or open prices without queue or slippage modelling. See Fill Models, Partial Fills and Order Queues |
| Path dependent logic | Stops, trailing stops and position sizing that depends on equity are awkward to vectorise |
| Portfolio constraints | Cash limits, margin and position caps are harder to model. See Portfolio and Multi-Asset Backtesting |
| Intrabar events | Cannot tell whether stop or target was hit first within a bar |
When to switch to event driven#
Move to an event driven backtest when the strategy has stops and targets, complex order types, equity based position sizing, portfolio constraints or needs the same code to run live. Libraries such as Backtrader, Zipline and NautilusTrader support event driven designs, while vectorbt offers fast vectorised testing with some path dependent features. See Backtesting Libraries Compared.
Good practice#
- Shift signals before multiplying by returns.
- Charge costs on position changes.
- Check results with a simple event driven loop for a sample period.
- Keep code and data versioned. See Backtest Reproducibility.
Testing many variations quickly#
Vectorised code makes it easy to loop over parameter grids or many assets in seconds. That speed is a double edged sword: it encourages testing thousands of variations, which raises the risk of finding chance results. Record how many variations you test and judge the best result with that count in mind. See P-Hacking and Multiple Testing.
Frequently asked questions#
What is a vectorised backtest?#
A backtest that calculates signals, positions and returns for all dates at once using array operations rather than looping through each bar.
Are vectorised backtests accurate?#
They are accurate for simple strategies if signals are lagged and costs are included, but they struggle with stops, complex orders and portfolio constraints.
Why is shift(1) important in pandas backtests?#
Because it ensures today's position is based on yesterday's signal, preventing the backtest from using information that was not yet available.
Next, learn how trades actually get filled in Fill Models, Partial Fills and Order Queues.
3 quick questions on this lesson. Get them all right to finish it.
Turn on JavaScript to take the quiz.
Mentioned in
- Costs and Slippage in BacktestsResearch and Backtesting
- Developing, Testing and Monitoring AlgorithmsAlgorithmic Trading