-
Notifications
You must be signed in to change notification settings - Fork 724
Backtesting Engine
- Introduction
- Event-Driven Simulation Architecture
- Order Execution Simulation
- Trading System Integration
- Configuration and Execution Examples
- C++ Core and Python Interface
- Bias Mitigation Strategies
- Performance Considerations
- Conclusion
The Hikyuu backtesting engine provides a comprehensive framework for evaluating trading strategies through historical market data simulation. This document details the architecture and implementation of the backtesting system, focusing on its event-driven simulation model, order execution mechanics, and integration with various trading components. The engine supports multiple order types, sophisticated slippage modeling, and transaction cost calculations, enabling realistic simulation of trading strategies. The system is designed with a clear separation between the high-performance C++ core and the accessible Python interface, allowing for both efficient computation and flexible strategy development.
The Hikyuu backtesting engine employs an event-driven architecture that processes market data in chronological order, simulating the execution of trading strategies based on historical price movements. The core of this architecture is the backtest function defined in the plugin interface, which orchestrates the simulation process by iterating through historical market data and triggering strategy execution at each time step.
The simulation begins with the initialization of a strategy context that contains all necessary components for trading decisions, including signals, money management, and risk control modules. Market data is fed into the engine through the KData structure, which represents historical price information for a specific security. The engine processes this data sequentially, advancing through each trading period and invoking the strategy's decision-making logic.
Key aspects of the event-driven architecture include:
- Chronological Processing: Market data is processed in strict chronological order, ensuring that trading decisions are based only on information available at each point in time
- Event Queue: The system maintains an event queue that handles various trading events such as order executions, position updates, and performance calculations
- State Management: The engine maintains the current state of the trading account, including cash balance, positions, and performance metrics
- Component Integration: Various trading system components are integrated into the simulation, allowing for comprehensive strategy evaluation
The architecture ensures that trading signals are processed in the correct temporal sequence, preventing look-ahead bias and providing a realistic simulation environment.
sequenceDiagram
participant User as "User/Strategy"
participant Backtest as "Backtest Engine"
participant Market as "Market Data"
participant TM as "TradeManager"
User->>Backtest : Initialize strategy context
Backtest->>Market : Request historical data
Market-->>Backtest : Provide KData stream
loop For each trading period
Backtest->>User : Trigger on_bar event
User->>Backtest : Submit trading signals
Backtest->>TM : Execute orders
TM-->>Backtest : Return execution results
Backtest->>Backtest : Update performance metrics
end
Backtest->>User : Return backtest results
Diagram sources
- backtest.h
- System.h
Section sources
- backtest.h
- System.h
The order execution simulation in Hikyuu supports multiple order types and incorporates realistic market conditions through slippage modeling and transaction cost calculations. The engine handles market, limit, and stop orders, simulating their execution based on the available market data.
The backtesting engine supports three primary order types:
- Market Orders: Executed at the current market price, either at the current bar's close or the next bar's open, depending on the execution mode
- Limit Orders: Executed only when the market price reaches the specified limit price
- Stop Orders: Triggered when the market price reaches a specified stop price, then executed as market orders
The execution mode is configurable through the mode parameter in the backtest function, where mode 0 executes trades at the current bar's close price, and mode 1 executes at the next bar's open price. This allows for different simulation scenarios that reflect various trading styles and market conditions.
Slippage is modeled through the Slippage component, which calculates the difference between the intended execution price and the actual execution price. The engine supports multiple slippage models:
- Fixed Slippage: Applies a constant price difference regardless of market conditions
- Percentage Slippage: Applies a percentage-based difference relative to the intended price
- Statistical Distribution: Models slippage using statistical distributions that reflect real market behavior
The slippage calculation is integrated into the order execution process through the _getRealBuyPrice and _getRealSellPrice methods in the System class, which modify the planned execution price based on the configured slippage model.
Transaction costs are calculated using the TradeCost component, which accounts for various fees and commissions associated with trading. The cost calculation includes:
- Commission Fees: Brokerage commissions based on trade value or volume
- Stamp Duty: Government-imposed taxes on transactions
- Transfer Fees: Fees for transferring securities
- Other Costs: Any additional transaction-related expenses
The cost calculation is performed during order execution and deducted from the trading account, providing a realistic assessment of strategy performance.
flowchart TD
Start([Order Submission]) --> OrderType{"Order Type?"}
OrderType --> |Market| MarketExecution["Execute at market price<br/>(current close or next open)"]
OrderType --> |Limit| LimitExecution["Execute when price ≤ limit<br/>(buy) or price ≥ limit<br/>(sell)"]
OrderType --> |Stop| StopExecution["Trigger when price ≥ stop<br/>(buy) or price ≤ stop<br/>(sell)"]
MarketExecution --> Slippage["Apply slippage model<br/>(fixed, percentage, statistical)"]
LimitExecution --> Slippage
StopExecution --> Slippage
Slippage --> CostCalculation["Calculate transaction costs<br/>(commission, tax, fees)"]
CostCalculation --> AccountUpdate["Update account balance<br/>& position records"]
AccountUpdate --> End([Execution Complete])
Diagram sources
- System.h
- backtest.h
- _TradeCost.cpp
Section sources
- System.h
- backtest.h
- _TradeCost.cpp
The Hikyuu backtesting engine integrates seamlessly with various trading system components, including signals, money management, and stop-loss modules. This integration allows for comprehensive strategy evaluation that considers multiple aspects of trading beyond simple entry and exit signals.
Trading signals are generated by the Signal component, which analyzes market data to identify potential trading opportunities. The signal component is integrated into the strategy execution loop, where it processes the current market data and generates buy or sell signals based on its internal logic. The signals are then passed to the order execution system for processing.
The money management component (MoneyManager) determines the position size for each trade based on the current account status and risk parameters. It considers factors such as available capital, risk tolerance, and position concentration limits when calculating the appropriate trade size. The money manager is invoked during the order preparation phase, where it calculates the number of shares or contracts to trade.
The stop-loss module (Stoploss) provides risk management by automatically closing positions when predefined loss thresholds are reached. The stop-loss component monitors the current position value and market conditions, triggering sell orders when the stop-loss price is reached. This helps protect the trading account from excessive losses and enforces disciplined risk management.
The integration of these components follows a well-defined sequence during each trading period:
- Market data is updated for the current period
- The environment and condition components evaluate market conditions
- The signal component generates trading signals
- The money manager calculates position size
- The stop-loss and profit goal components update their levels
- Orders are executed with slippage and cost calculations
- Account and performance metrics are updated
This modular design allows for flexible strategy development and easy component replacement or customization.
classDiagram
class System {
+TradeManagerPtr m_tm
+MoneyManagerPtr m_mm
+EnvironmentPtr m_ev
+ConditionPtr m_cn
+SignalPtr m_sg
+StoplossPtr m_st
+StoplossPtr m_tp
+ProfitGoalPtr m_pg
+SlippagePtr m_sp
+run(Stock, KQuery)
}
class TradeManager {
+double currentCash()
+FundsRecord getFunds()
+addTradeRecord()
}
class MoneyManager {
+double getBuyNumber()
+double getSellNumber()
}
class Signal {
+bool isValid()
+const PriceList& getBuySignal()
+const PriceList& getSellSignal()
}
class Stoploss {
+price_t getPrice()
+void _reset()
+StoplossPtr _clone()
}
class Slippage {
+price_t getRealBuyPrice()
+price_t getRealSellPrice()
+void _reset()
+SlippagePtr _clone()
}
System --> TradeManager : "uses"
System --> MoneyManager : "uses"
System --> Signal : "uses"
System --> Stoploss : "uses"
System --> Slippage : "uses"
System --> ProfitGoal : "uses"
System --> Environment : "uses"
System --> Condition : "uses"
Diagram sources
- System.h
- backtest.h
Section sources
- System.h
- backtest.h
The Hikyuu backtesting engine can be configured and executed through both C++ and Python interfaces. The following examples demonstrate how to set up and run a backtest using the analysis system functions.
The analysisSystemList function provides a high-level interface for running multiple backtests and comparing their performance. This function accepts a list of system strategies and corresponding stocks, executing each strategy on its designated security and returning performance metrics for comparison.
flowchart TD
Start([Initialize Backtest]) --> ContextSetup["Create StrategyContext<br/>Configure components"]
ContextSetup --> ParameterConfig["Set backtest parameters:<br/>- start_date<br/>- end_date<br/>- ktype<br/>- mode<br/>- support_short"]
ParameterConfig --> TMSetup["Initialize TradeManager<br/>(set initial cash, cost model)"]
TMSetup --> RunBacktest["Call backtest() function<br/>with on_bar callback"]
RunBacktest --> ProcessResults["Collect and analyze<br/>performance metrics"]
ProcessResults --> End([Generate Report])
Diagram sources
- analysis_sys.cpp
- backtest.h
Section sources
- analysis_sys.cpp
- backtest.h
The Hikyuu backtesting engine features a dual-layer architecture with a high-performance C++ core and a user-friendly Python interface. This design leverages the computational efficiency of C++ for intensive calculations while providing the accessibility and flexibility of Python for strategy development.
The C++ core implements the fundamental backtesting algorithms and data structures, optimized for performance and memory efficiency. Key components include:
- Event Processing: High-speed event queue and processing loop
- Data Structures: Efficient representations of market data, positions, and trading records
- Mathematical Calculations: Optimized implementations of performance metrics and statistical calculations
- Memory Management: Careful memory allocation and deallocation to minimize overhead
The C++ core is compiled as a shared library, exposing its functionality through a well-defined API.
The Python interface, implemented using pybind11, provides a convenient wrapper around the C++ core. This interface allows users to:
- Define trading strategies using Python code
- Configure backtest parameters through Python functions
- Access and analyze results using Python data structures
- Integrate with popular Python data analysis libraries
The interface maintains the performance benefits of the C++ core while providing the ease of use and rapid development capabilities of Python. The export_plugin_backtest function in the Python wrapper exposes the backtest functionality to Python, allowing users to define their strategy logic in Python while executing it through the high-performance C++ engine.
The C++ core includes several performance optimizations:
-
Parallel Processing: The
analysisSystemListfunction uses parallel execution to run multiple backtests simultaneously - Memory Efficiency: Data structures are designed to minimize memory footprint and cache misses
- Algorithm Optimization: Critical algorithms are optimized for speed and numerical stability
- Reduced Overhead: The core minimizes function call overhead and object creation during the simulation loop
This architecture enables efficient execution of large-scale backtests while maintaining the flexibility needed for strategy development.
graph TB
subgraph "Python Interface"
PyStrategy[Python Strategy]
PyConfig[Python Configuration]
PyAnalysis[Python Analysis]
end
subgraph "C++ Core Engine"
CppBacktest[Backtest Engine]
CppPerformance[Performance Calculator]
CppData[Market Data Handler]
CppExecution[Order Execution]
end
PyStrategy --> |Strategy Logic| CppBacktest
PyConfig --> |Parameters| CppBacktest
CppBacktest --> |Results| PyAnalysis
CppBacktest --> CppExecution
CppBacktest --> CppPerformance
CppBacktest --> CppData
Diagram sources
- _backtest.cpp
- backtest.cpp
Section sources
- _backtest.cpp
- backtest.cpp
The Hikyuu backtesting engine incorporates several mechanisms to mitigate common biases that can lead to overly optimistic performance estimates.
Look-ahead bias is prevented through strict chronological processing of market data. The engine ensures that trading decisions at any point in time are based only on information available up to that moment. This is achieved by:
- Processing market data in sequential order without peeking at future data
- Using appropriate execution modes (current close vs. next open) to reflect realistic trading conditions
- Implementing proper data alignment between different data sources
The event-driven architecture ensures that each trading decision is made based on the current state of the market and historical data, without access to future information.
Survivorship bias is addressed by including delisted securities in the backtesting universe when appropriate. The engine can be configured to:
- Include securities that were active during the backtest period, regardless of current listing status
- Handle corporate actions such as mergers, acquisitions, and bankruptcies
- Account for the impact of delistings on portfolio performance
This approach provides a more realistic assessment of strategy performance by considering the full universe of securities that would have been available to traders during the historical period.
Additional bias mitigation strategies include:
- Data Snooping Prevention: The engine supports walk-forward optimization and out-of-sample testing to validate strategy robustness
- Transaction Cost Realism: Comprehensive transaction cost modeling prevents overestimation of net returns
- Slippage Modeling: Realistic slippage assumptions account for market impact and liquidity constraints
- Position Sizing Constraints: Money management rules prevent unrealistic position sizes that could distort performance
These measures help ensure that backtest results provide a reliable indication of how a strategy might perform in live trading.
Section sources
- System.h
- backtest.h
Running large-scale backtests efficiently requires careful consideration of computational resources and optimization strategies. The Hikyuu backtesting engine provides several features to support efficient execution of extensive backtesting scenarios.
The engine supports parallel execution of multiple backtests through the analysisSystemList function, which uses the parallel_for_index utility to distribute computations across multiple CPU cores. This allows for simultaneous evaluation of multiple strategies or parameter combinations, significantly reducing total execution time.
Efficient memory management is critical for large-scale backtests. The engine employs several strategies to minimize memory usage:
- Data Streaming: Market data is loaded incrementally rather than all at once
- Object Reuse: Components are reused across multiple backtests when possible
- Memory Pooling: Frequently allocated objects are managed through memory pools to reduce allocation overhead
Additional performance optimizations include:
- Caching: Results of expensive calculations are cached when appropriate
- Algorithm Selection: Different algorithms are available for various performance vs. accuracy trade-offs
- Batch Processing: Related operations are grouped to minimize overhead
- I/O Optimization: Data access patterns are optimized for disk and memory performance
The engine is designed to scale from single-strategy backtests to large-scale strategy optimization and parameter sweeps. Users can configure the level of detail in the output to balance between comprehensive analysis and computational efficiency.
Section sources
- analysis_sys.cpp
- System.h
The Hikyuu backtesting engine provides a comprehensive and flexible framework for evaluating trading strategies through historical simulation. Its event-driven architecture ensures realistic execution of trading signals in chronological order, while its modular design allows for integration with various trading components such as signals, money management, and risk control modules. The engine supports multiple order types and incorporates realistic market conditions through sophisticated slippage modeling and transaction cost calculations.
The dual-layer architecture, with a high-performance C++ core and accessible Python interface, combines computational efficiency with development flexibility. This design enables both rapid strategy prototyping and efficient execution of large-scale backtests. The engine includes mechanisms to mitigate common biases such as look-ahead and survivorship bias, helping to ensure that backtest results provide a reliable indication of potential live trading performance.
For optimal results, users should carefully configure the backtest parameters, including execution mode, slippage model, and transaction costs, to reflect their intended trading style and market conditions. The engine's support for parallel execution and efficient memory management makes it suitable for both single-strategy evaluation and large-scale strategy optimization.