Backtest.Net
4.2.2
dotnet add package Backtest.Net --version 4.2.2
NuGet\Install-Package Backtest.Net -Version 4.2.2
<PackageReference Include="Backtest.Net" Version="4.2.2" />
<PackageVersion Include="Backtest.Net" Version="4.2.2" />
<PackageReference Include="Backtest.Net" />
paket add Backtest.Net --version 4.2.2
#r "nuget: Backtest.Net, 4.2.2"
#:package Backtest.Net@4.2.2
#addin nuget:?package=Backtest.Net&version=4.2.2
#tool nuget:?package=Backtest.Net&version=4.2.2
High-Performance Backtest.Net
A high-performance backtesting engine for algorithmic trading strategies in .NET.
Introduction
High-Performance Backtest.Net is a specialized backtesting library designed for quantitative trading applications. It processes multi-timeframe candlestick data across multiple symbols, simulating tick-by-tick strategy execution with proper warmup periods and OHLC handling.
Key Characteristics
- Multi-Symbol Support: Process multiple trading symbols in parallel
- Multi-Timeframe: Handle multiple timeframes per symbol with automatic synchronization
- Performance Optimized: 11 iteratively optimized engine versions, including an opt-in reusable-buffer engine for allocation-sensitive workloads
- Data Splitting: Intelligent data partitioning for memory-efficient large-scale backtests
- Cancellation Support: Graceful async cancellation with progress tracking
Features
| Feature | Description |
|---|---|
| EngineV10 | Stable optimized engine with Span-based iteration, binary search, and parallel processing |
| EngineV11 | Reusable feed-buffer engine designed to avoid per-tick feed graph allocations |
| SymbolDataSplitter | Partitions large datasets into memory-efficient chunks |
| Warmup Handling | Configurable warmup candle counts per timeframe |
| OHLC Simulation | Accurate current-candle OHLC handling during backtest |
| Progress Tracking | Real-time progress from 0-100% |
| SIMD Acceleration | Leverages SimdLinq for vectorized operations |
Performance Optimizations
The library implements focused performance techniques:
- Allocation-Aware Hot Paths:
EngineV11reuses feed buffers instead of cloning the strategy feed graph on every tick - Span-Based Iteration: Uses
Span<T>andCollectionsMarshal.AsSpanin tight loops where it is safe - Parallel Processing:
EngineV9andEngineV10useParallel.ForEachfor symbol-level work - Binary Search: O(log n) candlestick lookups
- Aggressive Inlining:
[MethodImpl(MethodImplOptions.AggressiveInlining)] - Sealed Classes: JIT optimization hints
Installation
Package Manager
dotnet add package Backtest.Net
PackageReference
<PackageReference Include="Backtest.Net" Version="4.2.0" />
Requires .NET 10.0 or later.
Quickstart
Basic Engine Usage
using Backtest.Net.Engines;
using Backtest.Net.SymbolsData;
using Backtest.Net.SymbolDataSplitters;
using Backtest.Net.Timeframes;
using Backtest.Net.Enums;
// 1. Prepare your symbol data (candlesticks per timeframe)
// Candlestick properties: OpenTime, Open, High, Low, Close, CloseTime, Volume
var symbolsData = new List<SymbolDataV2>
{
new SymbolDataV2
{
Symbol = "BTCUSDT",
Timeframes = new List<TimeframeV2>
{
new TimeframeV2
{
Timeframe = CandlestickInterval.M1,
Candlesticks = yourOneMinuteCandles
},
new TimeframeV2
{
Timeframe = CandlestickInterval.H1,
Candlesticks = yourOneHourCandles
}
}
}
};
// 2. Split data for efficient processing
var splitter = new SymbolDataSplitterV2(
daysPerSplit: 30,
warmupCandlesCount: 100,
backtestingStartDateTime: new DateTime(2024, 1, 1)
);
var splitData = await splitter.SplitAsyncV2(symbolsData);
// 3. Create and configure the engine
var engine = new EngineV10(
warmupCandlesCount: 100,
sortCandlesInDescOrder: false,
useFullCandleForCurrent: false
);
// 4. Set up your strategy callback
engine.OnTick = async (symbolData) =>
{
foreach (var symbol in symbolData)
{
var latestCandle = symbol.Timeframes[0].Candlesticks[^1];
// Your strategy logic here
Console.WriteLine($"{symbol.Symbol}: Close = {latestCandle.Close}");
}
};
// 5. Run the backtest
await engine.RunAsync(splitData);
Console.WriteLine($"Progress: {engine.GetProgress()}%");
With Cancellation Support
using var cts = new CancellationTokenSource();
engine.OnCancellationFinishedDelegate = () =>
{
Console.WriteLine("Backtest cancelled gracefully");
};
// Cancel after 30 seconds
cts.CancelAfter(TimeSpan.FromSeconds(30));
await engine.RunAsync(splitData, cts.Token);
Engine Versions
| Engine | Description | Use Case |
|---|---|---|
EngineV8 |
SymbolDataV2 support | Standard workloads |
EngineV9 |
Optimized OHLC handling | Memory-sensitive scenarios |
EngineV10 |
Full optimization suite with the existing per-tick feed contract | Safe default for new projects |
EngineV11 |
Reusable feed buffers and steady-state allocation reduction | Allocation-sensitive strategies that do not retain OnTick references |
Use EngineV10 when you want the safest compatibility profile. Use EngineV11 when your strategy only reads OnTick data during the callback and does not store references to the supplied array, symbols, timeframes, candle lists, or current-candle objects.
Integration with History-Vault.Net
This library works seamlessly with History-Vault.Net - a high-performance historical market data storage solution. Both libraries use identical data structures (SymbolDataV2, TimeframeV2, CandlestickV2) with the same properties, differing only in namespaces:
| Library | Namespace |
|---|---|
| Backtest.Net | Backtest.Net.SymbolsData, Backtest.Net.Timeframes, Backtest.Net.Candlesticks |
| History-Vault.Net | HistoryVault.Models |
Converting Between Types
The easiest way to convert between the two libraries' types is using JSON serialization:
using System.Text.Json;
using Backtest.Net.SymbolsData;
// Convert from History-Vault.Net to Backtest.Net
public static List<SymbolDataV2> ConvertFromHistoryVault(List<HistoryVault.Models.SymbolDataV2> historyVaultData)
{
var json = JsonSerializer.Serialize(historyVaultData);
return JsonSerializer.Deserialize<List<SymbolDataV2>>(json)!;
}
// Convert from Backtest.Net to History-Vault.Net
public static List<HistoryVault.Models.SymbolDataV2> ConvertToHistoryVault(List<SymbolDataV2> backtestData)
{
var json = JsonSerializer.Serialize(backtestData);
return JsonSerializer.Deserialize<List<HistoryVault.Models.SymbolDataV2>>(json)!;
}
Complete Workflow Example
using Backtest.Net.Engines;
using Backtest.Net.SymbolsData;
using Backtest.Net.SymbolDataSplitters;
using System.Text.Json;
using HistoryVault;
using HistoryVault.Configuration;
using HistoryVault.Models;
using HistoryVault.Storage;
// Configure the vault (paths are auto-detected based on OS and scope)
var options = new HistoryVaultOptions
{
DefaultScope = StorageScope.Local
};
await using var vault = new HistoryVaultStorage(options);
// Save candlestick data
var symbolData = new SymbolDataV2
{
Symbol = "BTCUSDT",
Timeframes = new List<TimeframeV2>
{
new TimeframeV2
{
Timeframe = CandlestickInterval.M1,
Candlesticks = candlesticks // Your candlestick list
}
}
};
// Load candlestick data
var loadOptions = LoadOptions.ForSymbol(
"BTCUSDT",
new DateTime(2025, 1, 1),
new DateTime(2025, 1, 31),
CandlestickInterval.M1
);
var historyData = await vault.LoadAsync(loadOptions);
// 2. Convert to Backtest.Net types via JSON
var json = JsonSerializer.Serialize(historyData);
var backtestData = JsonSerializer.Deserialize<List<SymbolDataV2>>(json)!;
// 3. Run backtest
var splitter = new SymbolDataSplitterV2(daysPerSplit: 30, warmupCandlesCount: 100);
var splitData = await splitter.SplitAsyncV2(backtestData);
var engine = new EngineV10(warmupCandlesCount: 100);
engine.OnTick = async (symbolData) =>
{
// Your strategy logic
};
await engine.RunAsync(splitData);
JSON conversion is the recommended approach as it cleanly handles namespace differences without requiring manual mapping or shared assemblies.
Benchmarks
Performance benchmarks run on Apple M3 Max with .NET 10.0, processing 4 million candlesticks (1 symbol × 4 timeframes × 1,000,000 candles each):
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|---|
| EngineV11Run | 84.82 ms | 0.539 ms | 0.478 ms | - | - | 1.62 KB |
Key findings:
- The benchmark resets mutated timeframe indexes before each engine run.
EngineV11is designed to avoid per-tick feed graph allocations after setup; the measured allocation reflects remaining setup/runtime overhead, not the old per-tick clone path.- Choose
EngineV10for broad compatibility andEngineV11for allocation-sensitive strategies that follow the borrowed-snapshot callback contract.
Benchmarks run with BenchmarkDotNet v0.15.8 on macOS Tahoe 26.2, Apple M3 Max, .NET 10.0
Development
Prerequisites
- .NET 10.0 SDK or later
- Git
Build
git clone https://github.com/islero/High-Performance-Backtest.Net.git
cd High-Performance-Backtest.Net
dotnet build
Test
dotnet test
Benchmark
cd benchmarks/Backtest.Net.Benchmarks
dotnet run -c Release
Format
dotnet format
Versioning & Releases
This project follows Semantic Versioning.
| Branch | Version Format | NuGet Feed |
|---|---|---|
master |
X.Y.Z (stable) |
nuget.org |
beta |
X.Y.Z-beta.N (prerelease) |
nuget.org |
Release Process
- Update version in
src/Backtest.Net/Backtest.Net.csproj - Create GitHub Release with tag
vX.Y.Z - CI automatically publishes to NuGet
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for guidelines.
License
This project is licensed under the GNU Lesser General Public License v3.0.
Acknowledgments
- SimdLinq - SIMD-accelerated LINQ operations
- BenchmarkDotNet - Performance benchmarking
<p align="center"> Built for the algorithmic trading community </p>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- SimdLinq (>= 1.3.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Added EngineV11 with reusable feed buffers and a borrowed-snapshot OnTick contract; added EngineV11 unit tests and benchmark coverage; corrected engine benchmark input reset so runs start from fresh indexes; updated README benchmark and engine guidance.