thetadatadx/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(missing_docs)]
3//! # thetadatadx
4//!
5//! Native Rust SDK for [ThetaData](https://thetadata.us) market data.
6//! Historical data via ThetaData's MDDS service, real-time streaming via
7//! ThetaData's FPSS service, and bulk flat-file pulls — all through a single
8//! authenticated client, without a JVM, subprocess, or local proxy.
9//!
10//! Requires a valid ThetaData subscription.
11//!
12//! ## Quick start
13//!
14//! ```rust,no_run
15//! use thetadatadx::{Client, Credentials, DirectConfig};
16//! use thetadatadx::streaming::{StreamEvent, StreamData};
17//! use thetadatadx::streaming::Contract;
18//!
19//! # async fn doc() -> Result<(), thetadatadx::Error> {
20//! let creds = Credentials::from_file("creds.txt")?;
21//! let client = Client::connect(&creds, DirectConfig::production()).await?;
22//!
23//! // Market-data — every query endpoint on the `market-data` surface
24//! let ticks = client.market_data().stock_history_eod("AAPL", "20240101", "20240301").await?;
25//!
26//! // Real-time streaming — on the `stream` surface
27//! client.stream().start_streaming(|event: &StreamEvent| {
28//! if let StreamEvent::Data(StreamData::Trade { contract, price, size, .. }) = event {
29//! println!("Trade: {} @ {price} x {size}", contract.symbol);
30//! }
31//! })?;
32//! client.stream().subscribe(Contract::stock("AAPL").quote())?;
33//!
34//! // Bulk flat files — on the `flat_files` surface, decoded in memory
35//! let rows = client.flat_files().option_trade_quote("20240115").await?;
36//! # let _ = rows;
37//! # Ok(()) }
38//! ```
39//!
40//! For streaming-only workloads, build an [`streaming::StreamingClient`] directly
41//! and iterate events on the caller's thread:
42//!
43//! ```rust,no_run
44//! use thetadatadx::streaming::{StreamingClient, StreamEvent};
45//! use thetadatadx::{Credentials, DirectConfig};
46//! use thetadatadx::streaming::Contract;
47//!
48//! # fn doc() -> Result<(), thetadatadx::streaming::StreamError> {
49//! let creds = Credentials::new("user@example.com", "pw");
50//! let config = DirectConfig::production();
51//! let hosts = config.streaming_hosts();
52//!
53//! let client = StreamingClient::builder(&creds, hosts)
54//! .build()?;
55//!
56//! client.subscribe(Contract::stock("AAPL").quote())?;
57//!
58//! for event in &client {
59//! let _event: StreamEvent = event?;
60//! }
61//! # Ok(()) }
62//! ```
63//!
64//! `client.next_event()` blocks until the next event or terminal
65//! shutdown; `try_next_event` is the non-blocking variant;
66//! `poll_batch(FnMut)` and `for_each(FnMut)` are the closure-driven
67//! shapes.
68//!
69//! For market-data-only workloads, build a [`market_data::MarketDataClient`]
70//! directly and query endpoints on it:
71//!
72//! ```rust,no_run
73//! use thetadatadx::market_data::MarketDataClient;
74//! use thetadatadx::{Credentials, DirectConfig};
75//!
76//! # async fn doc() -> Result<(), thetadatadx::Error> {
77//! let creds = Credentials::from_file("creds.txt")?;
78//! let client = MarketDataClient::connect(&creds, DirectConfig::production()).await?;
79//!
80//! let eod = client.stock_history_eod("AAPL", "20240101", "20240301").await?;
81//! println!("{} EOD ticks", eod.len());
82//! # Ok(()) }
83//! ```
84//!
85//! ## Data delivery
86//!
87//! Historical data arrives over ThetaData's MDDS service; real-time
88//! ticks arrive over ThetaData's FPSS service. Both are decoded
89//! inside the crate — consumers see typed tick rows on the market-data side
90//! and a typed [`streaming::StreamEvent`] stream on the streaming side.
91
92// `wire_semantics.rs` is `#[path]`-shared between this library and the
93// `generate_sdk_surfaces` binary's code-generation tree. The binary sees
94// the library as the external `thetadatadx` crate, so the shared file
95// names the option-right parser through `thetadatadx::right` rather than
96// `crate::`. This self-alias lets the same `thetadatadx::` path resolve
97// inside the library build too.
98extern crate self as thetadatadx;
99
100// ─── Internal module tree ────────────────────────────────────────────────────
101
102pub mod auth;
103pub mod backoff;
104pub(crate) mod client;
105pub(crate) mod client_builder;
106pub mod columns;
107pub mod config;
108pub mod error;
109pub mod flatfiles;
110// The streaming implementation lives here, but `thetadatadx::streaming` is the
111// canonical public path for the streaming surface. `fpss` stays `pub` so the
112// `streaming` re-exports resolve through it, and is `#[doc(hidden)]` so the
113// vendor protocol name no longer fronts the rendered API. The set of items it
114// re-exports tracks the public surface and is not itself a stability contract.
115#[doc(hidden)]
116pub mod fpss;
117#[cfg(any(feature = "polars", feature = "arrow"))]
118#[cfg_attr(docsrs, doc(cfg(any(feature = "polars", feature = "arrow"))))]
119#[doc(hidden)]
120pub mod frames;
121pub(crate) mod lifecycle;
122
123// The `grpc` module hosts the transport infrastructure (Channel, ChannelPool,
124// Status, ServerStreaming). The user-facing path is
125// `MarketDataClient::for_each_chunk(ServerStreaming<..>)`; the remainder is
126// consumed by the SDK's own integration tests and benches.
127//
128// In shipped builds (default features) the module is `pub(crate)` so none
129// of its types appear in the SemVer commitment or in rendered rustdoc.
130// Errors flowing out of the transport layer are converted to the public
131// [`crate::Error`] type at the crate boundary — consumers pattern-match on
132// [`crate::Error`] only.
133//
134// The `__test-helpers` feature re-opens the module to integration tests and
135// bench harnesses that need to drive the raw `Channel` / `ChannelPool`
136// surface against synthetic frames. This feature is private and
137// unsupported for downstream consumers.
138#[cfg(not(feature = "__test-helpers"))]
139pub(crate) mod grpc;
140#[cfg(feature = "__test-helpers")]
141#[doc(hidden)]
142pub mod grpc;
143
144pub(crate) mod observability;
145
146// The binary-encoding data layer — tick types, enums, `Price`, the
147// FIT codec, Black-Scholes Greeks, and the condition / exchange /
148// sequence lookups. The crate root re-exports its public surface under
149// stable `thetadatadx::*` paths (see the re-export blocks below); the
150// `tdbe` name itself stays internal so the SDK ships as one crate with
151// one published package. Never widen to `pub` — that would resurface
152// the `tdbe` path consumers must not depend on.
153//
154// The layer carries the complete data-format API: some entry points
155// (the per-Greek Black-Scholes primitives, the FIT codec, the
156// canonical-JSON helpers, a handful of enum/error constructors) have no
157// caller inside a default-feature build — they are reached by the
158// `__internal` re-exports below (workspace tools and bindings) and by the
159// data-format benches. Enabling `__internal` makes those re-exports `pub`,
160// so dead-code analysis still covers the whole layer there; the
161// allow applies only to the narrower default build where the curated
162// public surface does not name them.
163#[cfg_attr(not(feature = "__internal"), allow(dead_code))]
164pub(crate) mod tdbe;
165
166pub mod util;
167
168// `mdds/` holds the macros, registry, validate, wire_semantics, and the
169// shared endpoint runtime (`endpoint_args`).
170//
171// In default-feature builds the module is `pub(crate)` — none of its types
172// appear in the SemVer commitment or in rendered rustdoc. The `__internal`
173// feature re-opens the module to workspace tools (`tools/server`,
174// `tools/mcp`) and bindings (`ffi`, `thetadatadx-py`, `thetadatadx-ts`) that
175// need direct access to the registry, decode pipeline, and endpoint runtime.
176#[cfg(not(feature = "__internal"))]
177pub(crate) mod mdds;
178#[cfg(feature = "__internal")]
179#[doc(hidden)]
180pub mod mdds;
181
182/// Shared endpoint runtime (`EndpointArgs`, `EndpointError`, `invoke_endpoint`).
183/// Re-exported from [`mdds::endpoint_args`] so existing `thetadatadx::endpoint::*`
184/// paths continue to resolve.
185///
186/// Only available when the `__internal` feature is enabled. NOT a stable public
187/// surface — for workspace tools and bindings only.
188#[cfg(feature = "__internal")]
189#[doc(hidden)]
190pub use mdds::endpoint_args as endpoint;
191
192/// Decode pipeline re-exported from `mdds::decode`.
193///
194/// `pub(crate)` in default builds — internal modules (`grpc/endpoints.rs`,
195/// `mdds/endpoints.rs`, `error.rs`) reference it as `crate::decode`. The
196/// `__internal` feature widens it to `pub` so workspace bindings can import
197/// `thetadatadx::decode::*` directly.
198#[cfg(feature = "__internal")]
199#[doc(hidden)]
200pub use mdds::decode;
201#[cfg(not(feature = "__internal"))]
202pub(crate) use mdds::decode;
203
204/// Generated protobuf types from `mdds.proto` (package `BetaEndpoints`).
205///
206/// Wire-internal: bindings and decode-fixture consumers reach the payload
207/// shapes via [`crate::wire`], which surfaces only the types those callers
208/// genuinely need.
209#[allow(clippy::pedantic)]
210pub(crate) mod proto {
211 // The gRPC client stubs are pre-generated and committed at
212 // `proto/beta_endpoints.snapshot.rs`, so a normal build needs no
213 // `protoc`. The snapshot is regenerated (and drift-checked against
214 // `proto/mdds.proto`) only under the `grpc-codegen` feature; see
215 // `build_support/grpc/` and `proto/MAINTENANCE.md`.
216 include!(concat!(
217 env!("CARGO_MANIFEST_DIR"),
218 "/proto/beta_endpoints.snapshot.rs"
219 ));
220}
221
222/// Wire-payload re-exports for offline-decode callers.
223///
224/// SDK bindings that recover endpoint outputs from recorded byte streams
225/// need the protobuf payload types re-exported here. The generated `proto`
226/// module that hosts them is otherwise wire-internal — this re-export is
227/// the supported surface for that use case.
228///
229/// Only available when the `__internal` feature is enabled. NOT a stable public
230/// surface — for workspace tools and bindings only.
231#[cfg(feature = "__internal")]
232#[doc(hidden)]
233pub mod wire {
234 pub use super::proto::{
235 data_value, CompressionAlgo, CompressionDescription, DataTable, DataValue, DataValueList,
236 Price, ResponseData, TimeZone, ZonedDateTime,
237 };
238
239 /// Request proto types re-exported behind the `__test-helpers` feature so
240 /// integration tests can decode captured outbound wire bytes and assert
241 /// field-level content. Symbol stays `pub(crate)` in shipped builds.
242 #[cfg(feature = "__test-helpers")]
243 #[doc(hidden)]
244 pub mod test_requests {
245 pub use crate::proto::{
246 OptionHistoryGreeksFirstOrderRequest, OptionHistoryGreeksImpliedVolatilityRequest,
247 };
248
249 /// Request-side protos for `GetStockHistoryEod`, re-exported so
250 /// the transport-comparison bench can issue the identical wire
251 /// request through an external client stack and through the
252 /// in-house transport.
253 pub use crate::proto::{
254 AuthToken, QueryInfo, StockHistoryEodRequest, StockHistoryEodRequestQuery,
255 };
256 }
257}
258
259// ─── Doc-hidden internals reachable by tools/bindings ────────────────────────
260//
261// All symbols below are gated on `__internal`. In default-feature builds the
262// `mdds` module is `pub(crate)` so none of these paths are reachable from
263// outside the crate. Enabling `__internal` re-opens the module and these
264// re-exports so workspace tools and bindings can reference the registry,
265// decode pipeline, and endpoint runtime without patching the module tree.
266
267#[cfg(feature = "__internal")]
268#[doc(hidden)]
269pub use lifecycle::DispatcherSession;
270#[cfg(feature = "__internal")]
271#[doc(hidden)]
272pub use mdds::endpoint_args::{EndpointArgValue, EndpointArgs, EndpointError, EndpointOutput};
273#[cfg(feature = "__internal")]
274#[doc(hidden)]
275pub use mdds::registry::{
276 by_category, find, param_type_to_json_type, EndpointMeta, ParamMeta, ParamType, ReturnType,
277 CATEGORIES, ENDPOINTS,
278};
279// ─── Curated public client surface ───────────────────────────────────────────
280
281pub use auth::Credentials;
282pub use backoff::JitterMode;
283pub use client::{Client, ConnectionStatus, FlatFiles, StreamSurface, SubscriptionInfo};
284pub use client_builder::ClientBuilder;
285pub use config::{
286 BulkFetchPolicy, DirectConfig, FlatFilesConfig, MarketDataEnvironment, ReconnectAttemptClass,
287 ReconnectAttemptLimits, ReconnectPolicy, RetryPolicy, RuntimeConfig, StreamingEnvironment,
288 WaitMode,
289};
290pub use error::{
291 AuthErrorKind, ConfigErrorKind, DecodeErrorKind, DecompressErrorKind, Error, GrpcStatusKind,
292 StreamErrorKind, TransportErrorKind,
293};
294
295// ─── Real-time streaming ─────────────────────────────────────────────────────
296// The canonical streaming surface lives in the [`streaming`] module: build a
297// client with [`streaming::StreamingClient::builder`], subscribe via
298// [`streaming::Contract`], then drain with `next_event` / `poll_batch` / the
299// `Iterator` impl.
300
301/// Outcome of a single [`streaming::StreamingClient::poll_batch`] call, re-exported
302/// at the crate root for callers that drive the batch loop directly.
303pub use fpss::PollOutcome;
304
305/// Real-time streaming consumer surface.
306///
307/// This is the canonical module for the streaming client, its events, and the
308/// subscription-building types. Build a client with
309/// [`StreamingClient::builder`](crate::streaming::StreamingClient::builder),
310/// subscribe via [`Contract`](crate::streaming::Contract), then drain events
311/// with `next_event` / `poll_batch` / `for_each` or the `Iterator` impl on
312/// `&StreamingClient`.
313pub mod streaming {
314 pub use crate::fpss::protocol::{
315 Contract, FullSubscriptionKind, OptionLeg, SecTypeExt, Subscription, SubscriptionKind,
316 };
317 pub use crate::fpss::{
318 PollOutcome, StreamControl, StreamData, StreamError, StreamEvent, StreamingClient,
319 StreamingClientBuilder,
320 };
321
322 /// Pull-based columnar delivery — read the live stream as Apache Arrow
323 /// `RecordBatch` values instead of per-event callbacks.
324 ///
325 /// [`RecordBatchStream`] is a sibling to the per-event callback
326 /// registered through `client.stream().start_streaming(..)`: the same
327 /// subscriptions feed it, but market-data events arrive in columnar
328 /// batches under a fixed schema rather than one event at a time. Open it
329 /// with `client.stream().batches()`, tune `batch_size` / `linger` /
330 /// `backpressure` on the returned [`BatchReaderBuilder`], then pull
331 /// batches with the [`futures_core::Stream`] impl or `next_blocking`.
332 /// See [`crate::fpss::batch_schema::stream_batch_schema`] for the layout.
333 #[cfg(feature = "arrow")]
334 #[cfg_attr(docsrs, doc(cfg(feature = "arrow")))]
335 pub use crate::fpss::batch_reader::{
336 Backpressure, BatchReaderBuilder, RecordBatchStream, DEFAULT_BATCH_SIZE, DEFAULT_LINGER,
337 DEFAULT_QUEUE_DEPTH,
338 };
339
340 /// Estimated Arrow IPC stream size, in bytes, for a single batch of
341 /// `num_rows` rows under the fixed streaming schema. A buffer-sizing hint
342 /// for the binding batch encoders so they seed their output `Vec` from the
343 /// USED size (keyed on `num_rows`) rather than the builder's preallocated
344 /// column capacity. Hidden: an internal hint shared with the bindings, not
345 /// part of the supported surface.
346 #[cfg(feature = "arrow")]
347 #[doc(hidden)]
348 pub use crate::fpss::batch_schema::estimated_ipc_len;
349
350 /// The fixed streaming-batch Arrow schema. Hidden: shared with the binding
351 /// layers (and their tests) so they describe a streaming batch without
352 /// reconstructing the column list.
353 #[cfg(feature = "arrow")]
354 #[doc(hidden)]
355 pub use crate::fpss::batch_schema::stream_batch_schema;
356}
357
358// ─── Market-data queries ──────────────────────────────────────────────────────
359// The canonical market-data surface lives in the [`market_data`] module: build a
360// standalone [`market_data::MarketDataClient`], or reach the same query surface
361// through [`Client::market_data`] on the unified client.
362
363/// Standalone market-data query client.
364///
365/// `MarketDataClient` and its [`SubscriptionTier`] are also re-exported at the
366/// crate root so both `thetadatadx::MarketDataClient` and
367/// `thetadatadx::market_data::MarketDataClient` resolve.
368pub use mdds::{MarketDataClient, SubscriptionTier};
369
370/// Bulk-fetch shard planning (manual mode): describe a history query as a
371/// [`ShardQuery`], obtain the balanced [`ShardPlan`] via
372/// [`MarketDataClient::bulk_fetch_plan`], and run the [`ShardBand`]
373/// sub-requests under your own concurrency. The automatic path
374/// ([`BulkFetchPolicy::Auto`]) uses exactly these plans.
375pub use mdds::shard::{ShardBand, ShardPlan, ShardQuery};
376
377/// Date-range split math for history requests beyond the server's 365-day
378/// cap: [`split_date_range`] divides an inclusive `(start, end)` span into
379/// contiguous server-accepted chunks. Also exposed to Python as
380/// `thetadatadx.split_date_range`.
381pub use mdds::shard::{split_date_range, ChunkError};
382
383/// Market-data query consumer surface.
384///
385/// `thetadatadx::market_data` is the canonical path for the standalone
386/// market-data query client, the counterpart to [`streaming`].
387/// Build a [`MarketDataClient`] directly, or reach the same query surface
388/// through [`Client::market_data`](crate::Client::market_data) on the unified
389/// client.
390pub mod market_data {
391 pub use crate::mdds::{MarketDataClient, SubscriptionTier};
392}
393
394// ─── Flat-file bulk pulls ─────────────────────────────────────────────────────
395
396/// Bulk flat-file downloads from ThetaData's flat-file distribution.
397///
398/// For the accessor shape that matches the Python, TypeScript, and C++
399/// bindings, use [`Client::flat_files`] to reach the same surface through
400/// a [`FlatFiles`] view (`client.flat_files().option_trade_quote(date)`).
401/// The free functions below are the lower-level API: use
402/// [`flatfile_request`] to write directly to disk, or
403/// [`flatfile_request_decoded`] to materialise rows in memory.
404pub mod flatfiles_api {
405 pub use crate::client::FlatFiles;
406 pub use crate::flatfiles::{
407 flatfile_request, flatfile_request_decoded, flatfile_request_raw, FlatFileFormat,
408 FlatFileRow, FlatFileValue, FlatFilesUnavailableReason, ReqType as FlatFileReqType,
409 SecType as FlatFileSecType,
410 };
411}
412pub use flatfiles_api::*;
413
414// ─── Tick types ───────────────────────────────────────────────────────────────
415
416/// Per-response wire column set carried alongside decoded rows so the
417/// DataFrame builders project to the terminal's exact columns, the buffered
418/// [`Ticks`] return that carries it, and the trait a tick type implements to
419/// compute the set from a wire header list.
420pub use crate::columns::{ColumnPresence, Ticks, WireColumns};
421
422pub use crate::tdbe::types::tick::{
423 CalendarDay, EodTick, GreeksAllTick, GreeksEodTick, GreeksFirstOrderTick,
424 GreeksSecondOrderTick, GreeksThirdOrderTick, IndexPriceAtTimeTick, InterestRateTick, IvTick,
425 MarketValueTick, OhlcTick, OpenInterestTick, OptionContract, PriceTick, QuoteTick,
426 TradeGreeksAllTick, TradeGreeksFirstOrderTick, TradeGreeksImpliedVolatilityTick,
427 TradeGreeksSecondOrderTick, TradeGreeksThirdOrderTick, TradeQuoteTick, TradeTick,
428};
429
430// ─── Enums ────────────────────────────────────────────────────────────────────
431
432pub use crate::tdbe::types::enums::{
433 Interval, RateType, RemoveReason, RequestType, Right, SecType, StreamMsgType,
434 StreamResponseType, Venue, Version,
435};
436/// Variable-precision fixed-point price encoding (`value` / `price_type`
437/// mantissa-and-exponent pair) and its supporting types: the validated
438/// `PriceType` exponent, the `PriceError` its fallible constructor returns,
439/// and the `MAX_PRICE_TYPE` bound that constructor validates against.
440///
441/// This is a wire-encoding detail: a client receives decoded prices
442/// (`f64` dollars on the tick rows) and never sees, sets, or reasons about
443/// the raw `(value, price_type)` pair. The encoding therefore stays off the
444/// public API. Only available when the `__internal` feature is enabled, for
445/// workspace tools, bindings, and the data-format benches. NOT a stable
446/// public surface — external crates MUST NOT enable that feature.
447#[cfg(feature = "__internal")]
448#[doc(hidden)]
449pub use crate::tdbe::types::price::{Price, PriceError, PriceType, MAX_PRICE_TYPE};
450
451// ─── Option-right parsing ────────────────────────────────────────────────────
452
453/// Canonical parser for the option `right` parameter.
454///
455/// Accepts `call`/`put`/`both`/`C`/`P`/`*` (case-insensitive) at every
456/// user-facing input boundary. Use [`parse_right`] where the wildcard is
457/// meaningful and [`parse_right_strict`] where a single side is required.
458pub mod right {
459 pub use crate::tdbe::right::{parse_right, parse_right_strict, ParsedRight, RightError};
460}
461// Crate-root re-export of the option-right parser.
462pub use right::{parse_right, parse_right_strict, ParsedRight, RightError};
463
464// ─── Utility modules ─────────────────────────────────────────────────────────
465
466/// Auxiliary lookup tables.
467///
468/// - [`utils::conditions`] — condition-code descriptions
469/// - [`utils::exchange`] — exchange-code to name mapping
470/// - [`utils::sequences`] — sequence-number utilities
471pub mod utils {
472 pub use crate::tdbe::{conditions, exchange, sequences};
473}
474
475// ─── Doc-hidden data-layer internals reachable by tools/bindings/benches ──────
476//
477// The shipped public surface above is curated. Workspace tools
478// (`tools/server`, `tools/mcp`), bindings (`ffi`,
479// `thetadatadx-py`, `thetadatadx-ts`), and the bench harnesses reach a few
480// more data-layer items: the DST-aware epoch math, the canonical JSON
481// finite-or-null sanitiser, the FIT codec, and the calendar-status
482// enum the generated tick constructors validate against. These are gated
483// on `__internal` so they stay out of the SemVer commitment and rendered
484// rustdoc; external crates MUST NOT enable that feature. The `tdbe` name
485// never appears in the path — these resolve as `thetadatadx::time`,
486// `thetadatadx::json_canon`, `thetadatadx::codec`, and
487// `thetadatadx::CalendarStatus`.
488
489/// DST-aware epoch / civil-date math (`date_ms_to_epoch_ms`,
490/// `is_valid_yyyymmdd`, `timestamp_to_date`, ...).
491///
492/// Only available when the `__internal` feature is enabled. NOT a stable
493/// public surface — for workspace tools and bindings only.
494#[cfg(feature = "__internal")]
495#[doc(hidden)]
496pub use crate::tdbe::time;
497
498/// Canonical JSON helpers (`finite_or_null`, `canonicalize`,
499/// `canonicalize_and_serialize`) for the CLI / server / MCP renderers.
500///
501/// Only available when the `__internal` feature is enabled. NOT a stable
502/// public surface — for workspace tools and bindings only.
503#[cfg(feature = "__internal")]
504#[doc(hidden)]
505pub use crate::tdbe::json_canon;
506
507/// FIT 4-bit nibble codec for FPSS tick compression.
508///
509/// Only available when the `__internal` feature is enabled. NOT a stable
510/// public surface — for workspace tools and bindings only.
511#[cfg(feature = "__internal")]
512#[doc(hidden)]
513pub use crate::tdbe::codec;
514
515/// Calendar-day market status enum (`Open`, `EarlyClose`, `FullClose`,
516/// `Weekend`). The generated tick constructors validate the wire string
517/// against it; the FFI calendar-status helper resolves codes through it.
518///
519/// Only available when the `__internal` feature is enabled. NOT a stable
520/// public surface — for workspace tools and bindings only.
521#[cfg(feature = "__internal")]
522#[doc(hidden)]
523pub use crate::tdbe::types::enums::CalendarStatus;
524
525// ─── DataFrame extension traits (feature-gated) ──────────────────────────────
526
527#[cfg(any(feature = "polars", feature = "arrow"))]
528#[cfg_attr(docsrs, doc(cfg(any(feature = "polars", feature = "arrow"))))]
529/// DataFrame conversion for tick slices.
530///
531/// Feature-gated on `polars` and/or `arrow`. Each tick type implements the
532/// relevant trait so you can call `.to_polars()` or `.to_arrow()` on any
533/// `&[TickType]`.
534pub mod frames_api {
535 #[cfg(feature = "arrow")]
536 #[cfg_attr(docsrs, doc(cfg(feature = "arrow")))]
537 pub use crate::frames::TicksArrowExt;
538
539 #[cfg(feature = "polars")]
540 #[cfg_attr(docsrs, doc(cfg(feature = "polars")))]
541 pub use crate::frames::TicksPolarsExt;
542}
543
544// ─── Optional allocator ───────────────────────────────────────────────────────
545
546#[cfg(feature = "mimalloc-allocator")]
547#[cfg_attr(docsrs, doc(cfg(feature = "mimalloc-allocator")))]
548/// Re-export of `MiMalloc` from the [mimalloc](https://crates.io/crates/mimalloc) crate for use as `#[global_allocator]`.
549///
550/// Library crates cannot set a global allocator — that must live in
551/// the consuming binary. Enable the `mimalloc-allocator` feature and
552/// attach the handle in your binary's `main.rs`:
553///
554/// ```rust,ignore
555/// #[global_allocator]
556/// static GLOBAL: thetadatadx::mimalloc::MiMalloc = thetadatadx::mimalloc::MiMalloc;
557/// ```
558pub mod mimalloc {
559 pub use ::mimalloc::MiMalloc;
560}
561
562// ─── Prelude ──────────────────────────────────────────────────────────────────
563
564/// Convenience re-exports for the contract-first streaming API.
565///
566/// ```rust,no_run
567/// use thetadatadx::prelude::*;
568/// # async fn doc() -> Result<(), thetadatadx::Error> {
569/// let creds = Credentials::from_file("creds.txt")?;
570/// let client = Client::connect(&creds, DirectConfig::production()).await?;
571/// let stock = Contract::stock("AAPL");
572/// let option = Contract::option("SPX", OptionLeg { expiration: "20260620", strike: "5400", right: "C" })?;
573/// client.stream().subscribe(stock.quote())?;
574/// client.stream().subscribe(option.trade())?;
575/// client.stream().subscribe(SecType::Option.full_trades())?;
576/// # Ok(()) }
577/// ```
578pub mod prelude {
579 pub use crate::auth::Credentials;
580 pub use crate::client::{Client, ConnectionStatus};
581 pub use crate::config::DirectConfig;
582 pub use crate::error::Error;
583 pub use crate::streaming::{
584 Contract, FullSubscriptionKind, OptionLeg, SecTypeExt, Subscription, SubscriptionKind,
585 };
586 pub use crate::tdbe::types::enums::SecType;
587}
588
589/// Install the ring `CryptoProvider` as the process-wide rustls default.
590///
591/// `rustls`, `tokio-rustls`, `hyper-rustls`, and `rustls-platform-verifier`
592/// are pinned workspace-wide to `default-features = false, features =
593/// ["ring", ...]`, so ring is the sole `CryptoProvider` in the dep graph.
594/// `rustls::crypto::CryptoProvider::install_default` still has to fire
595/// before any TLS handshake; this helper is the binding-side hook the
596/// language bindings call from their module-init paths. Idempotent —
597/// second-and-later calls return `false` and leave the prior provider
598/// intact. Returns `true` on the install pass that won the race.
599#[doc(hidden)]
600pub fn __internal_install_ring_crypto_provider() -> bool {
601 rustls::crypto::ring::default_provider()
602 .install_default()
603 .is_ok()
604}