Professional Algorithmic Trading Systems in Python
High Performance Vectorised Backtesting
Beyond Loops: The Multi-Dimensional Strategy
Institutional-grade backtesting demands moving beyond event-driven loops, which are inherently serial and slow. The key to testing millions of strategy permutations in milliseconds is to represent the entire strategic universe as a single computational object: a multi-dimensional NumPy array.
In this paradigm, each axis of the array represents a core component of the simulation. A typical structure might be a 3D array where the axes correspond to time steps, financial instruments, and strategy parameters. A single vectorised operation—a NumPy universal function (ufunc)—can thus execute a complex calculation across all assets and all parameter variations for the entire time series simultaneously. This eliminates Python's loop overhead and leverages highly optimised, low-level C and Fortran code for computation.
Consider a simple dual moving average crossover strategy. Instead of looping through each combination of fast and slow window lengths, you can generate signals for all combinations in one pass. This approach treats the parameter space as just another dimension in a tensor, enabling exhaustive search and optimisation that would be computationally infeasible with traditional methods.
import numpy as np
import pandas as pd
import vectorbtpro as vbt
# Assume 'price' is a pandas Series of historical prices
# Create a multi-dimensional parameter space for two MA windows
fast_windows = np.arange(10, 30, 5)
slow_windows = np.arange(40, 60, 5)
# vbt.MA.run expands price into a 3D array: (time, fast_win, slow_win)
fast_ma, slow_ma = vbt.MA.run_combs(
price,
window=np.array([fast_windows, slow_windows]),
short_names=['fast', 'slow']
)
# Generate signals across all 16 parameter combinations instantly
entries = fast_ma.ma_crossed_above(slow_ma)
exits = fast_ma.ma_crossed_below(slow_ma)
Custom Logic at C-Speed
While vectorisation handles the 'what', high-performance backtesting also requires control over the 'how'. Standard backtesting engines often impose rigid logic for order execution and portfolio management. VectorBT PRO breaks this limitation by integrating with Numba, a just-in-time (JIT) compiler that translates Python and NumPy code into fast machine code.
This allows you to write custom portfolio simulators and signal processors in expressive Python, which are then compiled and executed at speeds comparable to C. You can implement bespoke logic—such as path-dependent stop-losses, dynamic capital allocation based on portfolio volatility, or complex order hierarchies—without sacrificing performance. The core simulation loop can be replaced entirely with a Numba-jitted function that operates directly on the underlying NumPy arrays, giving you complete control over state management from one time step to the next.
from numba import njit
@njit
def custom_portfolio_logic(ctx, entries, exits, price, cash, shares, ...):
# This function is compiled to machine code by Numba
# It provides granular control over the simulation loop
for i in range(len(price)):
# Custom logic: only trade if portfolio volatility is below a threshold
current_vol = calculate_rolling_vol(ctx.portfolio_returns[:i])
if current_vol < 0.02:
if entries[i]:
# Execute buy logic
pass
elif exits[i]:
# Execute sell logic
pass
# Update portfolio state for the next iteration
update_portfolio_state(cash, shares, price[i])
return # Return final portfolio state
Advanced Data and Execution Modelling
Real-world trading involves messy data and imperfect execution. A professional backtesting framework must model these realities with high fidelity.
Multi-asset portfolios often contain instruments with non-synchronous time series due to different trading hours or holidays. VectorBT PRO provides sophisticated data alignment and resampling tools that can intelligently forward-fill, back-fill, or interpolate data to create a coherent universe for vectorised operations. This ensures that signals generated on one asset are evaluated against the correct contemporaneous state of all other assets.
Furthermore, transaction costs cannot be modelled as a simple fixed percentage. Slippage, the difference between the expected and executed price, is a dynamic quantity. VectorBT PRO allows for the implementation of advanced slippage models, including those based on the bid-ask spread or the Volume-Weighted Average Price (VWAP). These models can be vectorised to apply realistic, volume-sensitive execution costs across thousands of simulated trades.
Finally, complex order types like stop-losses, take-profits, and partial fills are fundamental to risk management. Implementing these in a vectorised manner is non-trivial, as they create path-dependency. The solution involves generating signal arrays not just for entries, but for every potential exit condition. These separate signal matrices can then be combined with priority rules within the Numba-jitted portfolio simulator to determine which event takes precedence at each time step. This preserves the performance benefits of vectorisation while accurately modelling the behaviour of a real-world trading system.
What is the primary advantage of representing a backtesting simulation as a multi-dimensional NumPy array compared to a traditional event-driven loop?
How does Numba enhance the capabilities of a vectorised backtesting framework like VectorBT PRO?
By representing strategies as multi-dimensional arrays and leveraging JIT compilation for custom logic, it's possible to achieve institutional-level performance and fidelity in backtesting, turning a process that once took hours into one that takes seconds.
