Skip to content

Commit 0992c92

Browse files
userFRMclaude
andauthored
feat(ssot)!: TOML render map, generated tdbe ticks, per-endpoint typed Greeks (#475)
* feat(build): TOML-driven render map collapses 19 helper match arms (refs #474) Every per-language binding name a renderer needs for one tick type -- Rust direct-client return type, generated parser fn, Go struct + converter, FFI array struct + free fn + output variant + header-return type, C++ value type, six Python converters (dict, columnar, pyclass-list, pyclass-list-class, vec-to-pylist, slice-arrow), TypeScript class + class-vec converter, plus the Python pyclass struct name -- now lives in `[types.X.render]` blocks in `crates/thetadatadx/tick_schema.toml`. The 20 helper functions that previously hardcoded those names with parallel `match` dispatches collapse into single HashMap lookups against a `OnceLock`-cached load of the schema: * `build_support/endpoints/helpers.rs::direct_return_type` / `direct_parser_name` / `go_result_type` / `go_converter_name` / `ffi_array_type` / `ffi_array_empty_expr` / `ffi_output_variant` / `ffi_from_vec_array_type` / `ffi_header_return_type` / `ffi_free_fn` / `cpp_value_type` / `cpp_converter_expr` / `python_converter` / `python_columnar_converter` / `python_pyclass_list_converter` / `python_pyclass_list_class` / `python_vec_to_pylist_converter` / `python_slice_arrow_converter` / `ts_class_name` / `ts_class_vec_converter` * `build_support/ticks/mod.rs::pyclass_name` Adding a tick type now requires one TOML row -- no helper edits. The generated SDK surfaces (sdks/{python,typescript,go,cpp}/...) are byte-identical against main because the new TOML rows reproduce the names the helpers previously hardcoded; the `--check` regen pass shows zero drift. Sets up the SSOT foundation for the per-endpoint typed Greeks structs and tdbe tick struct generation tracked in #474; those land in follow-up PRs to keep this change reviewable. CI gates locally: cargo fmt, cargo clippy --all-targets -D warnings, cargo test --workspace, cargo deny check, and `generate_sdk_surfaces --check` are all clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(build): collapse per-tick match arms onto schema render block Drive the FFI sizes / layout asserts / Go converter dispatch / Python list-fn / TS class-vec / CLI raw-header constants from `tick_schema.toml` [types.X.render] entries instead of hand-coded match arms. Adding a tick type now requires zero edits in `build_support/ticks/{cpp,go,python_classes, typescript,cli_headers}.rs`. * `OpenInterestTick` and `TradeQuoteTick` gained `align = 64` in the schema -- they were `#[repr(C, align(64))]` in `tdbe::types::tick` but the schema row was missing the directive, so the Go/C++ FFI size emitters under-counted (32/144 vs the actual 64/192). The hand-coded emitters compensated by reading `size_of::<tdbe::T>()` directly; with the schema as the SSOT the directive must live there. * `cpp.rs` now reaches into `go::tick_ffi_size_and_align` for the schema-computed (size, align) pair instead of `size_of::<tdbe::T>()`, so the C++ static_asserts share one layout calculator with Go. * Dropped the dead `pub(crate) use ts_tick_class_factory_name` re-export (no remaining caller outside the typescript emitter). refs #474 * feat(tdbe): generate tick structs from tick_schema.toml `crates/tdbe/src/types/tick_generated.rs` is now emitted by `generate_sdk_surfaces` from the schema. Hand-written `tick.rs` `include!`s the generated file and keeps only the items the schema cannot express: `impl_contract_id!` macro applications, `TradeTick` flag helpers, and `OptionContract` is_call/is_put. Layout asserts in `types::tick::layout_asserts` pin every struct's size + alignment to the values the C / Go FFI mirrors and `tick_layout_asserts.hpp.inc` rely on, so a schema edit that drifts a layout fails `cargo test -p tdbe` before reaching the FFI side. refs #474 * feat(greeks)!: per-endpoint typed Greeks structs (closes #474) Split the union `GreeksTick` into the four shapes the v3 server actually emits across the `option_*_greeks_*` family. The full union becomes `GreeksAllTick` (returned by `_greeks_all` and `_greeks_eod`); the per-order endpoints (`_first_order`, `_second_order`, `_third_order`) return strict subsets matching the upstream OpenAPI -- no zero-default columns leak from one subset into another. `GreeksAllTick` adds `bid`, `ask`, `underlying_ms_of_day`, and `underlying_price` columns the OpenAPI publishes that the legacy `GreeksTick` was missing. `decode.rs::HEADER_ALIASES` gains `underlying_ms_of_day` -> `underlying_timestamp` so the wire Timestamp -> ms-of-day conversion flows through `row_number` on every Greeks endpoint. C ABI surface - Add `tdx_greeks_all_tick_array_free`, `tdx_greeks_first_order_tick_array_free`, `tdx_greeks_second_order_tick_array_free`, `tdx_greeks_third_order_tick_array_free`. Matching FFI array structs shipped on every binding. - Drop `tdx_greeks_tick_array_free`. Callers update to the typed free fn. No forwarding shim. Layout safety - Per-field `offset_of!` asserts in `crates/tdbe/src/types/tick.rs::layout_asserts` pin every observable Rust field offset that the C / Go FFI mirrors index into. Catches the field-order regression class that `size_of` checks miss (e.g. swapping two same-size fields keeps total size constant). - `QuoteTick.midpoint` now emits AFTER the `contract_id` triple in `tick_generated.rs` to match the legacy `tick.rs` order the C header and Go FFI mirror were already shipping. - Drop the unused `ffi_array_empty` row from the schema render block and `ffi_array_empty_expr` helper. The FFI emitter hard-codes `data: ptr::null()` for every `*const T`-pointer array; the row was dead and its `null_mut()` literal would have produced a const/mut mismatch if anyone wired it through. refs #474 * fix(mcp): map per-endpoint Greeks tick variants Wire serializers for GreeksAllTick / GreeksFirstOrderTick / GreeksSecondOrderTick / GreeksThirdOrderTick onto the matching EndpointOutput variants emitted by the generator. The MCP binary referenced the deleted GreeksTick / EndpointOutput::GreeksTicks names and failed to build. Add explicit cargo check for tools/mcp and tools/server in the Extended Surfaces job so out-of-workspace binaries can no longer silently regress on a workspace rename. * feat(tdbe): generate per-field offset asserts for every tick Promote the hand-written tdbe layout asserts to a generator-emitted crates/tdbe/src/types/tick_layout_asserts_generated.rs included from tick.rs. Coverage now includes size_of / align_of AND offset_of! for every column on every tick struct in tick_schema.toml -- including EodTick, OhlcTick, TradeQuoteTick, IvTick, MarketValueTick, OpenInterestTick, PriceTick, CalendarDay, InterestRateTick, which previously only had size/align checks. Extend the C++ layout-assert emitter to mirror the same offset table into sdks/cpp/include/tick_layout_asserts.hpp.inc as static_assert(offsetof(...) == ...) lines so a Rust struct field reorder fails the C++ build at the same point it fails cargo test. Adding a tick type to tick_schema.toml now picks up Rust + Go + C++ field-offset coverage automatically; no helper edit required. * fix(go): emit IV uniformly across every Greeks tick struct The legacy `("GreeksTick", "implied_volatility") => "IV"` special-case no longer fires after the type rename, so the four new per-order Greeks structs (`GreeksAllTick` / `GreeksFirstOrderTick` / `GreeksSecondOrderTick` / `GreeksThirdOrderTick`) fell through to the default `go_pascal_case` path and exposed `ImpliedVolatility` while the existing `IVTick` kept emitting `IV`. Replace the per-type arms with a single `(_, \"implied_volatility\") => \"IV\"` rule so every Greeks struct AND `IVTick` now expose the same Go field name. Renaming `IVTick.IV` was rejected as the larger source-breaking change; `IV` is the historical Go SDK convention since v3. * test(decode): value-check per-order Greeks parsers The new `parse_greeks_first_order_ticks`, `parse_greeks_second_order_ticks`, and `parse_greeks_third_order_ticks` parsers were only exercised by `is_empty()`-style coverage; the existing decode tests called `parse_greeks_all_ticks` against per-order column subsets, not the dedicated parsers. Add three tests that synthesise the exact wire shape each vendor endpoint publishes (column lists pinned to `items_option_snapshot_greeks_*_order` in `scripts/upstream_openapi.yaml`) and assert every decoded field -- including `bid`, `ask`, the underlying snapshot pair, and `date` -- against the input row's known values. A future regression in any field on any per-order parser surfaces here. * test(decode): assert HEADER_ALIASES values in greeks_all fixture The captured `option_history_greeks_all` fixture exercises two `HEADER_ALIASES` rewrites -- `implied_vol` -> `implied_volatility` and `underlying_timestamp` -> `underlying_ms_of_day` -- but the test only asserted the decoded `delta` and `iv_error` values; either alias could silently regress to default-zero without any test failing. Anchor the decoded `implied_volatility`, `underlying_ms_of_day`, and `underlying_price` (plus `date`) against known first-row values from the captured payload so an alias-table breakage surfaces here, not at runtime in production traffic. * test(frames): assert columns / dtypes / values for per-order Greeks The new GreeksFirstOrderTick / GreeksSecondOrderTick / GreeksThirdOrderTick frames coverage in test_frames_arrow.rs and test_frames_polars.rs was compile-time only -- the `every_tick_type_has_*_impl` checks proved the trait was implemented but no test asserted column count, exact column order, dtype per column, or any decoded value. Add three Arrow tests + three Polars tests, one per per-order Greeks struct, that pin: * total column count, * exact column order, * dtype per column (Int32 / Float64 / Utf8 or polars `String`), * one row's value for every column read back through the typed Arrow / Polars getters. A schema reorder, dtype regression, or per-column emitter typo on build_support/ticks/rust_frames.rs now surfaces here at test time. * test(decode): pin implied_vol alias decode against non-zero value (refs #474) Add a synthetic alias-decode test that drives `parse_greeks_all_ticks` with headers using ONLY the v3 server-side names (`implied_vol`, `underlying_timestamp`) and non-zero values (IV = 0.42, underlying ms-of-day = 34_200_000). A broken `implied_vol -> implied_volatility` alias would silently zero-default via `opt_float(None)` and the fixture-driven capture test cannot catch it because the recorded `first_row_implied_volatility` happens to be `0.0`. * fix(mcp): rename test code GreeksTick to GreeksAllTick CI gate `cargo test --manifest-path tools/mcp/Cargo.toml --locked` failed because `cargo check` (run locally) does not compile test code. Test target still imported the deleted `GreeksTick` and called the renamed `serialize_greeks_ticks`. Update sample helper to construct `GreeksAllTick` with the new `bid`/`ask`/`underlying_ms_of_day`/ `underlying_price` fields and rename two call sites. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cae1d38 commit 0992c92

55 files changed

Lines changed: 6610 additions & 1683 deletions

Some content is hidden

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

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ jobs:
9191
- run: python3 scripts/check_docs_consistency.py
9292
- run: python3 scripts/validate_agreement_test.py
9393
- run: cargo run -p thetadatadx --features config-file --bin generate_sdk_surfaces --locked -- --check
94+
- run: cargo check --manifest-path tools/mcp/Cargo.toml --locked
9495
- run: cargo clippy --manifest-path tools/mcp/Cargo.toml --locked -- -D warnings
9596
- run: cargo test --manifest-path tools/mcp/Cargo.toml --locked
97+
- run: cargo check --manifest-path tools/server/Cargo.toml --locked
9698
- run: cargo clippy --manifest-path tools/server/Cargo.toml --locked -- -D warnings
9799
- run: cargo test --manifest-path tools/server/Cargo.toml --locked
98100
- run: cargo check --manifest-path sdks/python/Cargo.toml --locked

CHANGELOG.md

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

1010
## [8.0.26] - 2026-05-05
1111

12+
### Breaking
13+
14+
- **`GreeksTick` removed in every language and on the C ABI.** The full
15+
union now ships as `GreeksAllTick` and the per-order endpoints return
16+
typed subsets. Callers update imports and method return types -- no
17+
forwarding shim. Renames:
18+
- Rust / Python / TypeScript / Go / C++ `GreeksTick` -> `GreeksAllTick`
19+
(full union returned by `option_*_greeks_all` and
20+
`option_*_greeks_eod`).
21+
- C ABI free fn `tdx_greeks_tick_array_free` -> `tdx_greeks_all_tick_array_free`.
22+
- Endpoints now returning `GreeksFirstOrderTick`:
23+
`option_snapshot_greeks_first_order`,
24+
`option_history_greeks_first_order`,
25+
`option_history_trade_greeks_first_order`.
26+
- Endpoints now returning `GreeksSecondOrderTick`:
27+
`option_snapshot_greeks_second_order`,
28+
`option_history_greeks_second_order`,
29+
`option_history_trade_greeks_second_order`.
30+
- Endpoints now returning `GreeksThirdOrderTick`:
31+
`option_snapshot_greeks_third_order`,
32+
`option_history_greeks_third_order`,
33+
`option_history_trade_greeks_third_order`.
34+
- `GreeksAllTick` adds `bid`, `ask`, `underlying_ms_of_day`,
35+
`underlying_price` columns the upstream OpenAPI publishes but the
36+
legacy `GreeksTick` did not carry. Field offset of every existing
37+
Greek shifts by 16 bytes (bid + ask) on the FFI mirror; rebuild any
38+
binary that links the C struct.
39+
40+
### Added
41+
42+
- **Per-endpoint typed Greeks structs.** The vendor's
43+
`option_*_greeks_first_order`, `_second_order`, `_third_order`
44+
endpoints emit strict subsets of the full Greek column set. The SDK
45+
now exposes `GreeksFirstOrderTick` (delta / theta / vega / rho /
46+
epsilon / lambda + bid/ask + IV pair + underlying snapshot),
47+
`GreeksSecondOrderTick` (gamma / vanna / charm / vomma / veta + bid/
48+
ask + IV pair + underlying snapshot), and `GreeksThirdOrderTick`
49+
(speed / zomma / color / ultima + bid/ask + IV pair + underlying
50+
snapshot). Each per-order endpoint returns `Vec<<Type>>` directly --
51+
no zero-default columns leak from one subset into another.
52+
- New C ABI free symbols: `tdx_greeks_all_tick_array_free`,
53+
`tdx_greeks_first_order_tick_array_free`,
54+
`tdx_greeks_second_order_tick_array_free`,
55+
`tdx_greeks_third_order_tick_array_free`. Matching FFI array types
56+
emitted on every binding (`TdxGreeksAllTickArray`,
57+
`TdxGreeksFirstOrderTickArray`, etc.).
58+
- New header alias `underlying_ms_of_day` -> `underlying_timestamp` in
59+
`crates/thetadatadx/src/decode.rs::HEADER_ALIASES` so the wire
60+
Timestamp -> ms-of-day conversion flows through the standard
61+
`row_number` path on every Greeks endpoint.
62+
- Per-field `offset_of!` layout assertions in
63+
`crates/tdbe/src/types/tick.rs::layout_asserts`. Field-offset drift
64+
(e.g. swapping two same-size fields) sneaks past `size_of` /
65+
`align_of` checks alone -- the new asserts pin every observable Rust
66+
field offset that the C / Go FFI mirrors index into.
67+
68+
### Changed
69+
70+
- **Generated `tdbe::types::tick` struct definitions.**
71+
`crates/tdbe/src/types/tick_generated.rs` is now emitted by
72+
`generate_sdk_surfaces` from `tick_schema.toml`. The hand-written
73+
`tick.rs` keeps `impl_contract_id!` macro applications, the
74+
`TradeTick` flag helpers, and `OptionContract::is_call` /
75+
`is_put` -- everything else flows from the schema. Adding a new tick
76+
type means adding one `[types.X]` row.
77+
- **Schema-driven C++ layout asserts and Go FFI sizes.**
78+
`sdks/cpp/include/tick_layout_asserts.hpp.inc` and
79+
`sdks/go/tick_ffi_sizes_generated.go` now compute every struct's
80+
size + alignment from the schema (via `tick_ffi_size_and_align`)
81+
rather than each emitter dispatching on the type name. Adding a tick
82+
type to `tick_schema.toml` produces the size/align pair, the C++
83+
`static_assert`, and the Go `unsafe.Sizeof` test entry without any
84+
`build_support/ticks/{cpp,go}.rs` edit.
85+
- `OpenInterestTick` and `TradeQuoteTick` gained the missing `align = 64`
86+
directive in `tick_schema.toml`. The schema now matches the
87+
`#[repr(C, align(64))]` declared on the corresponding `tdbe` types --
88+
the schema-derived FFI size used to under-count by the alignment
89+
rounding (32/144 vs 64/192) before reaching the C++ layout assert.
90+
1291
### Fixed
1392

1493
- **`option_*_greeks_*_order` no longer spams `expected column header
@@ -30,6 +109,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
30109

31110
### Changed
32111

112+
- **TOML-driven render map collapses 19 hand-coded helper match arms
113+
into single-key lookups.** Every per-language binding name a renderer
114+
needs for one tick type — Rust direct-client return type, generated
115+
parser fn, Go struct + converter, FFI array struct + free fn + output
116+
variant + header-return type, C++ value type, six Python converters
117+
(dict, columnar, pyclass-list, pyclass-list-class, vec-to-pylist,
118+
slice-arrow), TypeScript class + class-vec converter, and the Python
119+
pyclass struct name — moves into `[types.X.render]` blocks in
120+
`crates/thetadatadx/tick_schema.toml`. The 20 helper functions that
121+
previously enumerated those names by hand
122+
(`build_support/endpoints/helpers.rs::direct_return_type` and friends,
123+
plus `build_support/ticks/mod.rs::pyclass_name`) become single
124+
HashMap lookups against a `OnceLock`-cached load of the schema.
125+
Adding a tick type now requires one TOML row -- no helper edits. The
126+
generated SDK surfaces are byte-identical against `main` because the
127+
TOML rows reproduce the names the helpers previously hardcoded.
33128
- Per-endpoint vendor-schema column lists for the four Greeks families
34129
pinned and documented in `tick_schema.toml::GreeksTick` against the
35130
upstream OpenAPI capture in `scripts/upstream_openapi.yaml`. The

0 commit comments

Comments
 (0)