Skip to content

Backtesting Engine Architecture

Version: 1.0
Date: 2026-01-26
Status: Design Document

Table of Contents

  1. Overview
  2. Architecture Design
  3. Component Descriptions
  4. Event Flow
  5. Design Decisions
  6. Implementation Guidelines

Overview

The DSTA Backtesting Engine is an event-driven system for testing trading strategies against historical market data. It simulates realistic trading conditions including slippage, fees, and market microstructure.

Goals

  • Realistic Simulation: Accurately model real-world trading conditions
  • Flexibility: Support multiple strategies, timeframes, and instruments
  • Performance: Handle large datasets efficiently
  • Extensibility: Easy to add new strategies and indicators
  • Testability: Comprehensive test coverage for all components

Architecture Pattern

Event-Driven Architecture - Components communicate through events, ensuring loose coupling and temporal accuracy (no lookahead bias).

Architecture Design

High-Level Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Backtesting Engine                        │
│                                                              │
│  ┌──────────────┐        ┌──────────────┐                  │
│  │ DataHandler  │───────▶│  Event Queue │                  │
│  └──────────────┘        └──────┬───────┘                  │
│                                  │                           │
│                                  ▼                           │
│  ┌──────────────┐        ┌──────────────┐                  │
│  │   Strategy   │◀───────│Event Loop    │                  │
│  └──────┬───────┘        │(Coordinator) │                  │
│         │                └──────────────┘                  │
│         ▼                                                    │
│  ┌──────────────┐        ┌──────────────┐                  │
│  │  Portfolio   │───────▶│  Execution   │                  │
│  └──────────────┘        │   Handler    │                  │
│                          └──────────────┘                  │
└─────────────────────────────────────────────────────────────┘

Component Diagram

┌────────────────────────────────────────────────────────────────┐
│                        Event Queue                              │
│  [MarketEvent, SignalEvent, OrderEvent, FillEvent]             │
└────────────────┬──────────────────┬───────────────────────────┘
                 │                  │
    ┌────────────▼─────────────┐   │
    │     DataHandler          │   │
    │  - Load historical data  │   │
    │  - Emit MarketEvent      │   │
    │  - Bar-by-bar iteration  │   │
    └──────────────────────────┘   │
    ┌───────────────────────────────▼─────────────────┐
    │              Strategy                            │
    │  - Calculate indicators                          │
    │  - Generate signals                              │
    │  - Emit SignalEvent                             │
    │  Base class + concrete implementations           │
    └────────────────────┬─────────────────────────────┘
    ┌────────────────────▼─────────────────┐
    │          Portfolio                   │
    │  - Track positions & cash            │
    │  - Calculate portfolio value         │
    │  - Risk management                   │
    │  - Position sizing                   │
    │  - Emit OrderEvent                   │
    └────────────────────┬─────────────────┘
    ┌────────────────────▼─────────────────┐
    │       ExecutionHandler               │
    │  - Simulate order fills              │
    │  - Model slippage & fees             │
    │  - Emit FillEvent                    │
    │  - Track execution quality           │
    └──────────────────────────────────────┘

Component Descriptions

1. Event Classes

Purpose: Represent different types of events in the backtesting lifecycle.

Event Types:

  • MarketEvent: New market data available (OHLCV bar)
  • SignalEvent: Strategy generated a trading signal
  • OrderEvent: Order to be executed
  • FillEvent: Order has been filled

Design:

class Event(ABC):
    """Base event class."""
    event_type: str
    timestamp: datetime

class MarketEvent(Event):
    """Market data update."""
    symbol: str
    bar_data: Dict  # OHLCV + volume

class SignalEvent(Event):
    """Trading signal from strategy."""
    strategy_id: str
    symbol: str
    signal_type: str  # 'LONG', 'SHORT', 'EXIT'
    strength: float  # Signal confidence

class OrderEvent(Event):
    """Order to execute."""
    symbol: str
    order_type: str  # 'MARKET', 'LIMIT'
    quantity: int
    direction: str  # 'BUY', 'SELL'

class FillEvent(Event):
    """Executed order."""
    symbol: str
    quantity: int
    direction: str
    fill_price: Decimal
    commission: Decimal

2. DataHandler

Purpose: Provide historical market data to the backtesting engine.

Responsibilities: - Load data from database - Iterate through data bar-by-bar - Emit MarketEvent for each bar - Support multiple symbols and timeframes - Handle data synchronization

Interface:

class DataHandler(ABC):
    def get_latest_bar(self, symbol: str) -> Dict
    def get_latest_bars(self, symbol: str, N: int) -> List[Dict]
    def update_bars(self) -> None  # Move to next bar
    def continue_backtest(self) -> bool

3. Strategy

Purpose: Implement trading logic and generate signals.

Responsibilities: - Calculate technical indicators - Generate trading signals - Emit SignalEvent when conditions met - Track strategy state

Interface:

class Strategy(ABC):
    def calculate_signals(self, event: MarketEvent) -> None
    def get_indicators(self) -> Dict

Example Strategies: - Simple Moving Average Crossover - RSI Mean Reversion - MACD Trend Following - Bollinger Band Breakout

4. Portfolio

Purpose: Track positions, cash, and portfolio value. Generate orders from signals.

Responsibilities: - Maintain current positions - Track cash balance - Calculate portfolio value - Position sizing (fixed, percentage, Kelly criterion) - Risk management (stop-loss, take-profit) - Generate OrderEvent from SignalEvent

State:

class Portfolio:
    initial_capital: Decimal
    current_holdings: Dict[str, int]  # symbol -> quantity
    cash: Decimal
    equity: Decimal
    positions: List[Position]

5. ExecutionHandler

Purpose: Simulate order execution with realistic market conditions.

Responsibilities: - Execute orders at realistic prices - Model slippage - Apply commission fees - Handle partial fills - Emit FillEvent

Execution Models: - Market Order: Fill at next available price + slippage - Limit Order: Fill only if price reaches limit - Stop Order: Trigger when price crosses stop

Slippage Models: - Fixed percentage - Volume-based - Bid-ask spread

6. Backtesting Engine (Coordinator)

Purpose: Orchestrate the entire backtesting process.

Responsibilities: - Initialize all components - Run event loop - Coordinate event flow - Track progress - Generate results

Workflow:

def run_backtest():
    while data_handler.continue_backtest():
        # 1. Get new market data
        if data_handler.update_bars():
            # 2. Strategy calculates signals
            strategy.calculate_signals(market_event)

            # 3. Portfolio generates orders
            portfolio.update_signal(signal_event)

            # 4. Execute orders
            execution.execute_order(order_event)

            # 5. Update portfolio with fills
            portfolio.update_fill(fill_event)

Event Flow

Complete Event Flow Diagram

1. DataHandler
   ├─ Load next bar from database
   └─▶ Emit MarketEvent
2. Strategy
   ├─ Receive MarketEvent
   ├─ Calculate indicators
   ├─ Evaluate conditions
   └─▶ Emit SignalEvent (if condition met)
3. Portfolio
   ├─ Receive SignalEvent
   ├─ Check risk limits
   ├─ Calculate position size
   └─▶ Emit OrderEvent
4. ExecutionHandler
   ├─ Receive OrderEvent
   ├─ Simulate fill (price + slippage)
   ├─ Apply commission
   └─▶ Emit FillEvent
5. Portfolio
   ├─ Receive FillEvent
   ├─ Update positions
   ├─ Update cash
   └─ Calculate new equity

   [Repeat from step 1 until data exhausted]

Event Timeline Example

Time    Event           Source          Target          Data
────────────────────────────────────────────────────────────
10:00   MarketEvent     DataHandler     Strategy        BTC: O:50k H:51k L:49k C:50.5k
10:00   SignalEvent     Strategy        Portfolio       BUY signal, strength: 0.8
10:00   OrderEvent      Portfolio       Execution       BUY 1 BTC at MARKET
10:00   FillEvent       Execution       Portfolio       Filled @ 50,550 (0.1% slip)
10:01   MarketEvent     DataHandler     Strategy        BTC: O:50.5k H:51.5k...
...

Design Decisions

1. Event-Driven vs Time-Driven

Decision: Event-Driven Architecture

Rationale: - ✅ Eliminates lookahead bias (can't access future data) - ✅ More realistic - matches real trading - ✅ Easier to test individual components - ✅ Better separation of concerns - ❌ Slightly more complex than time-driven

2. In-Memory vs Database State

Decision: In-Memory state during backtest, persist results after

Rationale: - ✅ Much faster execution - ✅ Simpler implementation - ✅ Can replay/debug easily - ❌ Limited by available RAM (acceptable for most backtests)

3. Synchronous vs Asynchronous

Decision: Synchronous event processing

Rationale: - ✅ Deterministic execution - ✅ Easier debugging - ✅ Sequential processing matches backtesting needs - ❌ Can't parallelize (not needed for single-threaded backtest)

4. Position Sizing Strategy

Decision: Pluggable position sizing with multiple strategies

Rationale: - ✅ Different strategies need different approaches - ✅ Risk management is strategy-specific - ✅ Easy to A/B test different sizing methods

Supported Methods: - Fixed size - Percentage of portfolio - Volatility-adjusted (ATR-based) - Kelly criterion - Custom functions

5. Commission and Slippage Models

Decision: Realistic models based on exchange characteristics

Rationale: - ✅ Critical for accurate results - ✅ Can calibrate to actual trading costs - ✅ Prevents over-optimization

Default Settings: - Commission: 0.1% per trade (maker/taker fees) - Slippage: 0.05% for market orders - Minimum tick size: Exchange-specific

6. Data Frequency

Decision: Support multiple timeframes (1m, 5m, 15m, 1h, 4h, 1d)

Rationale: - ✅ Different strategies work at different timeframes - ✅ Can test strategy at multiple resolutions - ✅ Matches available data

Implementation Guidelines

File Structure

src/backtesting/
├── __init__.py
├── base.py              # Abstract base classes and interfaces
├── events.py            # Event classes
├── data_handler.py      # DataHandler implementation
├── strategy.py          # Strategy base class
├── portfolio.py         # Portfolio manager
├── execution.py         # ExecutionHandler
├── backtest.py          # Main backtesting engine
├── indicators.py        # Technical indicators (later)
├── performance.py       # Performance metrics (later)
└── strategies/          # Concrete strategy implementations
    ├── __init__.py
    ├── sma_crossover.py
    └── rsi_mean_reversion.py

Coding Conventions

  1. Type Hints: Use type hints for all function signatures
  2. Docstrings: Google-style docstrings for all classes and methods
  3. Testing: Unit tests for each component, integration tests for full backtest
  4. Logging: Comprehensive logging at DEBUG, INFO, WARNING levels
  5. Error Handling: Explicit exception handling with custom exceptions

Testing Strategy

  1. Unit Tests: Test each component in isolation
  2. Integration Tests: Test full backtest workflow
  3. Validation Tests: Compare results with known strategies
  4. Performance Tests: Ensure acceptable execution speed

Performance Considerations

  1. Data Loading: Batch load data, not one bar at a time
  2. Indicator Caching: Cache calculated indicators
  3. NumPy: Use NumPy for numerical computations
  4. Profiling: Profile hot paths and optimize

Extension Points

Future enhancements can be added through: - Custom indicator implementations - New strategy classes - Alternative execution models - Advanced position sizing algorithms - Multi-asset portfolio optimization

Summary

This event-driven architecture provides: - ✅ Realistic backtesting without lookahead bias - ✅ Modular, testable components - ✅ Easy to extend with new strategies - ✅ Efficient performance - ✅ Production-ready design

Next steps: Implement components following this design.