Backtesting Engine Architecture¶
Version: 1.0
Date: 2026-01-26
Status: Design Document
Table of Contents¶
- Overview
- Architecture Design
- Component Descriptions
- Event Flow
- Design Decisions
- 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¶
- Type Hints: Use type hints for all function signatures
- Docstrings: Google-style docstrings for all classes and methods
- Testing: Unit tests for each component, integration tests for full backtest
- Logging: Comprehensive logging at DEBUG, INFO, WARNING levels
- Error Handling: Explicit exception handling with custom exceptions
Testing Strategy¶
- Unit Tests: Test each component in isolation
- Integration Tests: Test full backtest workflow
- Validation Tests: Compare results with known strategies
- Performance Tests: Ensure acceptable execution speed
Performance Considerations¶
- Data Loading: Batch load data, not one bar at a time
- Indicator Caching: Cache calculated indicators
- NumPy: Use NumPy for numerical computations
- 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.