Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ Interval format conversion (later superseded by shorthand normalization in v4.2.

### Changed

- Tick types in `tdbe` are hand-written (no `include!()`, no `endpoint_schema.toml` codegen). IDE-navigable, visible in source.
- Tick types in `tdbe` are hand-written (no `include!()`, no `tick_schema.toml` codegen). IDE-navigable, visible in source.
- Magic numbers in `TradeTick` impl replaced with `tdbe::flags::` named constants
- Documentation updated across 17+ files for new import paths

Expand All @@ -370,7 +370,7 @@ Interval format conversion (later superseded by shorthand normalization in v4.2.
### Added

- **Fully typed returns for all 61 endpoints** - 9 new tick types (`TradeQuoteTick`, `OpenInterestTick`, `MarketValueTick`, `GreeksTick`, `IvTick`, `PriceTick`, `CalendarDay`, `InterestRateTick`, `OptionContract`). All 31 endpoints that returned raw `proto::DataTable` now return typed `Vec<T>`. The `raw_endpoint!` macro has been removed entirely. Zero raw protobuf in the public API.
- **TOML-driven codegen** - `endpoint_schema.toml` is the single source of truth for all 14 tick type definitions and DataTable column schemas. `build.rs` generates Rust structs and parsers at compile time. Adding a new column = one line in the TOML.
- **TOML-driven codegen** - `tick_schema.toml` is the single source of truth for all 14 tick type definitions and DataTable column schemas. `build.rs` generates Rust structs and parsers at compile time. Adding a new column = one line in the TOML.
- **Proto maintenance guide** (`proto/MAINTENANCE.md`) - step-by-step instructions for ThetaData engineers to add columns, RPCs, or replace proto files.
- 10 new parse functions in `decode.rs` (including `parse_eod_ticks` moved from inline in `direct.rs`)
- All downstream consumers updated: FFI (9 new JSON converters), CLI (9 new renderers), Server (9 new sonic_rs serializers), MCP (9 new serializers), Python SDK (9 new dict converters)
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ feat(core)!: replace DirectClient with ThetaDataDx unified client
The endpoint-facing source of truth is now split across:
- `crates/thetadatadx/proto/external.proto` for the wire contract
- `crates/thetadatadx/endpoint_surface.toml` for the normalized SDK surface
- `crates/thetadatadx/endpoint_schema.toml` for DataTable parser layouts
- `crates/thetadatadx/tick_schema.toml` for DataTable parser layouts

The build expands that metadata into the registry, shared endpoint runtime, and
`DirectClient` declarations automatically.
Expand All @@ -151,7 +151,7 @@ The build expands that metadata into the registry, shared endpoint runtime, and
- `cargo build` validates the declared surface against `external.proto` and generates the registry/runtime/direct surfaces

3. **Add the column schema** (if the response has a new layout)
- Add a `[types.YourTick]` block to `crates/thetadatadx/endpoint_schema.toml`
- Add a `[types.YourTick]` block to `crates/thetadatadx/tick_schema.toml`
- `cargo build` generates the parser
- See `docs/endpoint-schema.md` for the TOML format
- Note: tick type structs, `Price`, enums, codecs, and Greeks live in `crates/tdbe/`.
Expand Down Expand Up @@ -190,7 +190,7 @@ When ThetaData ships a new official proto revision:

1. Replace `crates/thetadatadx/proto/external.proto`
2. Update `endpoint_surface.toml` when the normalized SDK surface changes
3. Update `endpoint_schema.toml` if DataTable column layouts changed
3. Update `tick_schema.toml` if DataTable column layouts changed
4. Run the relevant checks from the sections above
5. See `crates/thetadatadx/proto/MAINTENANCE.md` for the detailed guide

Expand Down
2 changes: 1 addition & 1 deletion crates/thetadatadx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ license = "GPL-3.0-or-later"
keywords = ["thetadata", "market-data", "tick-data", "zero-copy", "low-latency"]
categories = ["finance", "api-bindings", "network-programming", "encoding"]
readme = "../../README.md"
include = ["src/**/*", "proto/**/*", "build.rs", "build_support/**/*", "Cargo.toml", "endpoint_schema.toml", "endpoint_surface.toml"]
include = ["src/**/*", "proto/**/*", "build.rs", "build_support/**/*", "Cargo.toml", "tick_schema.toml", "endpoint_surface.toml"]

[package.metadata.docs.rs]
all-features = true
Expand Down
4 changes: 2 additions & 2 deletions crates/thetadatadx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ src/
proto/
external.proto - canonical MDDS wire contract from ThetaData
MAINTENANCE.md - endpoint/proto maintenance guide
endpoint_schema.toml - single source of truth for tick type definitions
tick_schema.toml - single source of truth for tick type definitions
endpoint_surface.toml - explicit endpoint surface spec for registry/direct/runtime generation
build.rs - small build entrypoint
build_support/ - build-time generators for tick decoding and endpoint surfaces
```

## TOML Codegen

All 14 tick types and their DataTable parsers are generated at compile time from `endpoint_schema.toml`. Adding a new column is one line in the TOML. See [docs/endpoint-schema.md](../../docs/endpoint-schema.md).
All 14 tick types and their DataTable parsers are generated at compile time from `tick_schema.toml`. Adding a new column is one line in the TOML. See [docs/endpoint-schema.md](../../docs/endpoint-schema.md).

## Endpoint Surface Spec

Expand Down
19 changes: 7 additions & 12 deletions crates/thetadatadx/build_support/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,16 +925,15 @@ fn merge_surface_and_wire(
}

pub fn generate_all() -> Result<(), Box<dyn std::error::Error>> {
generate_endpoint_registry()?;
generate_endpoint_runtime()?;
generate_direct_endpoints()?;
let parsed = load_endpoint_specs()?;
generate_endpoint_registry(&parsed)?;
generate_endpoint_runtime(&parsed)?;
generate_direct_endpoints(&parsed)?;
println!("cargo:rerun-if-changed=proto/external.proto");
Ok(())
}

fn generate_endpoint_registry() -> Result<(), Box<dyn std::error::Error>> {
let parsed = load_endpoint_specs()?;

fn generate_endpoint_registry(parsed: &ParsedEndpoints) -> Result<(), Box<dyn std::error::Error>> {
// ── Generate Rust code ──────────────────────────────────────────────────
let mut code = String::new();
code.push_str(
Expand Down Expand Up @@ -994,9 +993,7 @@ fn generate_endpoint_registry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

fn generate_endpoint_runtime() -> Result<(), Box<dyn std::error::Error>> {
let parsed = load_endpoint_specs()?;

fn generate_endpoint_runtime(parsed: &ParsedEndpoints) -> Result<(), Box<dyn std::error::Error>> {
let mut code = String::new();
code.push_str(
"// Auto-generated by build.rs from endpoint_surface.toml validated against external.proto.\n",
Expand Down Expand Up @@ -1032,9 +1029,7 @@ fn generate_endpoint_runtime() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

fn generate_direct_endpoints() -> Result<(), Box<dyn std::error::Error>> {
let parsed = load_endpoint_specs()?;

fn generate_direct_endpoints(parsed: &ParsedEndpoints) -> Result<(), Box<dyn std::error::Error>> {
let mut list_code = String::new();
let mut parsed_code = String::new();
list_code.push_str(
Expand Down
2 changes: 1 addition & 1 deletion crates/thetadatadx/build_support/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Build-time generator orchestration for `thetadatadx`.
//!
//! The build pipeline has two distinct responsibilities:
//! - generate tick decoders from `endpoint_schema.toml`
//! - generate tick decoders from `tick_schema.toml`
//! - generate endpoint-facing surfaces from the explicit endpoint spec plus
//! the upstream wire contract in `proto/external.proto`

Expand Down
22 changes: 13 additions & 9 deletions crates/thetadatadx/build_support/ticks.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
// ─────────────────────────────────────────────────────────────────────────────
// Code-generate tick structs + parsers from endpoint_schema.toml
// Code-generate tick structs + parsers from tick_schema.toml
// ─────────────────────────────────────────────────────────────────────────────

#[derive(Debug, Deserialize)]
struct Schema {
types: HashMap<String, TickTypeDef>,
}

/// Parsed from tick_schema.toml. `doc`, `copy`, and `align` exist in the
/// TOML for documentation / FFI layout hints but are not used by the parser
/// generator — tick structs live in `tdbe::types::tick` (hand-written).
#[derive(Debug, Deserialize)]
struct TickTypeDef {
#[serde(rename = "doc")]
_doc: String,
#[serde(rename = "copy")]
_copy: bool,
#[serde(default, rename = "align")]
_align: Option<u32>,
#[allow(dead_code)]
doc: String,
#[allow(dead_code)]
copy: bool,
#[serde(default)]
#[allow(dead_code)]
align: Option<u32>,
parser: String,
#[serde(default)]
required: Vec<String>,
Expand All @@ -33,7 +37,7 @@ struct ColumnDef {
}

pub fn generate() -> Result<(), Box<dyn std::error::Error>> {
let schema_path = "endpoint_schema.toml";
let schema_path = "tick_schema.toml";
let schema_str = std::fs::read_to_string(schema_path)?;
let schema: Schema = toml::from_str(&schema_str)?;

Expand All @@ -48,7 +52,7 @@ pub fn generate() -> Result<(), Box<dyn std::error::Error>> {
// Only parser functions are generated here.
let mut parsers = String::new();
parsers.push_str(
"// Auto-generated by build.rs from endpoint_schema.toml \u{2014} do not edit manually.\n\n",
"// Auto-generated by build.rs from tick_schema.toml \u{2014} do not edit manually.\n\n",
);
// Note: tick types are imported by decode.rs via `use tdbe::types::tick::*;`
// No additional imports needed in the generated code.
Expand Down
12 changes: 6 additions & 6 deletions crates/thetadatadx/proto/MAINTENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ proto/
external.proto - canonical proto from ThetaData (60 RPCs, BetaEndpoints package)
MAINTENANCE.md - this file

../endpoint_schema.toml - column schemas for all DataTable-returning endpoints
../tick_schema.toml - column schemas for all DataTable-returning endpoints
../endpoint_surface.toml - normalized endpoint surface specification
../build.rs - small build entrypoint
../build_support/ - build-time generators and validators
Expand All @@ -50,7 +50,7 @@ proto/
`$OUT_DIR/direct_list_endpoints_generated.rs`,
`$OUT_DIR/direct_parsed_endpoints_generated.rs`.

3. **Tick parser codegen**: the build reads `endpoint_schema.toml` and generates
3. **Tick parser codegen**: the build reads `tick_schema.toml` and generates
`DataTable` parser functions. Output: `$OUT_DIR/decode_generated.rs`.
The public tick structs live in `crates/tdbe/src/types/tick.rs` and must stay
aligned with that schema.
Expand All @@ -77,7 +77,7 @@ groups/templates, and invalid overrides fail the build.

Example: ThetaData adds a `vwap` column to the EOD response.

1. Open `../endpoint_schema.toml`
1. Open `../tick_schema.toml`
2. Find the `[types.EodTick]` section
3. Add one line to the `columns` array:
```toml
Expand Down Expand Up @@ -113,7 +113,7 @@ service BetaThetaTerminal {

**Step 2 — Column schema**

If the response uses a new column layout, add a type to `../endpoint_schema.toml`:
If the response uses a new column layout, add a type to `../tick_schema.toml`:
```toml
[types.VwapTick]
doc = "Volume-weighted average price tick."
Expand Down Expand Up @@ -162,7 +162,7 @@ When ThetaData ships a new version:
4. If any RPCs were renamed or removed, `cargo build` will fail validation when
`endpoint_surface.toml` no longer matches the wire contract. Fix the spec.
5. If new RPCs were added, add corresponding entries to `endpoint_surface.toml`.
6. If column schemas changed, update `endpoint_schema.toml` to match.
6. If column schemas changed, update `tick_schema.toml` to match.
7. Run `cargo test` to verify everything works.

Note: the single-file `external.proto` layout means you no longer need to worry
Expand All @@ -184,4 +184,4 @@ are all in the same package as the request/response types.
## Questions?

If anything is unclear, check `docs/endpoint-schema.md` for the full TOML schema
reference, or look at the existing entries in `endpoint_schema.toml` as examples.
reference, or look at the existing entries in `tick_schema.toml` as examples.
2 changes: 1 addition & 1 deletion crates/thetadatadx/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ pub(crate) fn row_number_i64(row: &proto::DataValueList, idx: usize) -> i64 {
.unwrap_or(0)
}

// Generated code -- parser functions from endpoint_schema.toml by build.rs.
// Generated code -- parser functions from tick_schema.toml by build.rs.
#[allow(clippy::pedantic)]
mod decode_generated {
use super::*;
Expand Down
29 changes: 28 additions & 1 deletion crates/thetadatadx/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ impl EndpointArgs {
}

/// Read a required floating-point argument.
///
/// Accepts both `Float` and `Int` values. `Int` is widened to `f64`.
pub fn required_float64(&self, key: &str) -> Result<f64, EndpointError> {
match self.required_value(key)? {
EndpointArgValue::Float(value) => Ok(*value),
Expand All @@ -195,6 +197,8 @@ impl EndpointArgs {
}

/// Read an optional floating-point argument.
///
/// Accepts both `Float` and `Int` values. `Int` is widened to `f64`.
pub fn optional_float64(&self, key: &str) -> Result<Option<f64>, EndpointError> {
match self.optional_value(key) {
None => Ok(None),
Expand Down Expand Up @@ -239,6 +243,25 @@ pub enum EndpointError {
UnknownEndpoint(String),
}

impl std::fmt::Display for EndpointError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidParams(msg) => write!(f, "invalid params: {msg}"),
Self::Server(err) => write!(f, "server error: {err}"),
Self::UnknownEndpoint(name) => write!(f, "unknown endpoint: {name}"),
}
}
}

impl std::error::Error for EndpointError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Server(err) => Some(err),
_ => None,
}
}
}

impl From<Error> for EndpointError {
fn from(value: Error) -> Self {
Self::Server(value)
Expand Down Expand Up @@ -289,7 +312,11 @@ pub enum EndpointOutput {
OptionContracts(Vec<OptionContract>),
}

/// Invoke a generated endpoint adapter by endpoint name.
/// Public entry point for transport-neutral endpoint dispatch.
///
/// Delegates to the generated dispatch function. This indirection exists
/// as a hook point for cross-cutting concerns (auth retry, metrics,
/// rate limiting) without modifying generated code.
pub async fn invoke_endpoint(
client: &ThetaDataDx,
name: &str,
Expand Down
4 changes: 2 additions & 2 deletions docs-site/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Tick types in `tdbe` are hand-written (no `include!()`, no `endpoint_schema.toml` codegen). IDE-navigable, visible in source.
- Tick types in `tdbe` are hand-written (no `include!()`, no `tick_schema.toml` codegen). IDE-navigable, visible in source.
- Magic numbers in `TradeTick` impl replaced with `tdbe::flags::` named constants
- Documentation updated across 17+ files for new import paths

Expand All @@ -141,7 +141,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- **Fully typed returns for all 61 endpoints** - 9 new tick types (`TradeQuoteTick`, `OpenInterestTick`, `MarketValueTick`, `GreeksTick`, `IvTick`, `PriceTick`, `CalendarDay`, `InterestRateTick`, `OptionContract`). All 31 endpoints that returned raw `proto::DataTable` now return typed `Vec<T>`. The `raw_endpoint!` macro has been removed entirely. Zero raw protobuf in the public API.
- **TOML-driven codegen** - `endpoint_schema.toml` is the single source of truth for all 14 tick type definitions and DataTable column schemas. `build.rs` generates Rust structs and parsers at compile time. Adding a new column = one line in the TOML.
- **TOML-driven codegen** - `tick_schema.toml` is the single source of truth for all 14 tick type definitions and DataTable column schemas. `build.rs` generates Rust structs and parsers at compile time. Adding a new column = one line in the TOML.
- **Proto maintenance guide** (`proto/MAINTENANCE.md`) - step-by-step instructions for ThetaData engineers to add columns, RPCs, or replace proto files.
- 10 new parse functions in `decode.rs` (including `parse_eod_ticks` moved from inline in `direct.rs`)
- All downstream consumers updated: FFI (9 new JSON converters), CLI (9 new renderers), Server (9 new sonic_rs serializers), MCP (9 new serializers), Python SDK (9 new dict converters)
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ let (contract, consumed) = Contract::from_bytes(&bytes)?; // deserialize

## Tick Types

All 14 tick types are `Clone + Debug` structs generated from `endpoint_schema.toml`. Most are also `Copy` (except `OptionContract`, which contains a `String` field). Fields are typically `i32`, with `i64` for large values (e.g., `MarketValueTick.market_cap`), `f64` for Greeks/IV, and `String` for identifiers. All price fields are `f64` -- decoded during parsing. No `price_type` in the public API.
All 14 tick types are `Clone + Debug` structs generated from `tick_schema.toml`. Most are also `Copy` (except `OptionContract`, which contains a `String` field). Fields are typically `i32`, with `i64` for large values (e.g., `MarketValueTick.market_cap`), `f64` for Greeks/IV, and `String` for identifiers. All price fields are `f64` -- decoded during parsing. No `price_type` in the public API.

### Contract Identification Fields

Expand Down
6 changes: 3 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ Three response processing modes are available:
### Build-time Generation Pipeline

ThetaDataDx has two generation pipelines at build time:
- tick parser generation from `endpoint_schema.toml`
- tick parser generation from `tick_schema.toml`
- endpoint surface generation from `endpoint_surface.toml` validated against `external.proto`

```mermaid
flowchart LR
TOML["endpoint_schema.toml<br/><i>14 tick type definitions<br/>with column schemas</i>"]
TOML["tick_schema.toml<br/><i>14 tick type definitions<br/>with column schemas</i>"]
SURFACE["endpoint_surface.toml<br/><i>endpoint spec<br/>groups + templates</i>"]
PROTO["external.proto<br/><i>official wire contract</i>"]
BUILD["build.rs<br/><i>delegates to build_support/</i>"]
Expand Down Expand Up @@ -538,7 +538,7 @@ graph TD
REGISTRY["registry.rs<br/><i>EndpointMeta, ENDPOINTS static</i>"]

subgraph codegen["Build-time Generation"]
SCHEMA["endpoint_schema.toml<br/><i>14 tick type definitions</i>"]
SCHEMA["tick_schema.toml<br/><i>14 tick type definitions</i>"]
SURFACE["endpoint_surface.toml<br/><i>endpoint surface spec<br/>groups + templates</i>"]
BUILD["build.rs<br/><i>delegates to build_support/</i>"]
SUPPORT["build_support/<br/><i>endpoints.rs + ticks.rs</i>"]
Expand Down
10 changes: 5 additions & 5 deletions docs/endpoint-schema.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Endpoint Schema (`endpoint_schema.toml`)
# Endpoint Schema (`tick_schema.toml`)

The file `crates/thetadatadx/endpoint_schema.toml` is the canonical schema for
The file `crates/thetadatadx/tick_schema.toml` is the canonical schema for
ThetaData `DataTable` response layouts and the generated parser functions that
decode them into typed ticks.

Expand Down Expand Up @@ -55,7 +55,7 @@ must stay aligned with the schema.

## How to add a new endpoint/column

1. Add a new `[types.YourNewTick]` table to `endpoint_schema.toml`
1. Add a new `[types.YourNewTick]` table to `tick_schema.toml`
2. Define all columns with their header names, field names, and types
3. Set `parser = "parse_your_new_ticks"`
4. Set `required`, `copy`, `align`, etc. as needed
Expand Down Expand Up @@ -83,13 +83,13 @@ pub fn parse_greeks_ticks(table: &crate::proto::DataTable) -> Vec<GreeksTick> {

If ThetaData adds new fields to an existing endpoint's DataTable:

1. Add the new column(s) to the corresponding type in `endpoint_schema.toml`
1. Add the new column(s) to the corresponding type in `tick_schema.toml`
2. Run `cargo build` and `cargo test`
3. The new field automatically appears in the struct and is parsed from the DataTable

If ThetaData adds a completely new endpoint:

1. Update `proto/external.proto`
2. Add the endpoint entry to `endpoint_surface.toml`
3. Add the tick type to `endpoint_schema.toml`
3. Add the tick type to `tick_schema.toml`
4. Add or update the corresponding tick struct in `crates/tdbe/src/types/tick.rs`
Loading
Loading