Table of Contents

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

string

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

string

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

string

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

parameters GetRangeParams

What to download, over what range.

cancellationToken CancellationToken

Cancels 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

parameters is 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

parameters GetRangeParams

What to download, over what range.

path string

Where to write the raw .dbn.zst body. Overwritten if it exists; created along with any missing parent directory.

cancellationToken CancellationToken

Cancels 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

parameters is null.

ArgumentException

path is null or empty.

DatabentoApiException

The API refused the request.

IOException

The download or the write failed.

OpenFileAsync(string, VersionUpgradePolicy, CancellationToken)

public static Task<TimeseriesReader> OpenFileAsync(string path, VersionUpgradePolicy upgradePolicy = VersionUpgradePolicy.UpgradeToV3, CancellationToken cancellationToken = default)

Parameters

path string

The file to read.

upgradePolicy VersionUpgradePolicy

How to present records from an older DBN version.

cancellationToken CancellationToken

Cancels 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

path is null or empty.

DbnDecodeException

The file is not a valid DBN stream.