A high-performance .NET client for Databento market data. Stream real-time data or query historical records with async/await and IAsyncEnumerable.
- Installation
- Quick Start
- Features
- Symbol Mapping
- Backtesting
- API Reference
- Building from Source
- Troubleshooting
- License
dotnet add package Databento.ClientRequirements: .NET 8.0 or .NET 9.0
Platforms: Windows x64 (NuGet package) | Linux/macOS (build from source)
# Windows
$env:DATABENTO_API_KEY="your-api-key"
# Linux/macOS
export DATABENTO_API_KEY="your-api-key"Get your API key at databento.com/portal/keys
using Databento.Client.Builders;
using Databento.Client.Models;
await using var client = new LiveClientBuilder()
.WithKeyFromEnv()
.WithAutoReconnect() // Auto-reconnect on failure
.Build();
// Live mode (requires market hours)
await client.SubscribeAsync("EQUS.MINI", Schema.Trades, new[] { "NVDA" });
// Intraday Replay mode - replays from most recent market open, then continues live:
// (only available within 24h of last market open)
// await client.SubscribeAsync("EQUS.MINI", Schema.Trades, new[] { "NVDA" },
// startTime: DateTimeOffset.MinValue);
await client.StartAsync();
await foreach (var record in client.StreamAsync())
{
if (record is TradeMessage trade)
Console.WriteLine($"{trade.InstrumentId}: ${trade.PriceDecimal} x {trade.Size}");
}using Databento.Client.Builders;
using Databento.Client.Models;
await using var client = new HistoricalClientBuilder()
.WithKeyFromEnv()
.Build();
var start = new DateTimeOffset(2025, 1, 15, 14, 30, 0, TimeSpan.Zero);
var end = start.AddHours(1);
await foreach (var record in client.GetRangeAsync(
"EQUS.MINI", Schema.Trades, new[] { "NVDA" }, start, end))
{
Console.WriteLine(record);
}| Feature | Description |
|---|---|
| Live Streaming | Real-time market data with async/await and IAsyncEnumerable |
| Historical Queries | Time-range queries with efficient streaming |
| Backtesting | Run strategies against historical data with identical code to live |
| Auto-Reconnect | Configurable retry policies with exponential backoff |
| All Record Types | Full support for all 20 DBN record types |
| Symbol Mapping | Resolve InstrumentId to ticker symbols |
| Reference Data | SecurityMaster, CorporateActions, AdjustmentFactors |
| High Performance | Built on databento-cpp with native P/Invoke |
MBO · MBP-1 · MBP-10 · TBBO · Trades · OHLCV-1S · OHLCV-1M · OHLCV-1H · OHLCV-1D · OHLCV-EOD · Definition · Statistics · Status · Imbalance · CMBP-1 · CBBO-1S · CBBO-1M · TCBBO · BBO-1S · BBO-1M
Records contain numeric InstrumentId values instead of ticker symbols. Resolve them as shown below.
using System.Collections.Concurrent;
using Databento.Client.Builders;
using Databento.Client.Models;
var symbolMap = new ConcurrentDictionary<uint, string>();
await using var client = new LiveClientBuilder()
.WithKeyFromEnv()
.Build();
client.DataReceived += (sender, e) =>
{
if (e.Record is SymbolMappingMessage mapping)
{
// Use STypeOutSymbol (NOT STypeInSymbol)
symbolMap[mapping.InstrumentId] = mapping.STypeOutSymbol;
Console.WriteLine($"Mapped {mapping.InstrumentId} to {mapping.STypeOutSymbol}");
return;
}
if (e.Record is TradeMessage trade)
{
var symbol = symbolMap.GetValueOrDefault(trade.InstrumentId, "UNKNOWN");
Console.WriteLine($"{symbol}: ${trade.PriceDecimal}");
}
};
await client.SubscribeAsync("EQUS.MINI", Schema.Trades, new[] { "NVDA", "AAPL" });
await client.StartAsync();
await foreach (var record in client.StreamAsync()) { }Important: Always use
STypeOutSymbolfor the actual ticker.STypeInSymbolcontains your subscription string (e.g., "ALL_SYMBOLS").
using System.Collections.Concurrent;
using Databento.Client.Builders;
using Databento.Client.Models;
var symbolMap = new ConcurrentDictionary<uint, string>();
await using var client = new LiveBlockingClientBuilder()
.WithKeyFromEnv()
.WithDataset("EQUS.MINI")
.Build();
await client.SubscribeAsync("EQUS.MINI", Schema.Trades, new[] { "NVDA", "AAPL" });
await client.StartAsync();
while (true)
{
var record = await client.NextRecordAsync(timeout: TimeSpan.FromSeconds(5));
if (record == null) break;
if (record is SymbolMappingMessage mapping)
{
symbolMap[mapping.InstrumentId] = mapping.STypeOutSymbol;
Console.WriteLine($"Mapped {mapping.InstrumentId} to {mapping.STypeOutSymbol}");
continue;
}
if (record is TradeMessage trade)
{
var symbol = symbolMap.GetValueOrDefault(trade.InstrumentId, "UNKNOWN");
Console.WriteLine($"{symbol}: ${trade.PriceDecimal}");
}
}Note: Historical API does not send SymbolMappingMessage. Use
SymbologyResolveAsync()first.
using Databento.Client.Builders;
using Databento.Client.Models;
await using var client = new HistoricalClientBuilder()
.WithKeyFromEnv()
.Build();
var symbols = new[] { "NVDA", "AAPL" };
var start = new DateTimeOffset(2025, 1, 15, 14, 30, 0, TimeSpan.Zero);
var end = start.AddHours(1);
// Step 1: Resolve symbols to instrument IDs BEFORE streaming
var queryDate = DateOnly.FromDateTime(start.Date);
var resolution = await client.SymbologyResolveAsync(
"EQUS.MINI", symbols, SType.RawSymbol, SType.InstrumentId,
queryDate, queryDate.AddDays(1));
var symbolMap = new Dictionary<uint, string>();
foreach (var (inputSymbol, intervals) in resolution.Mappings)
foreach (var interval in intervals)
if (uint.TryParse(interval.Symbol, out var instrumentId))
{
symbolMap[instrumentId] = inputSymbol;
Console.WriteLine($"Mapped {instrumentId} to {inputSymbol}");
}
// Step 2: Stream data using the pre-built symbol map
await foreach (var record in client.GetRangeAsync(
"EQUS.MINI", Schema.Trades, symbols, start, end))
{
if (record is TradeMessage trade)
{
var symbol = symbolMap.GetValueOrDefault(trade.InstrumentId, "UNKNOWN");
Console.WriteLine($"{symbol}: ${trade.PriceDecimal} x {trade.Size}");
}
}Run your trading strategies against historical data using the same code you use for live trading.
using Databento.Client.Builders;
using Databento.Client.Models;
var start = new DateTimeOffset(2025, 1, 15, 9, 30, 0, TimeSpan.FromHours(-5));
var end = start.AddHours(6.5); // Full trading day
await using var client = new BacktestingClientBuilder()
.WithKeyFromEnv()
.WithTimeRange(start, end)
.WithDiskCache() // Cache for repeated runs
.Build();
await client.SubscribeAsync("EQUS.MINI", Schema.Trades, new[] { "NVDA", "AAPL" });
await client.StartAsync();
await foreach (var record in client.StreamAsync())
{
if (record is TradeMessage trade)
Console.WriteLine($"{trade.Timestamp}: {trade.PriceDecimal}");
}// No API key needed - use pre-downloaded DBN files
await using var client = new BacktestingClientBuilder()
.WithFileSource("/path/to/historical_data.dbn")
.Build();
await client.StartAsync();
await foreach (var record in client.StreamAsync())
{
if (record is TradeMessage trade)
Console.WriteLine($"{trade.PriceDecimal}");
}// Your strategy works identically with live or backtest clients
async Task RunStrategy(ILiveClient client)
{
await foreach (var record in client.StreamAsync())
{
if (record is TradeMessage trade)
ProcessTrade(trade);
}
}
// Backtest mode
await using var backtestClient = new BacktestingClientBuilder()
.WithKeyFromEnv()
.WithTimeRange(start, end)
.Build();
await RunStrategy(backtestClient);
// Live mode - same strategy code!
await using var liveClient = new LiveClientBuilder()
.WithKeyFromEnv()
.Build();
await RunStrategy(liveClient);See Backtesting Reference for playback control, caching options, and complete examples.
await using var client = new LiveClientBuilder()
.WithApiKey(apiKey) // Or .WithKeyFromEnv()
.WithDataset("GLBX.MDP3") // Default dataset
.WithAutoReconnect() // Enable resilience
.Build();
// Subscribe and stream
await client.SubscribeAsync(dataset, schema, symbols);
await client.StartAsync();
await foreach (var record in client.StreamAsync()) { }
// Events
client.DataReceived += (s, e) => { };
client.ErrorOccurred += (s, e) => { };Pull-based API for explicit control over record retrieval:
await using var client = new LiveBlockingClientBuilder()
.WithKeyFromEnv()
.WithDataset("EQUS.MINI")
.Build();
await client.SubscribeAsync("EQUS.MINI", Schema.Trades, new[] { "NVDA" });
await client.StartAsync();
// Pull records one at a time
while (true)
{
var record = await client.NextRecordAsync(timeout: TimeSpan.FromSeconds(5));
if (record == null) break; // Timeout reached
Console.WriteLine(record);
}await using var client = new HistoricalClientBuilder()
.WithKeyFromEnv()
.Build();
await foreach (var record in client.GetRangeAsync(
dataset, schema, symbols, startTime, endTime)) { }var client = new ReferenceClientBuilder()
.WithKeyFromEnv() // Or .WithApiKey(apiKey)
.Build();
// Security master
var records = await client.SecurityMaster.GetLastAsync(
symbols: new[] { "NVDA" }, stypeIn: SType.RawSymbol);
// Adjustment factors
var adjustments = await client.AdjustmentFactors.GetRangeAsync(
start: DateTimeOffset.UtcNow.AddDays(-90), symbols: new[] { "NVDA" });
// Corporate actions
var actions = await client.CorporateActions.GetRangeAsync(
start: DateTimeOffset.UtcNow.AddYears(-1), symbols: new[] { "NVDA" });The client includes built-in resilience features for production deployments:
using Databento.Client.Builders;
using Databento.Client.Resilience;
await using var client = new LiveClientBuilder()
.WithKeyFromEnv()
.WithAutoReconnect() // Enable auto-reconnect
.WithRetryPolicy(RetryPolicy.Aggressive) // 5 retries, longer delays
.WithHeartbeatTimeout(TimeSpan.FromSeconds(60)) // Stale connection detection
.Build();| Policy | Max Retries | Initial Delay | Max Delay |
|---|---|---|---|
RetryPolicy.Default |
3 | 1s | 30s |
RetryPolicy.Aggressive |
5 | 1s | 60s |
RetryPolicy.None |
0 | - | - |
using Databento.Client.Builders;
using Databento.Client.Resilience;
var options = new ResilienceOptions
{
AutoReconnect = true,
OnReconnecting = (attempt, ex) => {
Console.WriteLine($"Reconnecting (attempt {attempt}): {ex.Message}");
return true; // Continue reconnecting
},
OnReconnected = (attempts) => Console.WriteLine($"Reconnected after {attempts} attempts"),
OnReconnectFailed = (ex) => Console.WriteLine($"Reconnect failed: {ex.Message}")
};
await using var client = new LiveClientBuilder()
.WithKeyFromEnv()
.WithResilienceOptions(options)
.Build();Note: The NuGet package includes pre-built native libraries for Windows x64 only. Linux and macOS users must build from source.
# Prerequisites: .NET 8 SDK, CMake 3.24+, Visual Studio 2019+
# Full build (native + .NET)
.\build\build-all.ps1 -Configuration Release
# .NET only (if native library already built)
dotnet build -c Release# Prerequisites
sudo apt-get update
sudo apt-get install -y cmake build-essential libssl-dev libzstd-dev
# Clone and build
git clone https://github.com/Alparse/databento-dotnet.git
cd databento-dotnet
# Build native library
./build/build-native.sh --configuration Release
# Build .NET
dotnet build -c Release
# Native library output: src/Databento.Interop/runtimes/linux-x64/native/libdatabento_native.so# Prerequisites
brew install cmake openssl@3 zstd
# Clone and build
git clone https://github.com/Alparse/databento-dotnet.git
cd databento-dotnet
# Build native library
./build/build-native.sh --configuration Release
# Build .NET
dotnet build -c Release
# Native library output: src/Databento.Interop/runtimes/osx-*/native/libdatabento_native.dylibdatabento-dotnet/
├── src/
│ ├── Databento.Client/ # High-level .NET API
│ ├── Databento.Interop/ # P/Invoke layer + runtimes/
│ └── Databento.Native/ # C++ wrapper (CMake)
├── examples/ # 25+ working examples
└── docs/ # Additional documentation
The NuGet package includes native libraries for Windows x64 only.
On Windows:
- Try
dotnet restore --force - Install VC++ 2022 Redistributable
On Linux/macOS:
- You must build from source - native libraries are not included in the NuGet package
// Enable logging for diagnostics
var client = new LiveClientBuilder()
.WithKeyFromEnv()
.WithLogger(loggerFactory.CreateLogger<ILiveClient>())
.Build();- AI Coding Agents Guide - Optimized reference for AI code agents
- API Reference - Quick-start guide with examples
- API Classification - Complete method signatures
- Databento Documentation
- Issue Tracker
Apache 2.0 - See LICENSE
Built on databento-cpp