Skip to content

Commit bb152dc

Browse files
author
Serha
committed
chore: Release v3.0.29-beta - Add symbology parameter support
Added stype_in/stype_out parameters to GetRangeAsync and GetRangeToFileAsync methods. Features: - New overloads for GetRangeAsync with SType parameters (stypeIn, stypeOut) - New overloads for GetRangeToFileAsync with SType parameters - Full-stack implementation from C++ wrapper to C# API - Parent symbology support (e.g., ES.FUT, QQQ.OPT) - Proper exception handling via channel propagation Examples: - Added FuturesFilter.Example demonstrating parent symbology usage - Added ParentSymbolValidator for input validation - Added GetParentSymbols helper for discovering available symbols - Comprehensive README with symbology documentation Non-breaking change: Added as new overloads, existing code unchanged. Updated: - README.md: Version badge 3.0.28 → 3.0.29, Downloads 3.5K → 4.5K - Databento.Client.csproj: Version and release notes
1 parent 7d8dc74 commit bb152dc

File tree

19 files changed

+1257
-24
lines changed

19 files changed

+1257
-24
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# databento-dotnet
22

3-
[![NuGet](https://img.shields.io/badge/NuGet-v3.0.28--beta-blue)](https://www.nuget.org/packages/Databento.Client)
4-
[![Downloads](https://img.shields.io/badge/Downloads-3.5K-blue)](https://www.nuget.org/packages/Databento.Client)
3+
[![NuGet](https://img.shields.io/badge/NuGet-v3.0.29--beta-blue)](https://www.nuget.org/packages/Databento.Client)
4+
[![Downloads](https://img.shields.io/badge/Downloads-4.5K-blue)](https://www.nuget.org/packages/Databento.Client)
55
[![GitHub Release](https://img.shields.io/github/v/release/Alparse/databento-dotnet?include_prereleases&label=Release)](https://github.com/Alparse/databento-dotnet/releases)
66
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
77

databento-dotnet.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllSymbolsInstrumentClass.E
8686
EndProject
8787
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Comprehensive.Example", "Comprehensive.Example\Comprehensive.Example.csproj", "{28AF8097-C8C2-4918-B9F5-24AA24126442}"
8888
EndProject
89+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InstrumentDefinitionDecoder.Example", "examples\InstrumentDefinitionDecoder.Example\InstrumentDefinitionDecoder.Example.csproj", "{4FD17598-D68E-4820-8902-B9D226EC4061}"
90+
EndProject
91+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuturesFilter.Example", "examples\FuturesFilter.Example\FuturesFilter.Example.csproj", "{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}"
92+
EndProject
8993
Global
9094
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9195
Debug|Any CPU = Debug|Any CPU
@@ -528,6 +532,30 @@ Global
528532
{28AF8097-C8C2-4918-B9F5-24AA24126442}.Release|x64.Build.0 = Release|Any CPU
529533
{28AF8097-C8C2-4918-B9F5-24AA24126442}.Release|x86.ActiveCfg = Release|Any CPU
530534
{28AF8097-C8C2-4918-B9F5-24AA24126442}.Release|x86.Build.0 = Release|Any CPU
535+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
536+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Debug|Any CPU.Build.0 = Debug|Any CPU
537+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Debug|x64.ActiveCfg = Debug|Any CPU
538+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Debug|x64.Build.0 = Debug|Any CPU
539+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Debug|x86.ActiveCfg = Debug|Any CPU
540+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Debug|x86.Build.0 = Debug|Any CPU
541+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Release|Any CPU.ActiveCfg = Release|Any CPU
542+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Release|Any CPU.Build.0 = Release|Any CPU
543+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Release|x64.ActiveCfg = Release|Any CPU
544+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Release|x64.Build.0 = Release|Any CPU
545+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Release|x86.ActiveCfg = Release|Any CPU
546+
{4FD17598-D68E-4820-8902-B9D226EC4061}.Release|x86.Build.0 = Release|Any CPU
547+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
548+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
549+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Debug|x64.ActiveCfg = Debug|Any CPU
550+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Debug|x64.Build.0 = Debug|Any CPU
551+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Debug|x86.ActiveCfg = Debug|Any CPU
552+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Debug|x86.Build.0 = Debug|Any CPU
553+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
554+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Release|Any CPU.Build.0 = Release|Any CPU
555+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Release|x64.ActiveCfg = Release|Any CPU
556+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Release|x64.Build.0 = Release|Any CPU
557+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Release|x86.ActiveCfg = Release|Any CPU
558+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B}.Release|x86.Build.0 = Release|Any CPU
531559
EndGlobalSection
532560
GlobalSection(SolutionProperties) = preSolution
533561
HideSolutionNode = FALSE
@@ -565,6 +593,8 @@ Global
565593
{9A17EFEF-5061-4FF1-B031-5547B4978FD6} = {0CCA0EB9-9C5B-22E1-9E22-11B1D92CD054}
566594
{5C2D6E22-D0DC-4A95-A66B-5499B852A4BB} = {0CCA0EB9-9C5B-22E1-9E22-11B1D92CD054}
567595
{28AF8097-C8C2-4918-B9F5-24AA24126442} = {0CCA0EB9-9C5B-22E1-9E22-11B1D92CD054}
596+
{4FD17598-D68E-4820-8902-B9D226EC4061} = {0CCA0EB9-9C5B-22E1-9E22-11B1D92CD054}
597+
{A0F28F3B-6E4D-4C1E-B8A9-7D9E1F3A4C5B} = {0CCA0EB9-9C5B-22E1-9E22-11B1D92CD054}
568598
EndGlobalSection
569599
GlobalSection(ExtensibilityGlobals) = postSolution
570600
SolutionGuid = {0CBE475C-0102-41EF-96A2-1DBE0DF6F2E1}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\Databento.Client\Databento.Client.csproj" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using Databento.Client.Builders;
2+
using Databento.Client.Historical;
3+
using Databento.Client.Models;
4+
5+
namespace FuturesFilter.Example;
6+
7+
/// <summary>
8+
/// Example showing how to get a list of all parent symbols for a dataset
9+
/// </summary>
10+
public static class GetParentSymbols
11+
{
12+
public static async Task<HashSet<string>> GetAllParentsAsync(
13+
IHistoricalClient client,
14+
string dataset,
15+
DateTimeOffset date)
16+
{
17+
var parentSymbols = new HashSet<string>();
18+
19+
Console.WriteLine($"Fetching all instrument definitions for {dataset} on {date:yyyy-MM-dd}...");
20+
21+
// Query ALL_SYMBOLS with definition schema to get all instruments
22+
await foreach (var record in client.GetRangeAsync(
23+
dataset: dataset,
24+
schema: Schema.Definition,
25+
symbols: new[] { "ALL_SYMBOLS" },
26+
startTime: date,
27+
endTime: date.AddDays(1)))
28+
{
29+
if (record is InstrumentDefMessage def && !string.IsNullOrEmpty(def.Asset))
30+
{
31+
// Add parent symbols based on instrument class
32+
switch (def.InstrumentClass)
33+
{
34+
case InstrumentClass.Future:
35+
case InstrumentClass.FutureSpread:
36+
parentSymbols.Add($"{def.Asset}.FUT");
37+
break;
38+
39+
case InstrumentClass.Call:
40+
case InstrumentClass.Put:
41+
case InstrumentClass.OptionSpread:
42+
parentSymbols.Add($"{def.Asset}.OPT");
43+
break;
44+
}
45+
}
46+
}
47+
48+
return parentSymbols;
49+
}
50+
51+
public static async Task RunExampleAsync()
52+
{
53+
var apiKey = Environment.GetEnvironmentVariable("DATABENTO_API_KEY");
54+
if (string.IsNullOrEmpty(apiKey))
55+
{
56+
Console.WriteLine("ERROR: DATABENTO_API_KEY environment variable not set");
57+
return;
58+
}
59+
60+
await using var client = new HistoricalClientBuilder()
61+
.WithApiKey(apiKey)
62+
.Build();
63+
64+
// Example: Get all parent symbols for OPRA.PILLAR (options)
65+
var date = new DateTimeOffset(2024, 11, 1, 0, 0, 0, TimeSpan.Zero);
66+
var parents = await GetAllParentsAsync(client, "OPRA.PILLAR", date);
67+
68+
Console.WriteLine($"\nFound {parents.Count} unique parent symbols:");
69+
foreach (var parent in parents.OrderBy(p => p).Take(20))
70+
{
71+
Console.WriteLine($" {parent}");
72+
}
73+
74+
if (parents.Count > 20)
75+
{
76+
Console.WriteLine($" ... and {parents.Count - 20} more");
77+
}
78+
79+
// Example: Get futures parent symbols for GLBX.MDP3
80+
Console.WriteLine("\n" + new string('-', 50));
81+
var futuresParents = await GetAllParentsAsync(client, "GLBX.MDP3", date);
82+
83+
Console.WriteLine($"\nFound {futuresParents.Count} unique futures parent symbols:");
84+
foreach (var parent in futuresParents.OrderBy(p => p).Take(20))
85+
{
86+
Console.WriteLine($" {parent}");
87+
}
88+
89+
if (futuresParents.Count > 20)
90+
{
91+
Console.WriteLine($" ... and {futuresParents.Count - 20} more");
92+
}
93+
}
94+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System.Text.RegularExpressions;
2+
3+
namespace FuturesFilter.Example;
4+
5+
/// <summary>
6+
/// Validates parent symbology format before making API calls
7+
/// </summary>
8+
public static partial class ParentSymbolValidator
9+
{
10+
// Parent symbol format: [ROOT].FUT, [ROOT].OPT, or [ROOT].SPOT
11+
[GeneratedRegex(@"^[A-Z0-9]+\.(FUT|OPT|SPOT)$", RegexOptions.Compiled)]
12+
private static partial Regex ParentSymbolRegex();
13+
14+
/// <summary>
15+
/// Validates a parent symbol format
16+
/// </summary>
17+
/// <param name="symbol">The symbol to validate</param>
18+
/// <returns>True if valid, false otherwise</returns>
19+
public static bool IsValidParentSymbol(string symbol)
20+
{
21+
if (string.IsNullOrWhiteSpace(symbol))
22+
return false;
23+
24+
return ParentSymbolRegex().IsMatch(symbol);
25+
}
26+
27+
/// <summary>
28+
/// Validates a parent symbol and throws if invalid
29+
/// </summary>
30+
/// <param name="symbol">The symbol to validate</param>
31+
/// <exception cref="ArgumentException">Thrown if symbol format is invalid</exception>
32+
public static void ValidateParentSymbol(string symbol)
33+
{
34+
if (!IsValidParentSymbol(symbol))
35+
{
36+
throw new ArgumentException(
37+
$"Invalid parent symbol format: '{symbol}'. " +
38+
$"Expected format: '[ROOT].FUT', '[ROOT].OPT', or '[ROOT].SPOT'. " +
39+
$"Examples: ES.FUT, QQQ.OPT, BTCUSD.SPOT",
40+
nameof(symbol));
41+
}
42+
}
43+
44+
/// <summary>
45+
/// Validates multiple parent symbols
46+
/// </summary>
47+
public static void ValidateParentSymbols(IEnumerable<string> symbols)
48+
{
49+
foreach (var symbol in symbols)
50+
{
51+
ValidateParentSymbol(symbol);
52+
}
53+
}
54+
55+
/// <summary>
56+
/// Gets the root and type from a parent symbol
57+
/// </summary>
58+
/// <param name="symbol">Parent symbol (e.g., "ES.FUT")</param>
59+
/// <returns>Tuple of (root, type) or null if invalid</returns>
60+
public static (string Root, string Type)? ParseParentSymbol(string symbol)
61+
{
62+
if (!IsValidParentSymbol(symbol))
63+
return null;
64+
65+
var parts = symbol.Split('.');
66+
return (parts[0], parts[1]);
67+
}
68+
69+
/// <summary>
70+
/// Suggests corrections for common typos in parent symbols
71+
/// </summary>
72+
public static string? SuggestCorrection(string invalidSymbol)
73+
{
74+
if (string.IsNullOrWhiteSpace(invalidSymbol))
75+
return null;
76+
77+
// Common typos
78+
var suggestions = new Dictionary<string, string>
79+
{
80+
{ "FU4T", "FUT" },
81+
{ "FU7", "FUT" },
82+
{ "0PT", "OPT" },
83+
{ "OP7", "OPT" },
84+
{ "SP0T", "SPOT" },
85+
{ "FUTURES", "FUT" },
86+
{ "FUTURE", "FUT" },
87+
{ "OPTION", "OPT" },
88+
{ "OPTIONS", "OPT" },
89+
};
90+
91+
var parts = invalidSymbol.Split('.');
92+
if (parts.Length != 2)
93+
return null;
94+
95+
var root = parts[0];
96+
var type = parts[1].ToUpperInvariant();
97+
98+
// Try to find a suggestion for the type
99+
if (suggestions.TryGetValue(type, out var correctedType))
100+
{
101+
return $"{root}.{correctedType}";
102+
}
103+
104+
// Check if it's close to a valid type
105+
if (type.Contains("FUT") || type.Contains("FU"))
106+
return $"{root}.FUT";
107+
if (type.Contains("OPT") || type.Contains("OP"))
108+
return $"{root}.OPT";
109+
if (type.Contains("SPOT") || type.Contains("SP"))
110+
return $"{root}.SPOT";
111+
112+
return null;
113+
}
114+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using Databento.Client.Builders;
2+
using Databento.Client.Historical;
3+
using Databento.Client.Models;
4+
using Databento.Client.Models.Symbology;
5+
using FuturesFilter.Example;
6+
7+
// Uncomment this line to discover all available parent symbols
8+
//await GetParentSymbols.RunExampleAsync();
9+
//return 0;
10+
11+
Console.WriteLine("=== Databento Futures Filter Example (SType.Parent) ===\n");
12+
Console.WriteLine("This example demonstrates filtering for futures only using parent symbology.");
13+
Console.WriteLine("We'll query ES.FUT (all E-mini S&P 500 futures) from GLBX.MDP3\n");
14+
15+
// Get API key from environment variable
16+
var apiKey = Environment.GetEnvironmentVariable("DATABENTO_API_KEY");
17+
if (string.IsNullOrEmpty(apiKey))
18+
{
19+
Console.WriteLine("ERROR: DATABENTO_API_KEY environment variable not set");
20+
return 1;
21+
}
22+
23+
Console.WriteLine($"✓ API key found: {apiKey.Substring(0, 8)}... (masked)\n");
24+
25+
try
26+
{
27+
// Create historical client
28+
await using var client = new HistoricalClientBuilder()
29+
.WithApiKey(apiKey)
30+
.Build();
31+
32+
Console.WriteLine("✓ Created Historical client\n");
33+
34+
// Define time range (using a historical date to ensure data availability)
35+
var startTime = new DateTimeOffset(2024, 11, 1, 13, 30, 0, TimeSpan.FromHours(-5)); // 1:30 PM EST
36+
var endTime = startTime.AddMinutes(5); // 5 minutes of data
37+
38+
Console.WriteLine($"Query Parameters:");
39+
Console.WriteLine($" Dataset: GLBX.MDP3");
40+
Console.WriteLine($" Schema: Trades");
41+
Console.WriteLine($" Symbol: ES.FUT (all E-mini S&P 500 futures)");
42+
Console.WriteLine($" SType In: Parent");
43+
Console.WriteLine($" SType Out: InstrumentId (required by GLBX.MDP3)");
44+
Console.WriteLine($" Start Time: {startTime:yyyy-MM-dd HH:mm:ss zzz}");
45+
Console.WriteLine($" End Time: {endTime:yyyy-MM-dd HH:mm:ss zzz}");
46+
Console.WriteLine($" Limit: 100 records\n");
47+
48+
Console.WriteLine("Fetching futures data...\n");
49+
50+
var recordCount = 0;
51+
var instrumentsSeen = new HashSet<uint>();
52+
53+
// Query using the new overload with SType.Parent
54+
// NOTE: For GLBX.MDP3, Parent → InstrumentId is the only supported output
55+
await foreach (var record in client.GetRangeAsync(
56+
dataset: "GLBX.MDP3",
57+
schema: Schema.Trades,
58+
symbols: new[] { "ES.FUT2" }, // Parent symbol for all ES futures
59+
startTime: startTime,
60+
endTime: endTime,
61+
stypeIn: SType.Parent, // Interpret "ES.FUT" as parent symbol
62+
stypeOut: SType.InstrumentId, // Return instrument IDs (required)
63+
limit: 100)) // Limit to 100 records for demo
64+
{
65+
recordCount++;
66+
67+
if (record is TradeMessage trade)
68+
{
69+
// Track unique instrument IDs
70+
instrumentsSeen.Add(trade.InstrumentId);
71+
72+
// Show first 10 trades
73+
if (recordCount <= 10)
74+
{
75+
Console.WriteLine($" Trade #{recordCount}: InstrumentId={trade.InstrumentId}, " +
76+
$"Price=${trade.PriceDecimal:F2}, " +
77+
$"Size={trade.Size}, " +
78+
$"Time={trade.Timestamp:HH:mm:ss.fff}");
79+
}
80+
}
81+
}
82+
83+
Console.WriteLine($"\n✓ Query completed successfully!");
84+
Console.WriteLine($"\nSummary:");
85+
Console.WriteLine($" Total records: {recordCount}");
86+
Console.WriteLine($" Unique ES instruments: {instrumentsSeen.Count}");
87+
Console.WriteLine($"\nNote: All returned data is from ES futures contracts only,");
88+
Console.WriteLine($" filtered by using SType.Parent with 'ES.FUT'");
89+
Console.WriteLine($"\nSupported GLBX.MDP3 combinations:");
90+
Console.WriteLine($" ✓ parent → instrument_id");
91+
Console.WriteLine($" ✓ continuous → instrument_id");
92+
Console.WriteLine($" ✓ raw_symbol → instrument_id");
93+
Console.WriteLine($" ✓ instrument_id → raw_symbol");
94+
Console.WriteLine($"\nSee: https://databento.com/docs/standards-and-conventions/symbology");
95+
96+
return 0;
97+
}
98+
catch (Exception ex)
99+
{
100+
Console.WriteLine($"\n✗ Error: {ex.Message}");
101+
Console.WriteLine($" Type: {ex.GetType().Name}");
102+
if (ex.InnerException != null)
103+
{
104+
Console.WriteLine($" Inner: {ex.InnerException.Message}");
105+
}
106+
return 1;
107+
}

0 commit comments

Comments
 (0)