# 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.

Source: https://learn.tradelabsai.com/research/vectorized-backtesting/  
Track: Research and Backtesting · Level: Advanced · Updated: 2026-10-03  
Publisher: TradeLabs AI (https://tradelabsai.com). Education, not financial advice.  
Cite as: TradeLabs Learn, "Event-Driven vs Vectorized Backtesting", https://learn.tradelabsai.com/research/vectorized-backtesting/

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

1. **Load price data** into an array or DataFrame.
2. **Compute indicators** for all dates (moving averages, z scores).
3. **Compute signals** as arrays of positions (+1, 0, minus 1).
4. **Shift positions** by one bar so trades use only past information.
5. **Multiply positions by returns** to get strategy returns.
6. **Subtract costs** when positions change.
7. **Compute performance metrics.**

## A pandas example

```python
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](https://learn.tradelabsai.com/research/look-ahead-bias/).

## Advantages

| Advantage | Why it matters |
|---|---|
| Speed | Test many parameters and markets quickly. See [Parameter Optimization](https://learn.tradelabsai.com/research/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](https://learn.tradelabsai.com/research/fill-models/) |
| 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](https://learn.tradelabsai.com/research/portfolio-backtesting/) |
| Intrabar events | Cannot tell whether stop or target was hit first within a bar |

**Example: A stop loss problem**
A vectorised test adds a rule: exit if the price falls 3% below entry. Implementing this requires knowing the entry price of each trade, which depends on the path of previous signals, so a pure array operation no longer works. A common shortcut, checking whether the daily low is 3% below the previous close, ignores when the position was entered. The result can be badly wrong. For strategies with path dependent rules, an event driven loop is safer.

## 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](https://learn.tradelabsai.com/programming/backtesting-libraries-compared/).

## Good practice

1. **Shift signals** before multiplying by returns.
2. **Charge costs on position changes.**
3. **Check results with a simple event driven loop** for a sample period.
4. **Keep code and data versioned.** See [Backtest Reproducibility](https://learn.tradelabsai.com/research/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](https://learn.tradelabsai.com/research/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](https://learn.tradelabsai.com/research/fill-models/).

## Continue learning

- Next lesson: [Fill Models, Partial Fills and Order Queues](https://learn.tradelabsai.com/research/fill-models/)
- Previous lesson: [Costs and Slippage in Backtests](https://learn.tradelabsai.com/research/costs-and-slippage-in-backtests/)
- Related: [Costs and Slippage in Backtests](https://learn.tradelabsai.com/research/costs-and-slippage-in-backtests/): Ignoring costs is the fastest way to fool yourself in a backtest. Learn the costs to include, how to estimate slippage and market impact, and conservative rules.
- Related: [Backtesting Methodology](https://learn.tradelabsai.com/research/backtesting-methodology/): A backtest simulates a strategy on historical data. Learn the steps, the key performance metrics, common biases and a checklist for backtests you can trust.
- Related: [NumPy and Pandas for Traders](https://learn.tradelabsai.com/programming/numpy-and-pandas-for-traders/): Learn the pandas and NumPy operations traders use most: loading price data, returns, rolling windows, resampling bars, joining assets and avoiding common traps.
- Related: [Look-Ahead Bias](https://learn.tradelabsai.com/research/look-ahead-bias/): Look ahead bias happens when a backtest uses information that was not available at the time. Learn common sources, real examples and how to prevent it in code.
- Related: [Backtesting Libraries Compared](https://learn.tradelabsai.com/programming/backtesting-libraries-compared/): Compare popular Python backtesting tools: vectorbt, Backtrader, backtesting.py, Zipline Reloaded, NautilusTrader and LEAN. Learn their styles, strengths and limits.
- Related: [Python for Trading](https://learn.tradelabsai.com/programming/python-for-trading/): Why Python is the most popular language for trading research and bots, which libraries matter, how to set up a project and a first script that tests a simple rule.
