Skip to content

Commit 3cc589a

Browse files
authored
refactor!: rename Contract.root/exp_date to symbol/expiration (#484)
Closes #467. Rename the public-API fields on the SDK to match the v3 vendor surface documented in the v2 → v3 migration guide: Contract.root -> Contract.symbol Contract.exp_date -> Contract.expiration OptionContract.root -> OptionContract.symbol FlatFileRow.root -> FlatFileRow.symbol IndexEntry.root -> IndexEntry.symbol IndexEntry.exp -> IndexEntry.expiration Plus the matching constructor + parameter renames (Contract::stock(symbol), Contract::option(symbol, expiration, ...), Contract::option_raw(...), FlatFileRow::from_decoded(symbol, expiration, ...)) and the cross-language bindings regenerated from the SSOT (Python, TypeScript, Go, C++, C ABI). The wire format is unchanged. Contract::to_bytes / Contract::from_bytes still serialize the field as `root` per Contract.java parity, and the FLATFILES decoder still resolves both v2 (`root`) and v3 (`symbol`) response columns through the existing decode::HEADER_ALIASES alias table. Workspace 8.0.26 -> 8.0.27, tdbe 0.12.7 -> 0.12.8. CHANGELOG and docs-site/docs/changelog.md kept byte-identical. Local CI gate: cargo fmt --all -- --check # clean cargo clippy --workspace --all-targets -- -D warnings # clean cargo test --workspace # 0 failures cargo deny check # advisories+bans+licenses+sources ok generate_sdk_surfaces --check # no drift cargo check/clippy/test --manifest-path tools/mcp/Cargo.toml cargo check/clippy/test --manifest-path tools/server/Cargo.toml
1 parent a532f6a commit 3cc589a

74 files changed

Lines changed: 553 additions & 377 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [8.0.28] - 2026-05-06
11+
12+
### Breaking
13+
14+
- **`Contract`, `OptionContract`, `FlatFileRow`, and `IndexEntry` rename
15+
`root` to `symbol` and `exp_date` to `expiration` to match the v3
16+
vendor surface documented in the [v2 → v3 migration guide][v3-mig].
17+
The wire codec is unchanged — `Contract::to_bytes` /
18+
`Contract::from_bytes` still serialize the field as `root` per
19+
`Contract.java` parity, and the FLATFILES decoder still resolves both
20+
v2 (`root`) and v3 (`symbol`) response columns through the existing
21+
`decode::HEADER_ALIASES`. Per-language renames:
22+
23+
- **Rust** (`thetadatadx::fpss::protocol::Contract`,
24+
`tdbe::types::tick::OptionContract`,
25+
`thetadatadx::flatfiles::FlatFileRow`):
26+
- `Contract.root``Contract.symbol`
27+
- `Contract.exp_date``Contract.expiration`
28+
- `Contract::stock(root)``Contract::stock(symbol)`
29+
- `Contract::index(root)``Contract::index(symbol)`
30+
- `Contract::rate(root)``Contract::rate(symbol)`
31+
- `Contract::option(root, exp_date, …)`
32+
`Contract::option(symbol, expiration, …)`
33+
- `Contract::option_raw(root, exp_date, …)`
34+
`Contract::option_raw(symbol, expiration, …)`
35+
- `OptionContract.root``OptionContract.symbol`
36+
- `FlatFileRow.root``FlatFileRow.symbol`
37+
- **Python** (`thetadatadx.Contract`, `thetadatadx.OptionContract`):
38+
`contract.root` / `contract.exp_date`
39+
`contract.symbol` / `contract.expiration`;
40+
`OptionContract(root=…)` constructor keyword → `symbol=…`.
41+
- **TypeScript** (`Contract`, `OptionContract`):
42+
`contract.root` / `contract.expDate`
43+
`contract.symbol` / `contract.expiration`.
44+
- **Go** (`thetadatadx.Contract`, `thetadatadx.OptionContract`):
45+
`c.Root` / `c.ExpDate``c.Symbol` / `c.Expiration`.
46+
- **C++** (`OptionContract`, `TdxContract`, `TdxOptionContract`):
47+
`c.root` / `c.exp_date` / `c.has_exp_date`
48+
`c.symbol` / `c.expiration` / `c.has_expiration`.
49+
- **C ABI**: `TdxContract.root``TdxContract.symbol`,
50+
`TdxContract.exp_date``TdxContract.expiration`,
51+
`TdxContract.has_exp_date``TdxContract.has_expiration`,
52+
`TdxOptionContract.root``TdxOptionContract.symbol`.
53+
- **FLATFILES CSV / JSONL**: contract-prefix headers and JSON keys
54+
change from `root,expiration,strike,right,…` to
55+
`symbol,expiration,strike,right,…`. Stock blobs go from `root,…` to
56+
`symbol,…`. The vendor's response columns are unchanged; only the
57+
SDK's emitted file headers change.
58+
- **REST / WebSocket / MCP outputs** in `tools/server` and
59+
`tools/mcp` emit `"symbol"` / `"expiration"` keys on every contract
60+
payload (option lists, FPSS event contracts, FLATFILES rows).
61+
62+
[v3-mig]: https://docs.thetadata.us/Articles/Getting-Started/v2-migration-guide.html#_5-parameter-mapping
63+
64+
### Changed
65+
66+
- Workspace 8.0.27 → 8.0.28, tdbe 0.12.7 → 0.12.8. The tdbe bump rides
67+
the regenerated `OptionContract.symbol` field in
68+
`crates/tdbe/src/types/tick_generated.rs`; every other change ships
69+
as patch deltas off the existing v8 line per repo policy.
70+
- `tools/cli` raw column header for `OptionContract` is `symbol`
71+
instead of `root`, sourced from `tick_schema.toml::field` so future
72+
schema renames flow through the CLI without a helper edit.
73+
1074
## [8.0.27] - 2026-05-06
1175

1276
### Changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tdbe/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tdbe"
3-
version = "0.12.7"
3+
version = "0.12.8"
44
edition.workspace = true
55
rust-version.workspace = true
66
authors.workspace = true

crates/tdbe/src/types/enums.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// The FPSS decoder uses it for the empty-contract placeholder that flows on
55
/// data events arriving before their `ContractAssigned` frame — downstream
66
/// consumers can pattern-match `sec_type == SecType::Unknown` instead of
7-
/// relying on `contract.root.is_empty()`. `Unknown` has no wire-protocol
7+
/// relying on `contract.symbol.is_empty()`. `Unknown` has no wire-protocol
88
/// representation: [`SecType::from_code`] never returns it, and it is not
99
/// serialized in subscribe payloads.
1010
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

crates/tdbe/src/types/tick.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! ...). These read `flags::*` constants and don't fit the schema's
99
//! field-only model.
1010
//! * `impl OptionContract` for `is_call` / `is_put` -- a non-`Copy` struct
11-
//! so the macro doesn't apply.
11+
//! (because of the `String` `symbol` field) so the macro doesn't apply.
1212
//!
1313
//! The structs themselves are generated at build-time from
1414
//! `crates/thetadatadx/tick_schema.toml` by

crates/tdbe/src/types/tick_generated.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,12 @@ pub struct OpenInterestTick {
275275

276276
/// Option contract -- 4 fields. Contract specification.
277277
///
278-
/// Cannot be `Copy` because of the `String` root field.
278+
/// Cannot be `Copy` because of the `String` symbol field.
279279
#[must_use]
280280
#[derive(Debug, Clone)]
281281
#[repr(C)]
282282
pub struct OptionContract {
283-
pub root: String,
283+
pub symbol: String,
284284
pub expiration: i32,
285285
pub strike: f64,
286286
pub right: i32,

crates/thetadatadx/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thetadatadx"
3-
version = "8.0.27"
3+
version = "8.0.28"
44
edition.workspace = true
55
rust-version.workspace = true
66
authors.workspace = true
@@ -40,7 +40,7 @@ frames = ["polars", "arrow"]
4040
live-tests = []
4141

4242
[dependencies]
43-
tdbe = { version = "0.12.7", path = "../tdbe" }
43+
tdbe = { version = "0.12.8", path = "../tdbe" }
4444

4545
# gRPC + protobuf (tonic 0.14 extracted prost codec into tonic-prost)
4646
tonic = { version = "=0.14.5", features = ["tls-ring", "tls-native-roots", "channel", "transport"] }
@@ -138,7 +138,7 @@ prost-build = "=0.14.3"
138138
regex = "1.12.3"
139139
toml = "1.1.2"
140140
serde = { version = "1.0.228", features = ["derive"] }
141-
tdbe = { version = "0.12.7", path = "../tdbe" }
141+
tdbe = { version = "0.12.8", path = "../tdbe" }
142142

143143
[[bench]]
144144
name = "bench_decode"

crates/thetadatadx/build_support/endpoints/render/templates/cpp/option_contracts_convert.cpp.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
result.reserve(arr.len);
1010
for (size_t i = 0; i < arr.len; ++i) {
1111
OptionContract c;
12-
c.root = arr.data[i].root ? std::string(arr.data[i].root) : "";
12+
c.symbol = arr.data[i].symbol ? std::string(arr.data[i].symbol) : "";
1313
c.expiration = arr.data[i].expiration;
1414
c.strike = arr.data[i].strike;
1515
c.right = arr.data[i].right;

crates/thetadatadx/build_support/fpss_events/ffi_c.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ use super::schema::{sorted_data_events, Schema};
1010
/// `#[repr(C)] TdxContract`. Layout must match field-for-field with
1111
/// `render_contract_struct_rust` in `ffi_rust.rs`.
1212
fn render_contract_struct_c() -> &'static str {
13-
"/* FPSS Contract shared across every data event. `root` is a\n\
13+
"/* FPSS Contract shared across every data event. `symbol` is a\n\
1414
* NUL-terminated C string (may be null when not yet resolved);\n\
1515
* optional option fields use a tagged-present bool because C has no\n\
1616
* Option<T>. Layout is byte-identical to Rust's #[repr(C)] TdxContract.\n\
1717
*/\n\
1818
typedef struct {\n\
19-
const char *root;\n\
19+
const char *symbol;\n\
2020
int32_t sec_type;\n\
21-
bool has_exp_date;\n\
22-
int32_t exp_date;\n\
21+
bool has_expiration;\n\
22+
int32_t expiration;\n\
2323
bool has_is_call;\n\
2424
bool is_call;\n\
2525
bool has_strike;\n\

crates/thetadatadx/build_support/fpss_events/ffi_rust.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use super::schema::{sorted_data_events, Schema};
1212
/// event's `contract` field points at. Uses `#[repr(C)]` so the C header
1313
/// mirror gets byte-identical layout.
1414
///
15-
/// Strings (the `root` field) cross as C strings — the field is
15+
/// Strings (the `symbol` field) cross as C strings — the field is
1616
/// `*const c_char` backed by a `CString` inside `FfiBufferedEvent` so the
1717
/// pointer stays valid for the lifetime of the buffered event. Optional
1818
/// fields use a tagged-optional pattern (`has_*: bool` + value) because
@@ -21,20 +21,20 @@ fn render_contract_struct_rust() -> String {
2121
String::from(
2222
"/// FPSS `Contract` shared across every data event.\n\
2323
/// \n\
24-
/// `root` is a NUL-terminated C string; may be null when the SDK has not\n\
24+
/// `symbol` is a NUL-terminated C string; may be null when the SDK has not\n\
2525
/// yet resolved the server-assigned contract_id to a `ContractAssigned`\n\
26-
/// frame. Optional option fields (`exp_date`, `is_call`, `strike`) use a\n\
26+
/// frame. Optional option fields (`expiration`, `is_call`, `strike`) use a\n\
2727
/// tagged-present bool because `#[repr(C)]` cannot express `Option<T>`\n\
2828
/// directly.\n\
2929
#[repr(C)]\n\
3030
pub struct TdxContract {\n\
31-
/// Ticker root (e.g. \"AAPL\"). Null until ContractAssigned arrives.\n\
32-
pub root: *const c_char,\n\
31+
/// Ticker symbol (e.g. \"AAPL\"). Null until ContractAssigned arrives.\n\
32+
pub symbol: *const c_char,\n\
3333
/// Security type code — matches `tdbe::types::enums::SecType`.\n\
3434
pub sec_type: i32,\n\
35-
/// Whether `exp_date` is meaningful (options only).\n\
36-
pub has_exp_date: bool,\n\
37-
pub exp_date: i32,\n\
35+
/// Whether `expiration` is meaningful (options only).\n\
36+
pub has_expiration: bool,\n\
37+
pub expiration: i32,\n\
3838
/// Whether `is_call` is meaningful (options only).\n\
3939
pub has_is_call: bool,\n\
4040
pub is_call: bool,\n\
@@ -44,10 +44,10 @@ pub struct TdxContract {\n\
4444
}\n\
4545
\n\
4646
pub(crate) const ZERO_CONTRACT_STRUCT: TdxContract = TdxContract {\n\
47-
root: ptr::null(),\n\
47+
symbol: ptr::null(),\n\
4848
sec_type: 0,\n\
49-
has_exp_date: false,\n\
50-
exp_date: 0,\n\
49+
has_expiration: false,\n\
50+
expiration: 0,\n\
5151
has_is_call: false,\n\
5252
is_call: false,\n\
5353
has_strike: false,\n\
@@ -178,14 +178,15 @@ pub(super) fn render_ffi_fpss_event_converter(schema: &Schema) -> String {
178178
// cursors / auxiliary fields without breaking the FFI conversion.
179179
out.push_str(" ..\n");
180180
out.push_str(" }) => {\n");
181-
// Stage the CString backing the Contract.root pointer so it
181+
// Stage the CString backing the Contract.symbol pointer so it
182182
// outlives the TdxFpssEvent inside the FfiBufferedEvent. The
183183
// backing-memory wrapper already has a `_detail_string` slot we
184-
// repurpose here — only one owned CString per event, and Contract
185-
// .root is mutually exclusive with Control.detail on Data variants.
184+
// repurpose here — only one owned CString per event, and
185+
// Contract.symbol is mutually exclusive with Control.detail on
186+
// Data variants.
186187
if has_contract {
187188
out.push_str(
188-
" let contract_root_cstring = if contract.root.is_empty() {\n None\n } else {\n std::ffi::CString::new(contract.root.as_str()).ok()\n };\n let contract_root_ptr = contract_root_cstring\n .as_ref()\n .map_or(ptr::null(), |cs| cs.as_ptr());\n let tdx_contract = TdxContract {\n root: contract_root_ptr,\n sec_type: contract.sec_type as i32,\n has_exp_date: contract.exp_date.is_some(),\n exp_date: contract.exp_date.unwrap_or(0),\n has_is_call: contract.is_call.is_some(),\n is_call: contract.is_call.unwrap_or(false),\n has_strike: contract.strike.is_some(),\n strike: contract.strike.unwrap_or(0),\n };\n",
189+
" let contract_symbol_cstring = if contract.symbol.is_empty() {\n None\n } else {\n std::ffi::CString::new(contract.symbol.as_str()).ok()\n };\n let contract_symbol_ptr = contract_symbol_cstring\n .as_ref()\n .map_or(ptr::null(), |cs| cs.as_ptr());\n let tdx_contract = TdxContract {\n symbol: contract_symbol_ptr,\n sec_type: contract.sec_type as i32,\n has_expiration: contract.expiration.is_some(),\n expiration: contract.expiration.unwrap_or(0),\n has_is_call: contract.is_call.is_some(),\n is_call: contract.is_call.unwrap_or(false),\n has_strike: contract.strike.is_some(),\n strike: contract.strike.unwrap_or(0),\n };\n",
189190
);
190191
}
191192
out.push_str(" FfiBufferedEvent {\n");
@@ -225,7 +226,7 @@ pub(super) fn render_ffi_fpss_event_converter(schema: &Schema) -> String {
225226
out.push_str(" raw_data: ZERO_RAW,\n");
226227
out.push_str(" },\n");
227228
if has_contract {
228-
out.push_str(" _detail_string: contract_root_cstring,\n");
229+
out.push_str(" _detail_string: contract_symbol_cstring,\n");
229230
} else {
230231
out.push_str(" _detail_string: None,\n");
231232
}

0 commit comments

Comments
 (0)