Class TimeseriesClient
- Namespace
- DatabentoDotNet.Historical
- Assembly
- DatabentoDotNet.Historical.dll
The timeseries.* endpoints: the market data itself.
public sealed class TimeseriesClient
- Inheritance
-
TimeseriesClient
- Inherited Members
Remarks
Reached through Timeseries rather than constructed. Port of
upstream's TimeseriesClient (timeseries.rs:19-22).
Everything on this facade costs money, and nothing else in this library does. Every
metadata.* and symbology.* endpoint is discovery or a billing enquiry, answered
free. These two move data and are billed for the bytes they move. Call
GetCostAsync(MetadataQueryParams, CancellationToken) first — ToQuery() exists so
that pricing a request and sending it cannot drift apart, which is a thing upstream leaves to
the caller to get right by hand.
var request = new GetRangeParams { /* … */ };
var dollars = await client.Metadata.GetCostAsync(request.ToQuery(), ct);
if (dollars <= budget)
{
await using var data = await client.Timeseries.GetRangeAsync(request, ct);
}
Fields
BinaryMediaType
The Accept these requests carry. The only non-JSON request in the historical API —
upstream marks it with that same observation in a comment (timeseries.rs:141).
public const string BinaryMediaType = "application/octet-stream"
Field Value
Remarks
The server answers Content-Type: application/zstd regardless, which is not a
contradiction: this header says what the client will accept, not what it expects to be
called.
RequestCompression
The compression every request sends. Not a parameter either — it is what makes a
multi-gigabyte range a reasonable thing to ask for, and upstream hard-codes it identically
(timeseries.rs:131-134).
public const string RequestCompression = "zstd"
Field Value
RequestEncoding
The encoding every request sends. Not a parameter: this client returns a decoder, so
DBN is the only encoding it could ask for.
public const string RequestEncoding = "dbn"
Field Value
Methods
GetRangeAsync(GetRangeParams, CancellationToken)
Downloads a range of records and returns a reader over them. This costs money.
public Task<TimeseriesReader> GetRangeAsync(GetRangeParams parameters, CancellationToken cancellationToken = default)
Parameters
parametersGetRangeParamsWhat to download, over what range.
cancellationTokenCancellationTokenCancels the request and the download.
Returns
- Task<TimeseriesReader>
A reader over the records, positioned at the first.
Examples
// Price it first — metadata.get_cost is free and takes the same parameters through ToQuery().
decimal cost = await client.Metadata.GetCostAsync(request.ToQuery());
await using var reader = await client.Timeseries.GetRangeAsync(request);
await foreach (OwnedRecord record in reader.ReadRecordsAsync())
{
if (record.TryGet(out TradeMsg trade))
{
Console.WriteLine($"{DbnTime.ToInstant(trade.IndexTs)} {trade.Price} x {trade.Size}");
}
}
Remarks
Port of upstream's get_range (timeseries.rs:88-97) and its private
get_range_impl. Price it first with
GetCostAsync(MetadataQueryParams, CancellationToken), passing
ToQuery() — that way the request you priced is the request you
send.
An empty result is a stream, not an error. A range the dataset has no records for
answers 200 with a well-formed metadata block, no records, and
X-Warning: No data found for the request you submitted. — which
HistoricalClient logs. The returned stream yields nothing and throws nothing.
The response is chunked, with no Content-Length. Its size is not known until
it ends, which is why nothing here pre-sizes a buffer and why a connection dropped mid-body
surfaces from FillBufferAsync(CancellationToken) as an
IOException rather than as a clean end.
Exceptions
- ArgumentNullException
parametersis null.- DatabentoApiException
The API refused the request — an empty range is
422 data_time_range_start_on_or_after_end, though DateTimeRange refuses to build one in the first place.- DbnDecodeException
The response is not a valid DBN stream.
GetRangeToFileAsync(GetRangeParams, string, CancellationToken)
Downloads a range of records to a file, then returns a reader over that file. This costs money.
public Task<TimeseriesReader> GetRangeToFileAsync(GetRangeParams parameters, string path, CancellationToken cancellationToken = default)
Parameters
parametersGetRangeParamsWhat to download, over what range.
pathstringWhere to write the raw
.dbn.zstbody. Overwritten if it exists; created along with any missing parent directory.cancellationTokenCancellationTokenCancels the request, the download and the write.
Returns
- Task<TimeseriesReader>
A reader over the file just written, positioned at the first record.
Remarks
This does not port the way upstream writes it, and the difference is visible to
callers. Upstream decodes the response and re-encodes it to disk with
AsyncDbnEncoder (timeseries.rs:100-115), applying the upgrade policy on the
way through. This library has no record encoder and deliberately will not have one
(CLAUDE.md, "Testing"), so that route is closed — and it is also unnecessary. The response
body already is zstd-framed DBN: writing it to disk is a byte copy, with no decode,
no re-encode, and nothing to get wrong in between. The file that lands is bit-identical to
what the API served.
The consequence, stated rather than left to be discovered. Upstream's file holds
records at the upgraded version and is read back with AsIs; this one holds
them at the version the API sent, and UpgradePolicy applies
each time the file is read. A file written by this library and read by upstream's — or by a
later version of this one — gets that reader's upgrade behaviour rather than this writer's.
Ours is the more defensible of the two, since a cached response that is not what the server
sent is a cache that can lie, but it is a difference and not a detail.
The server names the file too, and it is ignored. The response carries
Content-Disposition: attachment; filename=…. path wins, as it does
upstream; the header is unused deliberately rather than unnoticed, because a caller who
named a path did not ask to be second-guessed by a remote host.
Exceptions
- ArgumentNullException
parametersis null.- ArgumentException
pathis null or empty.- DatabentoApiException
The API refused the request.
- IOException
The download or the write failed.
OpenFileAsync(string, VersionUpgradePolicy, CancellationToken)
Opens a .dbn.zst file written by GetRangeToFileAsync(GetRangeParams, string, CancellationToken).
public static Task<TimeseriesReader> OpenFileAsync(string path, VersionUpgradePolicy upgradePolicy = VersionUpgradePolicy.UpgradeToV3, CancellationToken cancellationToken = default)
Parameters
pathstringThe file to read.
upgradePolicyVersionUpgradePolicyHow to present records from an older DBN version.
cancellationTokenCancellationTokenCancels the metadata read.
Returns
- Task<TimeseriesReader>
A reader over the file, positioned at the first record.
Remarks
Separate from the download so a cached file can be re-read without one, which is most of the reason to write it to disk in the first place.
Exceptions
- ArgumentException
pathis null or empty.- DbnDecodeException
The file is not a valid DBN stream.