← Back to Blog

Designing My Own Backtesting Framework for Multi-Asset Portfolios

Peter Bieda

Author

I’ve always believed that if you want to really understand a system, you have to build it yourself. That principle applies to trading backtests as much as anything else. Over time, I stopped trusting third-party backtesting platforms. Their assumptions were often opaque, execution logic was hidden, and edge cases were rarely documented. If I wanted to confidently test multi-asset strategies, I had to build my own framework.

This project was about accuracy, speed, and flexibility. I wanted a system capable of handling equities, options, futures, and forex, while allowing me to simulate complex portfolio dynamics. The guiding principle was vectorized simulation: processing entire time series at once wherever possible, rather than iterating through rows with slow loops.

Why I Built My Own Framework

Existing platforms often failed in three areas:

  1. Opacity – You don’t know exactly how trades are executed, fees applied, or slippage calculated.
  2. Performance – Loop-based simulations for multi-asset portfolios are painfully slow.
  3. Flexibility – Backtests often assume single-asset strategies or fixed data feeds, making experimentation difficult.

I realized that if I wanted full control over assumptions, execution logic, and risk, I had to write my own. Python is perfect for rapid iteration, and with NumPy and Pandas, vectorized simulation makes large-scale multi-asset testing feasible.

Core Architecture

I designed the framework around three main components:

  1. Data Layer – Ingests and cleans multiple time series, aligns timestamps, handles missing data.
  2. Execution Layer – Simulates order execution, fills, and slippage.
  3. Portfolio Engine – Tracks positions, cash, risk metrics, and aggregates performance.

Here’s a conceptual diagram:

           +----------------+
           | Market Data    |
           | (multi-asset)  |
           +--------+-------+
                    |
                    v
           +----------------+
           | Data Layer     |
           | - Alignment    |
           | - Cleaning     |
           +--------+-------+
                    |
                    v
           +----------------+
           | Execution Layer|
           | - Vectorized   |
           | - Slippage     |
           | - Fees         |
           +--------+-------+
                    |
                    v
           +----------------+
           | Portfolio Engine|
           | - Positions    |
           | - PnL          |
           | - Risk Metrics |
           +----------------+

Each stage interacts seamlessly, allowing me to plug in new strategies without rewriting the infrastructure.

Vectorized Simulation

The key innovation in my framework was vectorization. Instead of looping through every timestamp and asset individually, I represent prices and positions as arrays, enabling NumPy to do the heavy lifting in compiled code.

Example: simulating fills for a portfolio of 100 symbols across 10,000 timestamps:

import numpy as np

# Prices: 100 assets x 10,000 timestamps
prices = np.random.rand(100, 10000) * 100

# Signals: buy=1, sell=-1, hold=0
signals = np.random.choice([-1, 0, 1], size=(100, 10000))

# Portfolio positions (vectorized)
positions = np.cumsum(signals, axis=1)

# Calculate daily PnL (vectorized)
daily_pnl = positions[:, 1:] * (prices[:, 1:] - prices[:, :-1])
portfolio_pnl = daily_pnl.sum(axis=0)

This approach is orders of magnitude faster than loop-based backtests, making it feasible to run hundreds of scenarios in minutes.

Handling Multi-Asset Portfolios

I designed the framework to handle:

  • Equities – share-level fills, bid-ask spreads, commissions
  • Options – greeks, delta-hedging, complex payoffs
  • Futures – contract rollover, margin
  • Forex – bid/ask, spreads, leverage

Everything shares the same vectorized execution logic. This makes multi-asset strategies seamless. For example, I can simulate a hedged equity-futures portfolio in a single vectorized pass:

# Equity position + futures hedge
hedge_ratio = 0.5
combined_positions = positions_equity - hedge_ratio * positions_futures

daily_pnl = combined_positions[:, 1:] * (prices_combined[:, 1:] - prices_combined[:, :-1])
portfolio_pnl = daily_pnl.sum(axis=0)

Execution Logic

One of the main reasons I built my own system was control over execution. Third-party backtests often assume unrealistic fills. My framework accounts for:

  • Market impact – large orders move prices
  • Slippage – difference between intended and actual fill
  • Partial fills – orders can execute in chunks across timestamps
  • Latency – simulate delays between signal generation and execution

Example of a simple fill model:

def execute_order(prices, qty, max_slippage=0.01):
    slippage = np.random.uniform(-max_slippage, max_slippage, size=prices.shape)
    fill_prices = prices * (1 + slippage)
    return qty * fill_prices

Portfolio Metrics

The portfolio engine tracks:

  • Positions per asset
  • Cash and margin
  • Realized and unrealized PnL
  • Sharpe ratio, drawdown, and other risk metrics

Everything is computed vectorized, enabling quick aggregation over long histories and multiple scenarios.

Sample Test Script

To validate the framework, I created a small test simulation:

import numpy as np

# Simulate 5 assets over 1,000 timestamps
prices = np.random.rand(5, 1000) * 100
signals = np.random.choice([-1, 0, 1], size=(5, 1000))

positions = np.cumsum(signals, axis=1)
daily_pnl = positions[:, 1:] * (prices[:, 1:] - prices[:, :-1])
portfolio_pnl = daily_pnl.sum(axis=0)

print("Portfolio PnL:", portfolio_pnl[-10:])
print("Total return:", portfolio_pnl.sum())

This test allowed me to:

  • Validate vectorized calculations
  • Ensure multi-asset alignment
  • Track cumulative performance
  • Quickly debug edge cases like missing data or zero liquidity

What I Learned

1. Vectorization is a game-changer

Loop-based backtests are slow and error-prone. Vectorized simulation lets you run thousands of scenarios in minutes.

2. Transparency is critical

When you build your own engine, every assumption is explicit: fees, fills, rollover, and slippage. No hidden black boxes.

3. Multi-asset logic is tricky but manageable

Handling equities, options, futures, and forex in a single system requires careful alignment of timestamps and positions. Vectorization makes this feasible.

4. Speed enables experimentation

Once backtests are fast, you can test multiple portfolio allocations, hedging ratios, and rebalancing intervals without waiting hours.

5. Control builds confidence

Knowing exactly how each trade is executed allows me to trust the results and make informed decisions.

Extending the Framework

I plan to add:

  • Event-driven backtesting – simulate tick-level events
  • Monte Carlo scenarios – randomizing market conditions
  • Transaction cost modeling – impact of large orders on fills
  • Python/C++ hybrid engine – speed up heavy loops with compiled code

The goal is always the same: fast, accurate, transparent, and flexible multi-asset backtesting.

Conclusion

By building my own backtesting framework:

  • I stopped relying on opaque third-party tools
  • I gained complete control over portfolio execution logic
  • I accelerated multi-asset simulations through vectorization
  • I built a foundation for rapid strategy experimentation

This project reinforced a simple lesson: if you want to trust your results, you have to control the simulation. Python’s ecosystem made it easy to prototype, but careful vectorized design made the system fast enough for realistic multi-asset portfolios.

Diagram: Multi-Asset Backtesting Framework

+----------------+
| Market Data    |
| (Equities, FX, |
| Futures, Options)|
+--------+-------+
         |
         v
+----------------+
| Data Layer     |
| - Clean & Align|
+--------+-------+
         |
         v
+----------------+
| Execution Layer|
| - Vectorized   |
| - Slippage     |
| - Fees         |
+--------+-------+
         |
         v
+----------------+
| Portfolio Engine|
| - Positions     |
| - PnL           |
| - Risk Metrics  |
+----------------+