Skip to main content

Event-Driven Backtesting

Event-driven backtesting simulates the market environment by processing each price update sequentially, just like live trading. It provides realistic order execution with full support for stop losses, take profits, and position sizing.

Quick Start

from investing_algorithm_framework import create_app, BacktestDateRange
from datetime import datetime, timezone

app = create_app()
app.add_strategy(MyStrategy())
app.add_market(market="bitvavo", trading_symbol="EUR")

# Define the backtest period
backtest_range = BacktestDateRange(
start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2024, 1, 1, tzinfo=timezone.utc)
)

# Run an event backtest
backtest = app.run_backtest(
backtest_date_range=backtest_range,
initial_amount=1000
)

Running an Event-Based Backtest

backtest = app.run_backtest(
backtest_date_range=backtest_range,
initial_amount=1000,
risk_free_rate=0.027, # Optional: for metrics such as Sharpe ratio calculation
)

Multiple Date Ranges

Test your strategy across different market conditions:

date_ranges = [
BacktestDateRange(
start_date=datetime(2022, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2022, 12, 31, tzinfo=timezone.utc),
name="Bear Market 2022"
),
BacktestDateRange(
start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2023, 12, 31, tzinfo=timezone.utc),
name="Recovery 2023"
),
]

backtests = app.run_backtests(
backtest_date_ranges=date_ranges,
... # other params
)

Event-Driven vs Vector Backtesting

The framework supports two backtesting modes. This page covers event-driven backtesting. For vector backtesting, see Vector Backtesting.

AspectEvent-DrivenVector
SpeedSlower, realistic simulation10-100x faster
Stop Loss / Take ProfitFully supportedNot supported
Signal TimingExecutes at next strategy intervalExecutes at exact signal timestamp
Position SizingBased on portfolio at execution timeBased on portfolio at signal time
Data LoadingSliding window at each stepAll data loaded at once
Best ForFinal validation, realistic resultsFast prototyping, parameter sweeps

Analyzing Results

Backtest Report

Generate a visual report of your backtest:

from investing_algorithm_framework import BacktestReport

# Single strategy
report = BacktestReport(backtest)
report.show(browser=True) # Opens in your default browser

# Multi-strategy comparison
report = BacktestReport(backtests=[backtest_a, backtest_b])
report.show()

# Load from saved backtests on disk
report = BacktestReport.open(directory_path="./my_backtests")
report.show()

# Save as a self-contained HTML file
report.save("comparison_report.html")

See Backtest Reports for full documentation on the dashboard features, compare mode, and API reference.

Accessing Metrics

# Get performance metrics
metrics = backtest.get_backtest_metrics()
print(f"Total Return: {metrics.total_return}%")
print(f"Sharpe Ratio: {metrics.sharpe_ratio}")
print(f"Max Drawdown: {metrics.max_drawdown}%")
print(f"Total Trades: {metrics.number_of_trades_closed}")

Accessing Trades

for run in backtest.get_all_backtest_runs():
trades = run.trades
for trade in trades:
print(f"Symbol: {trade.symbol}")
print(f"Entry: {trade.entry_price}")
print(f"Exit: {trade.exit_price}")
print(f"Return: {trade.return_percentage}%")

Saving and Loading Backtests

Save to Directory

backtests = app.run_vector_backtests(
strategies=strategies,
backtest_date_ranges=date_ranges,
backtest_storage_directory="./my_backtests",
initial_amount=1000
)

Load from Directory

from investing_algorithm_framework import load_backtests_from_directory

backtests = load_backtests_from_directory("./my_backtests")

Advanced Features

Checkpointing

Resume interrupted backtests:

backtests = app.run_vector_backtests(
strategies=strategies,
backtest_date_ranges=date_ranges,
backtest_storage_directory="./my_backtests",
use_checkpoints=True, # Resume from last checkpoint
initial_amount=1000
)

Filtering Strategies

Filter out underperforming strategies during backtesting:

def window_filter(backtest_run):
"""Filter after each date range"""
return backtest_run.backtest_metrics.total_return > 0

def final_filter(backtest):
"""Filter at the end"""
return backtest.backtest_summary.sharpe_ratio > 1.0

backtests = app.run_vector_backtests(
strategies=strategies,
backtest_date_ranges=date_ranges,
window_filter_function=window_filter,
final_filter_function=final_filter,
initial_amount=1000
)

Parallel Processing

Utilize multiple CPU cores for faster backtesting:

import os

backtests = app.run_vector_backtests(
strategies=strategies,
backtest_date_ranges=date_ranges,
n_workers=os.cpu_count() - 1, # Use all cores except one
initial_amount=1000
)

Best Practices

  1. Use representative date ranges: Include bull markets, bear markets, and sideways periods
  2. Avoid overfitting: Don't optimize too heavily on historical data
  3. Account for costs: Consider trading fees and slippage in your strategy
  4. Use checkpointing: For long-running backtests, enable checkpoints to avoid losing progress
  5. Start with event-based: Use event-based backtesting for realistic simulation, then scale with vector backtesting

Next Steps