You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Feature-by-feature comparison of the Rust SDK against the Java terminal's
behavior. [✓] = parity, [✗] = intentional deviation (documented),
[~] = partial / in progress.
Vocabulary note. The Java terminal exposes contract fields under the v2
wire names (root, expDate). The Rust SDK exposes them under the
post-#484 v3 vocabulary (symbol, expiration); see CHANGELOG entry for
v8.0.28. The wire codec is unchanged — the rename is API-side only — so any
root / expDate references on the Java side of a comparison row are kept
verbatim, and the Rust counterpart is given in the post-rename form.
Last audited: 2026-04-03 against the Java terminal v202603181.
Coverage: all 21 StreamMsgType codes, all 19 RemoveReason codes, all 4
StreamResponseType codes, all 4 SecType codes, all 30 ReqType codes,
TradeTick (16-field), TradeRef (8-field), QuoteTick (11-field),
OhlcTick (9-field), OpenInterestTick (3-field), OHLCVC derivation,
Contract serialization, FIT codec, PriceCalcUtils, FPSSClient
lifecycle, WSEvents trade output.
Wire protocol
Feature
Parity
Notes
gRPC proto definitions (field numbers, types, service methods)
[✓]
Canonical mdds.proto from ThetaData engineering.
FPSS frame layout (1-byte LEN + 1-byte CODE + payload)
FPSS credential length read as unsigned (readUnsignedShort)
[✓]
FPSS write buffer flushed only on PING (batched writes)
[✓]
FPSS ROW_SEP resets field index to SPACING unconditionally
[✓]
FPSS contract ID extracted via FIT decode
[✓]
FPSS delta state cleared on START/STOP signals
[✓]
"client": "terminal" in gRPC query_parameters
[✓]
Price encoding formula (value * 10^(type - 10))
[✓]
All enum codes (StreamMsgType, RemoveReason, SecType, DataType, ...)
[✓]
Authentication
Feature
Parity
Notes
StreamMsgType::Credentials plaintext email+password over TLS
[✓]
Nexus auth URL, terminal key, request/response format
[✓]
Nexus 401/404 handling treated as invalid credentials
[✓]
SubjectPublicKeyInfo (SPKI) pinning on FPSS TLS
[✗]
Intentional improvement — Java trusts the system CA store, which accepts an expired FPSS certificate and would let any system-trusted cert impersonate the server. The Rust verifier pins on the SPKI digest and enforces a hostname allowlist.
Connection lifecycle
Feature
Parity
Notes
Embedded library vs standalone daemon
[✗]
Rust is an in-process library; Java launches as a JVM daemon exposing REST/WS. Same connection longevity, no IPC overhead.
Unified ThetaDataDx::connect (auth + MDDS + FPSS in one client)
[✗]
Intentional improvement — one auth call, persistent gRPC channel, lazy FPSS connection that stays alive until stop_streaming() or Drop.
MDDS gRPC endpoint (mdds-01.thetadata.us:443)
[✓]
FPSS server list (nj-a:20000/20001, nj-b:20000/20001)
[✓]
Connect timeout
[✓]
Java uses socket.connect(addr, 2000) covering TCP+TLS. Rust splits into separate TCP and TLS timeouts, both wrapped in tokio::time::timeout. Same effective behavior.
Read timeout (10 s)
[✓]
socket.setSoTimeout(10000) vs tokio::time::timeout around read_frame().
Auth HTTP timeout
[~]
Java uses 5 s connect + 5 s read; Rust uses 5 s connect + 10 s total request. Slightly more generous.
DNS hostname resolution
[✓]
Both accept hostnames and IPs; Rust uses ToSocketAddrs (matches Java's InetSocketAddress).
TLS stack
[✗]
Java uses JSSE + cacerts; Rust uses rustls (ring backend) + webpki-roots. Same TLS 1.2/1.3, different implementation.
Java always auto-reconnects (except on AccountAlreadyConnected); Rust exposes reconnect() as a caller-driven method for explicit retry/backoff control. reconnect_delay() helper matches Java's delay calculation.
TooManyRequests -> 130 s backoff
[✓]
Permanent auth errors -> no retry
[✗]
Rust treats 7 reasons as permanent (InvalidCredentials, InvalidLoginValues, InvalidLoginSize, AccountAlreadyConnected, FreeAccount, ServerUserDoesNotExist, InvalidCredentialsNullUser); Java only stops on AccountAlreadyConnected. Avoids burning rate limits on bad credentials.
Resubscribe active contracts after reconnect
[✓]
Endpoint generation
Aspect
Java
Rust
Handler structure
Each of the current gRPC handlers hand-coded with per-endpoint request/response logic
The full current endpoint surface is generated from endpoint_surface.toml + mdds.proto, plus the convenience range variant; MddsClient macros remain an internal expansion target
Rationale: the Java terminal duplicates boilerplate (auth injection,
QueryInfo setup, response streaming, zstd decompression) across 60 handlers.
thetadatadx centralizes the endpoint contract in a checked-in surface spec,
validates it against the wire contract, and generates the registry/runtime/
client projections from that data. Requests remain wire-identical.
FPSS streaming
Feature
Parity
Notes
Dispatch model (LMAX Disruptor pattern)
[✓]
Java: LMAX Disruptor ring buffer. Rust: disruptor-rs v4 — lock-free, bounded-latency, cache-line-padded sequence counters. FPSS I/O thread is fully synchronous.
Ring-buffer capacity monitoring
[~]
Java's RingBuffer.remainingCapacity() enables back-pressure warnings; disruptor-rs v4 does not expose a fill-level API. Known upstream limitation.
FpssEvent split (FpssData + FpssControl)
[✗]
Intentional API improvement — enables exhaustive match on data-only events without touching lifecycle events. Wire format unchanged.
FPSS streaming prices exposed as f64
[✗]
Intentional improvement — Rust decodes prices at frame-parse time using the per-cell price_type. Java exposes raw integers + price_type and requires callers to invoke PriceCalcUtils manually.
Contract::option(symbol, expiration, strike, right) API
[✗]
Intentional improvement — Rust accepts string inputs matching MDDS historical ("SPY", "20260417", "550", "C"). Java's Contract(root, expDate, isCall, strike) leaks wire-format details. A typed IntoOptionSpec constructor is planned for 9.0.0 to replace the deferred Contract::option_raw() shape used by the drop-in server.
FPSS subscription tracking
[✗]
Rust: per-instance Mutex. Java: static ConcurrentHashMap shared across all FPSSClient instances in the JVM. Rust isolates subscription state per client.
Pragmatic improvement for dev-server usability. Rust detects non-printable bytes in ERROR frames and skips the frame; Java logs garbled strings. Text error messages are still surfaced as FpssControl::ServerError.
OHLCVC::processTrade field extraction (price, priceType, size)
[✓]
Different index paths (Rust indexes into the FIT-decoded array; Java indexes into a pre-parsed trade tick array), same values.
OHLCVC volume/count use i64
[✗]
Java uses int (32-bit) and wraps silently on high-volume symbols. Rust uses i64 — correct values on symbols like SPY that exceed i32::MAX cumulative volume.
OHLCVC-from-trade derivation
[✓]
Default on, opt-out via DirectConfig::derive_ohlcvc = false. Java always derives with no toggle.
TradeRef 8-field vs 16-field auto-detection
[✗]
thetadatadx detects the field count from the first absolute tick per (msg_type, contract_id) and dispatches to the correct index mapping. Java's TradeRef.java hard-codes 8-field indices and applies them to 16-field arrays.
FIT overflow handling
[✗]
Java wraps int silently; Rust saturates i64 accumulator to i32::MAX/MIN. Real market data never exceeds i32 range.
PriceCalcUtils.getPriceDouble() formula (DOUBLES[pType] * price)
[✓]
Price decoding: f64 at parse time
[✗]
Intentional improvement — Rust decodes every Price cell to f64 individually using the cell's own price_type. No price_type in the public API. Java exposes raw integers + price_type and leaves decoding to callers.
Greeks
Feature
Parity
Notes
Operator precedence on all formulas
[✓]
Fixed in v1.2.0 to match Java bytecode. Higher-order Greeks (veta, speed, zomma, color, dual_gamma) follow canonical textbook formulas (decompilers lose parenthesization).
Rust uses .exp() (hardware); Java uses Math.pow(Math.E, x) which inserts a ln(e) multiply. ~1 ULP precision improvement.
Degenerate-input guard (t=0, v=0)
[✗]
Rust returns 0.0 (or intrinsic value for value()); Java returns NaN/Inf. Prevents silent corruption of downstream portfolio analytics.
Precomputed intermediates in all_greeks()
[✗]
Numerically identical to independent calls. Java recomputes d1/d2 per-Greek; Rust precomputes once in all_greeks(). ~20x fewer transcendental function calls.
Validation
Feature
Parity
Notes
Contract symbol length check
[✗]
Rust: assert!(symbol.len() <= 244). Java: silent as byte truncation on the root field.
Price-type range check
[✓]
Both enforce 0 <= type < 20.
Frame payload size
[✗]
Rust: assert!(payload.len() <= 255) in release. Java: implicit u8 truncation.
Date format validation (8 ASCII digits)
[✗]
Rust validates client-side in mdds/validate.rs::validate_date(). Java relies on server-side rejection.
Error handling
Feature
Parity
Notes
CONTRACT parse failure surfaced to caller
[✗]
Rust emits FpssEvent::Error. Java logs and silently drops.
REQ_RESPONSE parse failure surfaced to caller
[✗]
Same as above.
Truncated frame header treated as fatal
[✓]
EOFException vs Error::FpssProtocol, both error out.
Concurrency
Feature
Parity
Notes
Concurrent request limit (2^tier)
[✓]
Derived from the Nexus auth response tier, with manual override via DirectConfig::mdds_concurrent_requests.
QueryInfo fields
Field
Java
Rust
Parity
terminal_git_commit
Build git hash
Empty string
[✗]
client_type
Empty
"rust-thetadatadx"
[✗]
terminal_version
Empty
Crate version
[✗]
Rust sets client_type/terminal_version to help ThetaData's server-side
telemetry distinguish Rust SDK requests. Server accepts both populated and
empty forms.
Endpoint defaults
Feature
Parity
Notes
start_time="09:30:00" / end_time="16:00:00" on interval endpoints
[✓]
Matches Java (added v4.2.0).
venue="nqb" on stock snapshot + intraday history endpoints
Server accepts both; wire value differs (normalize_interval() in mdds/endpoints.rs).
Response streaming
Feature
Parity
Notes
collect_stream — materialize to typed Vec<Tick>
[~]
Java interposes ArrayBlockingQueue(2) between gRPC thread and HTTP writer; Rust has no HTTP writer. collect_stream uses an original_size pre-allocation hint.
for_each_chunk — streaming callback
[✗]
Intentional improvement — avoids full materialization for very large responses.
_stream endpoint variants
[✗]
SDK-only convenience — extend the for_each_chunk model to per-endpoint helpers. Ideal for millions-of-rows responses.
Higher-level SDKs convert at the language boundary to match user
expectations; the Rust core preserves the raw integer for zero-overhead C
interop.
v2 -> v3 automatic normalizations
These conversions happen automatically in the Rust SDK so callers can pass
either v2-style or v3-style parameter values.
Right
v2
v3
Where
"C" / "c"
"call"
normalize_right() in wire_semantics.rs
"P" / "p"
"put"
normalize_right() in wire_semantics.rs
"*"
"both"
normalize_right() in wire_semantics.rs
Interval
v2 (ms)
v3
Where
"60000"
"1m"
normalize_interval() in mdds/endpoints.rs
"1000"
"1s"
normalize_interval() in mdds/endpoints.rs
"300000"
"5m"
normalize_interval() in mdds/endpoints.rs
already shorthand
pass-through
normalize_interval() in mdds/endpoints.rs
Symbol field
The v3 protobuf uses symbol in ContractSpec (not root as in v2). The
Rust SDK has always used symbol in its public API and proto definitions —
no conversion needed.
start_time / end_time
The v2 rth boolean is replaced by explicit start_time/end_time. The
Rust SDK defaults to "09:30:00"/"16:00:00" on all interval endpoints.
Intentional deviations (value-adds over Java)
SPKI pinning — authenticates the FPSS server on its public key alone,
not on the expired certificate chain.
Typed event surface across 4 SDKs — Java's API is untyped and
callback-based; the Rust core exposes typed FpssEvent variants across
Python / TypeScript / C++ / Rust.
Arrow columnar DataFrame adapter — Java has no DataFrame integration;
Python's to_arrow() / to_pandas() / to_polars() pipe through
zero-copy Arrow buffers.
Sub-millisecond decode path — no JVM warmup, no GC pauses; nibble-
packed FIT decoder and lock-free ring buffer on the streaming path.
Zero-copy FFI across Python / TypeScript / C++ — one extern "C"
ABI shared by all non-Rust SDKs (and available to any third-party
C-interop language).
Unified ThetaDataDx client — auth, MDDS, and FPSS behind a single
long-lived handle with Deref<Target=MddsClient> for historical
methods.
Manual reconnect policy — explicit control over retry policy, backoff
strategy, and circuit breaking. reconnect_delay() helper matches Java's
timing if desired.
Stricter permanent-disconnect handling — 7 reason codes treated as
fatal vs Java's 1; avoids futile reconnect loops on bad credentials.
Per-instance subscription state — prevents cross-contamination between
multiple clients in the same process.
i64 OHLCVC counters — correct cumulative volume on high-volume
symbols (SPY, QQQ) where int would wrap.
FIT overflow saturation — preserves sign and makes overflow
detectable rather than silently corrupting tick data.
Binary error-frame skipping — dev-server replay-loop boundary leaks
raw FIT tick data into ERROR frames; Rust skips binary payloads
instead of logging them as garbled strings.
f64 prices at parse time — no price_type in the public API; every
Price cell is decoded using its own price_type.
Typed Contract::option(symbol, expiration, strike, right) API —
strings matching the MDDS historical API; no wire-format leakage.
Class-level mapping
For a complete enumeration of Java terminal classes and their Rust
equivalents (or why they're not needed), see the table below. It covers all
588 classes in the reference v202603181 build.
Core protocol (implemented)
Java class
Rust equivalent
Notes
fpssclient/FPSSClient.java
fpss/mod.rs
Full streaming client with disruptor-rs ring buffer
fpssclient/Contract.java
fpss/protocol.rs::Contract
Wire serialization matches byte-for-byte
fpssclient/OHLCVC.java
fpss/mod.rs::OhlcvcAccumulator
Derives OHLCVC from trade stream
fpssclient/PacketStream.java
fpss/framing.rs
Frame read/write [len:u8][code:u8][payload]
fpssclient/StreamPacket.java
fpss/framing.rs::Frame
Frame struct
fie/FITReader.java
tdbe::codec::fit
FIT nibble decoder (738 LOC)
FIE.java
tdbe::codec::fie
FIE nibble encoder
fie/TickIterator.java
Inline in fpss/mod.rs::decode_frame()
Tick iteration over FIT-decoded rows
grpc/GrpcHttpStreamBridge.java
mdds/client.rs
gRPC response streaming (direct to typed structs, no HTTP bridge)
grpc/AbstractGrpcBridge.java
mdds/client.rs::collect_stream()
Base response collection
grpc/GrpcMcpBridge.java
tools/mcp/ (separate crate)
MCP integration
auth/UserAuthenticator.java
auth/nexus.rs
Nexus API auth flow
config/CredentialFileParser.java
auth/creds.rs
creds.txt parsing
config/ConfigurationManager.java
config.rs::DirectConfig
Server addresses, timeouts
config/BuildInfo.java
CARGO_PKG_VERSION constant
Version identification
math/Greeks.java
tdbe::greeks
22 Black-Scholes Greeks + IV solver
RestResource.java
mdds/endpoints.rs
REST-to-gRPC bridge, contains all endpoint defaults (venue, start_time, interval)
These classes exist because the Java terminal runs as a standalone daemon
process with an embedded HTTP server. The Rust SDK is an embedded library —
users call it directly. No HTTP server, no WebSocket server, no CLI daemon.
The generated/ and generated/v3grpc/ directories in the Java terminal
contain 497 protobuf-generated classes (Request/Response/OrBuilder types for
every RPC). The Rust equivalent is tonic::include_proto!() output from
mdds.proto.
Package
Class count
Rust equivalent
generated/ (v2 proto)
~250
Historical; superseded by mdds.proto
generated/v3grpc/ (v3 proto)
~247
proto module via tonic::include_proto!("beta_endpoints")