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
137 changes: 137 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,143 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [9.0.0] - 2026-05-07

### Breaking

- **`Contract::option` is now polymorphic; `Contract::option_raw` is gone.**
A new sealed `IntoOptionSpec` trait accepts either `(&str, &str, &str)`
(human-friendly: expiration / strike / right) or `(i32, bool, i32)`
(wire-format integer triple). Callers pass one tuple instead of four
loose arguments, and the wire-format constructor moves under the same
method name.

```rust
// Before:
let c = Contract::option("SPY", "20261218", "60", "C")?;
let c = Contract::option_raw("SPY", 20261218, true, 60_000);

// After:
let c = Contract::option("SPY", ("20261218", "60", "C"))?;
let c = Contract::option("SPY", (20261218, true, 60_000))?;
```

- **`FpssClient::connect` takes one `FpssConnectArgs` struct instead of
seven loose arguments.** The struct exposes `creds`, `hosts`,
`ring_size`, `flush_mode`, `policy`, `derive_ohlcvc`. `Default` and a
`new(creds, hosts)` shortcut cover the common path.

```rust
// Before:
FpssClient::connect(&creds, &hosts, 4096, FpssFlushMode::default(),
ReconnectPolicy::default(), true, handler)?;

// After:
let args = FpssConnectArgs::new(&creds, &hosts);
FpssClient::connect(args, handler)?;
```

- **Wire-internal `contract_id: i32` removed from every public surface.**
Dropped from Rust (`ThetaDataDx::contract_map`, `ThetaDataDx::contract_lookup`,
`FpssClient::contract_map`, `FpssClient::contract_lookup`), C ABI
(`tdx_unified_contract_map`, `tdx_unified_contract_lookup`,
`tdx_fpss_contract_map`, `tdx_fpss_contract_lookup`,
`tdx_contract_map_array_free`, `TdxContractMapArray`, `TdxContractMapEntry`),
Python (`contract_map()`, `contract_lookup()`), TypeScript
(`contractMap()`, `contractLookup()`), and C++ (`FpssClient::contract_map`,
`FpssClient::contract_lookup`). Users identify contracts by
`(symbol, expiration, right, strike)`; the wire id stays inside the
reader-thread cache and is delivered alongside every event via
`FpssControl::ContractAssigned { id, contract }` for callers that
still need to maintain their own id→contract map.

```rust
// Before:
let map = client.contract_map()?;
if let Some(c) = client.contract_lookup(id)? { ... }

// After: build the map yourself from the event stream.
client.start_streaming(|event| {
if let FpssEvent::Control(FpssControl::ContractAssigned { id, contract }) = event {
my_map.insert(*id, Arc::clone(contract));
}
})?;
```

- **`pub mod proto` is now `pub(crate)`.** Generated protobuf types are
wire-internal. Bindings that need `DataTable` / `DataValueList` /
`ResponseData` / `Price` / `data_value::*` go through the new
`thetadatadx::wire` re-export, which surfaces only the types
offline-decode harnesses actually need.

```rust
// Before:
use thetadatadx::proto::{DataTable, ResponseData};

// After:
use thetadatadx::wire::{DataTable, ResponseData};
```

- **FPSS submodules `connection`, `framing`, `dispatcher`, `ring`
reduced to `pub(crate)`.** Only `protocol` remains a public submodule
of `fpss`. `Frame`, `read_frame`, `write_frame` are surfaced as items
at `thetadatadx::fpss::` for benchmark consumers; everything else
(TLS connect, ring-buffer wait strategies, dispatcher internals) is
now crate-private.

```rust
// Before:
use thetadatadx::fpss::framing::{read_frame, write_frame, Frame};

// After:
use thetadatadx::fpss::{read_frame, write_frame, Frame};
```

### Added

- `IntoOptionSpec` sealed trait + impls for `(&str, &str, &str)` and
`(i32, bool, i32)` — see `fpss::protocol::IntoOptionSpec`.
- `FpssConnectArgs` struct + `FpssConnectArgs::new(creds, hosts)`
shortcut.
- `thetadatadx::wire` module — the supported re-export surface for the
generated protobuf payload types (`DataTable`, `DataValueList`,
`DataValue`, `ResponseData`, `Price`, `CompressionAlgo`,
`CompressionDescription`, `data_value`).

### Changed

- **Comprehensive public-API discipline sweep.** `auth::{creds, nexus,
session}` reduced to `pub(crate)`; user-facing types (`Credentials`,
`AuthResponse`, `AuthUser`, `SessionToken`, `authenticate`,
`authenticate_at`) re-exported at `thetadatadx::auth::*` and the
crate root. Every `pub fn` / `pub struct` reachable from a public
path was audited; internal helpers (TLS connect entry points,
ring-size constants, framing reader idle predicates) are now
crate-private.
- `tdbe` 0.12.10 → 0.13.0 (eastern-time + json_canon + conditions
codegen surface expansion warrants the minor bump).

### Removed

- `Contract::option_raw` (folded into `Contract::option` via
`IntoOptionSpec`).
- `ThetaDataDx::contract_map`, `ThetaDataDx::contract_lookup`,
`FpssClient::contract_map`, `FpssClient::contract_lookup`.
- C ABI: `tdx_unified_contract_map`, `tdx_unified_contract_lookup`,
`tdx_fpss_contract_map`, `tdx_fpss_contract_lookup`,
`tdx_contract_map_array_free`, `TdxContractMapArray`,
`TdxContractMapEntry`.
- Python SDK: `ThetaDataDx.contract_map`, `ThetaDataDx.contract_lookup`.
- TypeScript SDK: `ThetaDataDx.contractMap`, `ThetaDataDx.contractLookup`.
- C++ SDK: `FpssClient::contract_map`, `FpssClient::contract_lookup`.
- `pub mod proto` (now `pub(crate)`; consumers use `thetadatadx::wire`).
- `pub mod fpss::{connection, framing, dispatcher, ring}` (now
`pub(crate)`; surfaces preserved as items at `fpss::` root where
needed).
- Dead helpers removed: `fpss::connection::connect_to`,
`fpss::framing::FrameReadState::is_idle`,
`fpss::ring::DEFAULT_RING_SIZE`.

## [8.0.37] - 2026-05-07

### Added
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/tdbe/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tdbe"
version = "0.12.10"
version = "0.13.0"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions crates/thetadatadx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "thetadatadx"
version = "8.0.37"
version = "9.0.0"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
Expand Down Expand Up @@ -40,7 +40,7 @@ frames = ["polars", "arrow"]
live-tests = []

[dependencies]
tdbe = { version = "0.12.10", path = "../tdbe" }
tdbe = { version = "0.13.0", path = "../tdbe" }

# gRPC + protobuf (tonic 0.14 extracted prost codec into tonic-prost)
tonic = { version = "=0.14.5", features = ["tls-ring", "tls-native-roots", "channel", "transport"] }
Expand Down Expand Up @@ -149,7 +149,7 @@ prost-build = "=0.14.3"
regex = "1.12.3"
toml = "1.1.2"
serde = { version = "1.0.228", features = ["derive"] }
tdbe = { version = "0.12.10", path = "../tdbe" }
tdbe = { version = "0.13.0", path = "../tdbe" }

[[bench]]
name = "bench_decode"
Expand Down
2 changes: 1 addition & 1 deletion crates/thetadatadx/benches/bench_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use thetadatadx::decode::{
decode_data_table, decompress_response, extract_number_column, extract_price_column,
parse_ohlc_ticks, parse_quote_ticks, parse_trade_ticks,
};
use thetadatadx::proto;
use thetadatadx::wire as proto;

// ═══════════════════════════════════════════════════════════════════════════
// Helpers
Expand Down
2 changes: 1 addition & 1 deletion crates/thetadatadx/benches/bench_framing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::hint::black_box;
use std::io::Cursor;

use tdbe::types::enums::StreamMsgType;
use thetadatadx::fpss::framing::{read_frame, write_frame, Frame};
use thetadatadx::fpss::{read_frame, write_frame, Frame};

// ═══════════════════════════════════════════════════════════════════════════
// FPSS framing benchmarks
Expand Down
8 changes: 4 additions & 4 deletions crates/thetadatadx/benches/bench_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn bench_contract_stock_to_bytes(c: &mut Criterion) {
}

fn bench_contract_option_to_bytes(c: &mut Criterion) {
let contract = Contract::option("SPY", "20261218", "60", "C").unwrap();
let contract = Contract::option("SPY", ("20261218", "60", "C")).unwrap();
c.bench_function("contract_option_to_bytes", |b| {
b.iter(|| {
black_box(black_box(&contract).to_bytes());
Expand All @@ -26,7 +26,7 @@ fn bench_contract_option_to_bytes(c: &mut Criterion) {
}

fn bench_contract_from_bytes(c: &mut Criterion) {
let contract = Contract::option("SPY", "20261218", "60", "C").unwrap();
let contract = Contract::option("SPY", ("20261218", "60", "C")).unwrap();
let bytes = contract.to_bytes();
c.bench_function("contract_from_bytes", |b| {
b.iter(|| {
Expand All @@ -36,7 +36,7 @@ fn bench_contract_from_bytes(c: &mut Criterion) {
}

fn bench_contract_roundtrip(c: &mut Criterion) {
let contract = Contract::option("AAPL", "20261220", "17.5", "P").unwrap();
let contract = Contract::option("AAPL", ("20261220", "17.5", "P")).unwrap();
c.bench_function("contract_roundtrip", |b| {
b.iter(|| {
let bytes = black_box(&contract).to_bytes();
Expand All @@ -58,7 +58,7 @@ fn bench_build_credentials_payload(c: &mut Criterion) {
}

fn bench_build_subscribe_payload(c: &mut Criterion) {
let contract = Contract::option("SPY", "20261218", "60", "C").unwrap();
let contract = Contract::option("SPY", ("20261218", "60", "C")).unwrap();
c.bench_function("build_subscribe_payload", |b| {
b.iter(|| {
let _ = black_box(build_subscribe_payload(black_box(42), black_box(&contract)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
/// semantics).
fn decode_chunks_into_table(
chunks: &[&[u8]],
) -> PyResult<thetadatadx::proto::DataTable> {
) -> PyResult<thetadatadx::wire::DataTable> {
let mut headers: Vec<String> = Vec::new();
let mut rows: Vec<thetadatadx::proto::DataValueList> = Vec::new();
let mut rows: Vec<thetadatadx::wire::DataValueList> = Vec::new();
for (idx, chunk) in chunks.iter().enumerate() {
let response = <thetadatadx::proto::ResponseData as prost::Message>::decode(*chunk)
let response = <thetadatadx::wire::ResponseData as prost::Message>::decode(*chunk)
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"decode_response_bytes: chunk {idx} is not a valid proto::ResponseData: {e}"
Expand All @@ -27,7 +27,7 @@ fn decode_chunks_into_table(
}
rows.extend(table.data_table);
}
Ok(thetadatadx::proto::DataTable {
Ok(thetadatadx::wire::DataTable {
headers,
data_table: rows,
})
Expand Down
12 changes: 0 additions & 12 deletions crates/thetadatadx/build_support/sdk_surface/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@ fn cpp_fpss_decl(method: &MethodSpec) -> String {
writeln!(out, " int {}({params});", method.name).unwrap();
}
MethodKind::IsAuthenticated => out.push_str(" bool is_authenticated() const;\n"),
MethodKind::ContractLookup => {
out.push_str(" std::optional<std::string> contract_lookup(int id) const;\n");
}
MethodKind::ContractMap => {
out.push_str(" std::map<int32_t, std::string> contract_map() const;\n");
}
MethodKind::ActiveSubscriptions => {
out.push_str(" std::vector<Subscription> active_subscriptions() const;\n");
}
Expand Down Expand Up @@ -141,12 +135,6 @@ fn cpp_fpss_def(method: &MethodSpec) -> String {
"bool FpssClient::is_authenticated() const { return tdx_fpss_is_authenticated(handle_.get()) != 0; }\n"
.to_string()
}
MethodKind::ContractLookup => {
include_str!("templates/cpp/contract_lookup_def.cpp.tmpl").to_string()
}
MethodKind::ContractMap => {
include_str!("templates/cpp/contract_map_def.cpp.tmpl").to_string()
}
MethodKind::ActiveSubscriptions => {
include_str!("templates/cpp/active_subscriptions_def.cpp.tmpl").to_string()
}
Expand Down
30 changes: 1 addition & 29 deletions crates/thetadatadx/build_support/sdk_surface/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn python_streaming_method(method: &MethodSpec) -> String {
out.push_str(" ) -> PyResult<()> {\n");
writeln!(
out,
" let contract = fpss::protocol::Contract::option({}, {}, {}, {}).map_err(to_py_err)?;",
" let contract = fpss::protocol::Contract::option({}, ({}, {}, {})).map_err(to_py_err)?;",
method.params[0].name,
method.params[1].name,
method.params[2].name,
Expand Down Expand Up @@ -208,34 +208,6 @@ fn python_streaming_method(method: &MethodSpec) -> String {
.unwrap();
out.push_str(" }\n");
}
MethodKind::ContractMap => {
writeln!(
out,
" fn {}(&self) -> PyResult<std::collections::HashMap<i32, String>> {{",
method.name
)
.unwrap();
out.push_str(" self.tdx\n");
out.push_str(" .contract_map()\n");
out.push_str(" .map(|m| m.into_iter().map(|(id, c)| (id, format!(\"{c}\"))).collect())\n");
out.push_str(" .map_err(to_py_err)\n");
out.push_str(" }\n");
}
MethodKind::ContractLookup => {
let param = &method.params[0];
writeln!(
out,
" fn {}(&self, {}: {}) -> PyResult<Option<String>> {{",
method.name,
param.name,
python_type(param.param_type)
)
.unwrap();
writeln!(out, " self.tdx.contract_lookup({})", param.name).unwrap();
out.push_str(" .map(|opt| opt.map(|c| format!(\"{c}\")))\n");
out.push_str(" .map_err(to_py_err)\n");
out.push_str(" }\n");
}
MethodKind::ActiveSubscriptions => {
writeln!(
out,
Expand Down
9 changes: 0 additions & 9 deletions crates/thetadatadx/build_support/sdk_surface/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ pub(super) enum MethodKind {
StockContractCall,
OptionContractCall,
FullCall,
ContractMap,
ContractLookup,
ActiveSubscriptions,
NextEvent,
Reconnect,
Expand Down Expand Up @@ -235,13 +233,6 @@ fn validate_method_spec(method: &MethodSpec) -> Result<(), Box<dyn std::error::E
false,
&[("sec_type", ParamType::String)],
),
MethodKind::ContractMap => (Some("contract_map"), &[PY, TS, CPP], false, &[]),
MethodKind::ContractLookup => (
Some("contract_lookup"),
&[PY, TS, CPP],
false,
&[("id", ParamType::I32)],
),
MethodKind::ActiveSubscriptions => {
(Some("active_subscriptions"), &[PY, TS, CPP], false, &[])
}
Expand Down

This file was deleted.

Loading
Loading