This guide explains the macro system used by the generated MddsClient
surface.
Important
Most contributors should not add endpoints by hand with macro invocations. The current endpoint workflow is:
- update
crates/thetadatadx/proto/mdds.protowhen the wire contract changes - update
crates/thetadatadx/endpoint_surface.tomlfor the normalized SDK surface - update
crates/thetadatadx/tick_schema.tomlif a newDataTablelayout is introduced
The build then generates the registry, shared endpoint runtime, and
MddsClient declarations automatically. This guide is for understanding and
maintaining the macro layer that those generators target.
Every non-streaming MddsClient endpoint ultimately expands through
parsed_endpoint! defined in crates/thetadatadx/src/macros.rs; the
invocation sites live in crates/thetadatadx/src/mdds/endpoints.rs.
A single invocation
generates three things:
- A builder struct (e.g.,
StockHistoryOhlcBuilder) that holds required params as owned fields and optional params asOption<T>fields. - Chainable setter methods on the builder for each optional parameter.
- An
IntoFutureimpl so that.await-ing the builder executes the gRPC call, collects the response stream, and parses theDataTableinto typed ticks.
The MddsClient gets a method that constructs and returns the builder.
parsed_endpoint! {
/// Doc comment shown on the client method.
builder StockHistoryOhlcBuilder;
fn stock_history_ohlc(
symbol: str, // required params with type tags
date: str,
interval: str
) -> Vec<OhlcTick>; // return type
grpc: get_stock_history_ohlc; // gRPC stub method name
request: StockHistoryOhlcRequest; // protobuf request wrapper type
query: StockHistoryOhlcParams { // protobuf params struct + field mapping
symbol: symbol.to_string(),
date: date.to_string(),
ivl: interval.to_string(),
};
parse: decode::parse_ohlc_ticks; // DataTable -> Result<Vec<T>, DecodeError> parser
dates: date; // optional: validate YYYYMMDD format
optional { // optional params (chainable setters)
venue: opt_str = None,
start_time: opt_str = None,
end_time: opt_str = None,
}
}// 1. Builder struct
pub struct StockHistoryOhlcBuilder<'a> {
client: &'a MddsClient,
pub(crate) symbol: String, // required (str -> String)
pub(crate) date: String,
pub(crate) interval: String,
pub(crate) venue: Option<String>, // optional (opt_str -> Option<String>)
pub(crate) start_time: Option<String>,
pub(crate) end_time: Option<String>,
}
// 2. Setters
impl<'a> StockHistoryOhlcBuilder<'a> {
pub fn venue(mut self, v: &str) -> Self { ... }
pub fn start_time(mut self, v: &str) -> Self { ... }
pub fn end_time(mut self, v: &str) -> Self { ... }
}
// 3. IntoFuture -- makes `.await` work
impl<'a> IntoFuture for StockHistoryOhlcBuilder<'a> { ... }
// 4. Client method
impl MddsClient {
pub fn stock_history_ohlc(&self, symbol: &str, date: &str, interval: &str)
-> StockHistoryOhlcBuilder<'_> { ... }
}Usage:
// Simple
let ticks = client.stock_history_ohlc("AAPL", "20260401", "1m").await?;
// With options
let ticks = client.stock_history_ohlc("AAPL", "20260401", "1m")
.venue("arca")
.start_time("04:00:00")
.await?;Required and optional parameters use short "type tags" instead of raw Rust types.
The helper macros (req_field_type!, req_param_type!, opt_field_type!,
opt_setter!) expand each tag into the correct types.
| Tag | Struct field type | Constructor param type | Notes |
|---|---|---|---|
str |
String |
&str |
Most common |
str_vec |
Vec<String> |
&[&str] |
Multi-symbol endpoints |
| Tag | Field type | Setter param type | Notes |
|---|---|---|---|
opt_str |
Option<String> |
&str |
String options (venue, time) |
opt_i32 |
Option<i32> |
i32 |
Integer options (limit) |
opt_f64 |
Option<f64> |
f64 |
Float options |
opt_bool |
Option<bool> |
bool |
Boolean flags |
string |
String |
&str |
Required-with-default string |
The supported path for new endpoints is spec-driven, not hand-written macro expansion.
Add a [types.YourTick] block in crates/thetadatadx/tick_schema.toml.
build.rs generates the parse_your_ticks() function automatically. See
docs/endpoint-schema.md for the TOML format. The tick structs themselves live
in crates/tdbe/. Set contract_id = true if the tick type should carry
contract identification fields (expiration/strike/right).
Update crates/thetadatadx/proto/mdds.proto to add the request/params
messages. cargo build regenerates Rust types.
Update crates/thetadatadx/endpoint_surface.toml with the normalized endpoint
name, REST path, return kind, and parameter semantics. Reuse existing
param_groups and templates where possible instead of copying large parameter
blocks.
Run cargo build. The generator validates endpoint_surface.toml against
mdds.proto and emits the registry, shared endpoint runtime, and
MddsClient endpoint declarations.
You only need to edit the macro layer or files under build_support/endpoints/ if the
new endpoint cannot be expressed by the existing surface specification model.
Run:
cargo run -p thetadatadx --features config-file --bin generate_sdk_surfacesFor registry-driven endpoints, the SDK/FFI surface is generated from
endpoint_surface.toml. For non-endpoint SDK utilities and FPSS wrappers, use
crates/thetadatadx/sdk_surface.toml. For tick projection helpers, use
tick_schema.toml.
You only edit ffi/src/lib.rs, sdks/python/src/lib.rs, or sdks/cpp/*
directly when changing runtime plumbing that is intentionally outside the
checked-in generated surface.
Add to [Unreleased].
Direct streaming endpoint builders are now emitted directly from
endpoint_surface.toml; there is no separate streaming_endpoint! macro
surface to maintain. If you need a new direct streaming endpoint, add the
surface entry and regenerate.
Groups of endpoints that share the same required/optional parameter signatures are wrapped in family-specific macros to reduce duplication.
Wraps parsed_endpoint! for the 5 option-snapshot-greeks variants. All take
(symbol, expiration, strike, right) as required params plus the same set of
optional params.
For interval-based option history greeks endpoints. Adds date and interval
to the required params.
For trade-level option history greeks (no interval). Takes date as an
additional required param.
These macros call parsed_endpoint! internally -- they only factor out
the repeated parameter lists.
The FFI layer uses its own macro set to wrap the Rust builders into
#[no_mangle] extern "C" functions.
| Macro | Purpose |
|---|---|
tick_array_type! |
Defines a #[repr(C)] array struct with from_vec() and free() |
tick_array_free! |
Generates the extern "C" free function for a tick array |
ffi_typed_endpoint! |
Wraps a typed endpoint with C string params |
ffi_typed_endpoint_no_params! |
Wraps a typed endpoint with no params |
ffi_typed_snapshot_endpoint! |
Wraps a snapshot endpoint (takes C string array of symbols) |
ffi_list_endpoint! |
Wraps a list endpoint with C string params |
ffi_list_endpoint_no_params! |
Wraps a list endpoint with no params |
The pattern for adding an FFI endpoint:
// 1. Define the array type (if new tick type)
tick_array_type!(TdxVwapTickArray, VwapTick);
tick_array_free!(tdx_vwap_tick_array_free, TdxVwapTickArray);
// 2. Wrap the endpoint
ffi_typed_endpoint!(
/// Fetch historical VWAP data.
tdx_stock_history_vwap => stock_history_vwap, TdxVwapTickArray(symbol, start, end)
);