Building an Equities Backtesting Engine
Quant Insider
May 22, 2025
Quant Insider Building an Equities Backtesting Engine May 22, 2025 1 / 15
1. Objectives & Scope
Asset Universe: Equities, ETFs, Indices, multi-asset portfolios
Data Frequency: Daily, Intraday (minute, tick)
Order Types: Market, Limit, Stop-Loss, Stop-Limit
Costs & Slippage: Commissions (fixed, per-share), spread models, market impact
Risk Constraints: Position limits, maximum drawdown, VaR, margin requirements
Performance Metrics: Returns, volatility, Sharpe, Sortino, drawdown, turnover, hit-rate
Quant Insider Building an Equities Backtesting Engine May 22, 2025 2 / 15
2. Modular Architecture
Event Queue: FIFO stream of all events (Bar, Signal, Order, Fill)
DataHandler: Data ingestion, adjustment for corporate actions, streaming bars
Strategy: Indicator computation, signal generation, parameterization
Portfolio: Position sizing, risk management, cash accounting, P&L updates
ExecutionHandler: Slippage model, fill simulation, commission calculation
PerformanceTracker: Equity curve, statistical metrics, visualization
Quant Insider Building an Equities Backtesting Engine May 22, 2025 3 / 15
3. Event Queue
from collections import deque
# Initialize central event queue
events = deque ()
# Components enqueue and dequeue events :
# BarEvent -> SignalEvent -> OrderEvent -> FillEvent
Quant Insider Building an Equities Backtesting Engine May 22, 2025 4 / 15
4. DataHandler I
Load historical quotes (CSV, Parquet, Database API)
Adjust for splits and dividends (factor-based adjustments)
Handle missing data: interpolate or forward-fill gaps
Stream next bar per symbol with timestamp
class DataHandler :
def __init__ ( self , symbols , start , end ) :
self . data = self . _load_adjust ( symbols , start , end )
self . idx = { s : 0 for s in symbols }
def get_next_bar ( self ) :
bars = {}
for s , df in self . data . items () :
i = self . idx [ s ]
if i < len ( df ) :
bars [ s ] = df . iloc [ i ]
Quant Insider Building an Equities Backtesting Engine May 22, 2025 5 / 15
4. DataHandler II
self . idx [ s ] += 1
if not bars :
return None
return BarEvent ( bars )
Quant Insider Building an Equities Backtesting Engine May 22, 2025 6 / 15
5. Event Types
BarEvent: Market data snapshot per timestamp
SignalEvent: Strategy entry/exit signals (LONG, SHORT, EXIT)
OrderEvent: Order details (symbol, size, type)
FillEvent: Execution confirmation (price, quantity, commission)
CustomEvents: Risk breaches, scheduled rebalances
Quant Insider Building an Equities Backtesting Engine May 22, 2025 7 / 15
6. Strategy Module I
Initialize rolling windows for indicators (SMA, EMA, RSI, ATR)
Parameter sweeps and optimization hooks
On each BarEvent:
Update price history
Compute indicators
Check rules and thresholds
Emit one or more SignalEvents
Quant Insider Building an Equities Backtesting Engine May 22, 2025 8 / 15
6. Strategy Module II
class S m a C r o s s S t r a t e g y :
def on_bar ( self , event ) :
bars = event . bars
for sym , bar in bars . items () :
self . history [ sym ]. append ( bar . close )
if len ( self . history [ sym ]) >= self . long_w :
short_sma = mean ( self . history [ sym ][ - self . short_w :])
long_sma = mean ( self . history [ sym ][ - self . long_w :])
if short_sma > long_sma and not self . in_position ( sym ) :
return SignalEvent ( sym , bar . time , ’ LONG ’)
elif short_sma < long_sma and self . in_position ( sym ) :
return SignalEvent ( sym , bar . time , ’ EXIT ’)
Quant Insider Building an Equities Backtesting Engine May 22, 2025 9 / 15
7. Portfolio Module
Track cash, positions, and holdings per symbol
Risk management: max position size, sector limits, VaR checks
Signal -¿ Order conversion:
Fixed-fraction sizing, dollar-risk sizing
Check available buying power and margin
On FillEvent: update cash, positions, realized/unrealized P&L
class Portfolio :
def on_signal ( self , signal ) :
qty = self . c alculate _size ( signal )
return OrderEvent ( signal . symbol , ’ MKT ’ , qty )
def on_fill ( self , fill ) :
cost = fill . fill_price * fill . quantity + fill . commission
self . cash -= cost
self . positions [ fill . symbol ] += fill . quantity
self . _ u p d a t e _ h o l d i n g s ()
Quant Insider Building an Equities Backtesting Engine May 22, 2025 10 / 15
8. ExecutionHandler
Simulate market impact and slippage:
Spread-based, volume-based, or parametric models
Commission structures: tiered, per-share, or flat fee
Partial fills, latency emulation, and fill probability
class E x e c u t i o n H a n d l e r :
def execute_order ( self , order ) :
bar = self . data . g et_late st_bar ( order . symbol )
slippage = bar . close * np . random . normal (0 , 0.0003)
price = bar . close + slippage
commission = self . _ c a l c _ c o m m i s si o n ( order . quantity )
return FillEvent ( order . symbol , order . quantity , price , commission )
Quant Insider Building an Equities Backtesting Engine May 22, 2025 11 / 15
9. Performance Tracking
Record timestamped equity and cash balances
Compute periodic and cumulative returns
Key metrics: Sharpe, Sortino, Calmar, max drawdown, win rate
Visual reports: equity curve, drawdown plot, rolling metrics heatmap
Quant Insider Building an Equities Backtesting Engine May 22, 2025 12 / 15
10. Main Backtest Loop I
while True :
bar = dh . get_next_bar ()
if bar is None :
break
events . append ( bar )
while events :
event = events . popleft ()
if isinstance ( event , BarEvent ) :
signals = strategy . on_bar ( event )
if signals : events . extend ( signals )
perf . update ( event . bars )
elif isinstance ( event , SignalEvent ) :
order = portfolio . on_signal ( event )
if order : events . append ( order )
elif isinstance ( event , OrderEvent ) :
fill = exec_handler . execute_order ( order )
events . append ( fill )
elif isinstance ( event , FillEvent ) :
portfolio . on_fill ( event )
Quant Insider Building an Equities Backtesting Engine May 22, 2025 13 / 15
11. Testing Validation
Unit tests: data adjustments, indicator accuracy, P&L calculations
Integration tests: reproducible runs with fixed RNG seeds
Edge-case handling: zero volume, missing dates, corporate actions
Performance regression tests after code changes
Quant Insider Building an Equities Backtesting Engine May 22, 2025 14 / 15
12. Scaling Optimization
Vectorized backtesting for simple strategies (Pandas, NumPy)
Parallel parameter sweeps using multiprocessing or Dask
Use optimized storage: HDF5, Feather, or kdb+ for large tick data
Caching indicator results, precomputing heavy computations
Quant Insider Building an Equities Backtesting Engine May 22, 2025 15 / 15