From 0671c4e20739b95cbf902d2c70a74098464724bd Mon Sep 17 00:00:00 2001 From: userFRM Date: Wed, 6 May 2026 11:36:11 +0200 Subject: [PATCH 1/3] refactor!: remove Go SDK (closes #481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cgo bridge between Rust's C ABI and Go's runtime carries per-call overhead that masks the upstream throughput this SDK is engineered for. Users who need Go bindings can build their own cgo wrapper against the unchanged C ABI in `crates/ffi/` — header at `sdks/cpp/include/thetadx.h`, all FFI types and free fns exported as `tdx_*` symbols. Removes: - `sdks/go/` (entire directory: 30 source files) - Per-Go build_support modules + templates: `ticks/go.rs`, `fpss_events/go_structs.rs`, `sdk_surface/go.rs`, `endpoints/render/go.rs`, `endpoints/render/go_validate.rs`, all `templates/go/`, `templates/validate_go/`, `templates/go_structs/` - Go-specific `tick_schema.toml` render rows (`go_struct`, `go_converter`) on every `[types.X.render]` block - Go-specific `sdk_surface.toml` targets (`go_fpss`, `go`) and the entire `[[go_ffi.tls_reader_markers]]` SSOT block - `MethodTarget::GoFpss`, `UtilityTarget::Go`, `GoFfiSpec`, `TlsReaderMarker`, plus every Go-only helper in `endpoints/helpers.rs` and `sdk_surface/common.rs` - CI Go SDK jobs in `ci.yml` and `live.yml` (`actions/setup-go`, cgo wrapper steps), Go module from `dependabot.yml`, and the `sdks/go/` rules in `.gitattributes` Extracts shared `(size, align, offset)` math used by the C++ and tdbe layout-assert emitters into a neutral `ticks/layout.rs` module. Refreshes README, CONTRIBUTING, ROADMAP, the docs-site, and the in-repo `docs/` tree. Mirrors `CHANGELOG.md` -> `docs-site/docs/changelog.md`. The C ABI in `crates/ffi/` is unchanged — third-party cgo / Swift / Zig wrappers continue to work against the same `extern "C"` surface. Closes #481. --- .gitattributes | 10 - .github/dependabot.yml | 7 - .github/workflows/ci.yml | 19 - .github/workflows/live.yml | 24 - CHANGELOG.md | 13 + CONTRIBUTING.md | 3 - Cargo.lock | 6 +- README.md | 40 +- ROADMAP.md | 6 +- crates/thetadatadx/Cargo.toml | 2 +- .../build_support/endpoints/helpers.rs | 67 - .../endpoints/render/build_out.rs | 9 - .../build_support/endpoints/render/go.rs | 384 --- .../endpoints/render/go_validate.rs | 65 - .../build_support/endpoints/render/mod.rs | 2 - .../endpoints/render/sdk_files.rs | 29 +- .../render/templates/go/bool_case.go.tmpl | 8 - .../render/templates/go/float_case.go.tmpl | 4 - .../render/templates/go/int_case.go.tmpl | 4 - .../render/templates/go/string_case.go.tmpl | 5 - .../render/templates/go/with_deadline.go.tmpl | 18 - .../templates/go/with_timeout_ms.go.tmpl | 11 - .../render/templates/validate_go/cell.go.tmpl | 7 - .../templates/validate_go/postamble.go.tmpl | 56 - .../templates/validate_go/preamble.go.tmpl | 66 - .../build_support/fpss_events/ffi_c.rs | 11 +- .../build_support/fpss_events/mod.rs | 32 +- .../go_structs/control_and_raw_data.go.tmpl | 22 - .../go_structs/control_kind_consts.go.tmpl | 24 - .../templates/go_structs/kind_enum.go.tmpl | 12 - .../build_support/sdk_surface/common.rs | 47 - .../build_support/sdk_surface/go.rs | 393 --- .../build_support/sdk_surface/mod.rs | 26 +- .../build_support/sdk_surface/spec.rs | 68 +- crates/thetadatadx/build_support/ticks/cpp.rs | 2 +- .../thetadatadx/build_support/ticks/layout.rs | 78 + crates/thetadatadx/build_support/ticks/mod.rs | 21 +- .../thetadatadx/build_support/ticks/schema.rs | 4 +- .../build_support/ticks/tdbe_structs.rs | 10 +- crates/thetadatadx/endpoint_surface.toml | 4 +- crates/thetadatadx/sdk_surface.toml | 77 +- crates/thetadatadx/tick_schema.toml | 38 +- docs-site/docs/api-reference.md | 263 +- docs-site/docs/changelog.md | 13 + docs-site/docs/getting-started/first-query.md | 36 +- docs-site/docs/getting-started/index.md | 11 +- .../docs/getting-started/installation.md | 51 +- docs-site/docs/getting-started/quickstart.md | 98 +- docs-site/docs/index.md | 29 +- docs-site/docs/streaming/index.md | 57 +- docs-site/docs/streaming/latency.md | 85 +- docs-site/docs/streaming/reconnection.md | 96 +- docs/ATTRIBUTION.md | 2 +- docs/api-reference.md | 31 +- docs/architecture.md | 2 +- docs/java-parity-checklist.md | 10 +- docs/macro-guide.md | 6 +- docs/public-api-redesign.md | 2 +- ffi/Cargo.toml | 2 +- ffi/README.md | 4 +- ffi/src/error.rs | 16 +- ffi/src/lib.rs | 3 +- scripts/check_docs_consistency.py | 57 +- scripts/validate_agreement.py | 27 +- scripts/validate_release.sh | 37 +- sdks/README.md | 50 +- sdks/cpp/include/fpss_event_structs.h.inc | 5 +- sdks/go/README.md | 400 --- sdks/go/client.go | 62 - sdks/go/cmd/fpss_smoke/main.go | 211 -- sdks/go/cmd/live_smoke/main.go | 73 - sdks/go/cmd/validate/main.go | 80 - sdks/go/dropped_events_test.go | 66 - sdks/go/endpoint_ffi_sizes_generated.go | 5 - sdks/go/endpoint_options.go | 379 --- sdks/go/endpoint_request_options.h.inc | 33 - sdks/go/endpoint_with_options.h.inc | 62 - sdks/go/examples/main.go | 53 - sdks/go/ffi_bridge.h | 259 -- sdks/go/ffi_layout_generated_test.go | 272 -- sdks/go/fpss.go | 83 - sdks/go/fpss_ffi_offset_checks_generated.go | 78 - sdks/go/fpss_ffi_sizes_generated.go | 13 - sdks/go/go.mod | 3 - sdks/go/historical.go | 1253 -------- sdks/go/thetadx.go | 219 -- sdks/go/tick_ffi_sizes_generated.go | 22 - sdks/go/timeout_race_test.go | 149 - sdks/go/timeout_test.go | 99 - sdks/go/utilities.go | 68 - sdks/go/validate.go | 2710 ----------------- sdks/python/Cargo.lock | 4 +- sdks/python/Cargo.toml | 2 +- sdks/python/tests/test_dropped_events.py | 3 +- sdks/typescript/Cargo.lock | 4 +- sdks/typescript/Cargo.toml | 2 +- .../__tests__/dropped_events.test.mjs | 2 +- tools/cli/Cargo.toml | 2 +- tools/cli/src/main.rs | 14 +- tools/mcp/Cargo.lock | 4 +- tools/mcp/Cargo.toml | 2 +- tools/mcp/src/main.rs | 8 +- tools/server/Cargo.lock | 4 +- tools/server/Cargo.toml | 2 +- tools/server/src/format.rs | 12 +- 105 files changed, 313 insertions(+), 9131 deletions(-) delete mode 100644 crates/thetadatadx/build_support/endpoints/render/go.rs delete mode 100644 crates/thetadatadx/build_support/endpoints/render/go_validate.rs delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/go/bool_case.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/go/float_case.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/go/int_case.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/go/string_case.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/go/with_deadline.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/go/with_timeout_ms.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/validate_go/cell.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/validate_go/postamble.go.tmpl delete mode 100644 crates/thetadatadx/build_support/endpoints/render/templates/validate_go/preamble.go.tmpl delete mode 100644 crates/thetadatadx/build_support/fpss_events/templates/go_structs/control_and_raw_data.go.tmpl delete mode 100644 crates/thetadatadx/build_support/fpss_events/templates/go_structs/control_kind_consts.go.tmpl delete mode 100644 crates/thetadatadx/build_support/fpss_events/templates/go_structs/kind_enum.go.tmpl delete mode 100644 crates/thetadatadx/build_support/sdk_surface/go.rs create mode 100644 crates/thetadatadx/build_support/ticks/layout.rs delete mode 100644 sdks/go/README.md delete mode 100644 sdks/go/client.go delete mode 100644 sdks/go/cmd/fpss_smoke/main.go delete mode 100644 sdks/go/cmd/live_smoke/main.go delete mode 100644 sdks/go/cmd/validate/main.go delete mode 100644 sdks/go/dropped_events_test.go delete mode 100644 sdks/go/endpoint_ffi_sizes_generated.go delete mode 100644 sdks/go/endpoint_options.go delete mode 100644 sdks/go/endpoint_request_options.h.inc delete mode 100644 sdks/go/endpoint_with_options.h.inc delete mode 100644 sdks/go/examples/main.go delete mode 100644 sdks/go/ffi_bridge.h delete mode 100644 sdks/go/ffi_layout_generated_test.go delete mode 100644 sdks/go/fpss.go delete mode 100644 sdks/go/fpss_ffi_offset_checks_generated.go delete mode 100644 sdks/go/fpss_ffi_sizes_generated.go delete mode 100644 sdks/go/go.mod delete mode 100644 sdks/go/historical.go delete mode 100644 sdks/go/thetadx.go delete mode 100644 sdks/go/tick_ffi_sizes_generated.go delete mode 100644 sdks/go/timeout_race_test.go delete mode 100644 sdks/go/timeout_test.go delete mode 100644 sdks/go/utilities.go delete mode 100644 sdks/go/validate.go diff --git a/.gitattributes b/.gitattributes index 4b0a1d81..ad840c20 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,14 +2,6 @@ # `generate_sdk_surfaces --check` doesn't false-positive on Windows. ffi/src/endpoint_request_options.rs text eol=lf linguist-generated=true ffi/src/endpoint_with_options.rs text eol=lf linguist-generated=true -sdks/go/endpoint_options.go text eol=lf linguist-generated=true -sdks/go/endpoint_request_options.h.inc text eol=lf linguist-generated=true -sdks/go/endpoint_with_options.h.inc text eol=lf linguist-generated=true -sdks/go/fpss_methods.go text eol=lf linguist-generated=true -sdks/go/historical.go text eol=lf linguist-generated=true -sdks/go/tick_converters.go text eol=lf linguist-generated=true -sdks/go/tick_structs.go text eol=lf linguist-generated=true -sdks/go/utilities.go text eol=lf linguist-generated=true sdks/cpp/include/endpoint_options.hpp.inc text eol=lf linguist-generated=true sdks/cpp/include/endpoint_request_options.h.inc text eol=lf linguist-generated=true sdks/cpp/include/endpoint_with_options.h.inc text eol=lf linguist-generated=true @@ -20,7 +12,6 @@ sdks/cpp/src/fpss.cpp.inc text eol=lf linguist-generated=true sdks/cpp/src/historical.cpp.inc text eol=lf linguist-generated=true sdks/cpp/src/lifecycle.cpp.inc text eol=lf linguist-generated=true sdks/cpp/src/utilities.cpp.inc text eol=lf linguist-generated=true -sdks/go/validate.go text eol=lf linguist-generated=true sdks/python/src/historical_methods.rs text eol=lf linguist-generated=true sdks/python/src/streaming_methods.rs text eol=lf linguist-generated=true scripts/validate_python.py text eol=lf linguist-generated=true @@ -36,7 +27,6 @@ sdks/typescript/src/streaming_methods.rs text eol=lf linguist-generated=true sdks/typescript/src/tick_classes.rs text eol=lf linguist-generated=true sdks/typescript/src/fpss_event_classes.rs text eol=lf linguist-generated=true sdks/typescript/src/buffered_event.rs text eol=lf linguist-generated=true -sdks/go/fpss_event_structs.go text eol=lf linguist-generated=true sdks/cpp/include/fpss_event_structs.h.inc text eol=lf linguist-generated=true # Generated validator surfaces that were missed from the original list. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5b55f9fc..cca58cd8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -45,10 +45,3 @@ updates: patch-minor: update-types: ["patch", "minor"] - - package-ecosystem: "gomod" - directory: "/sdks/go" - schedule: - interval: "weekly" - groups: - go-patch-minor: - update-types: ["patch", "minor"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a9dc2b3..c22c7d6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,9 +77,6 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: clippy - - uses: actions/setup-go@v6 - with: - go-version: "1.23" - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -123,9 +120,6 @@ jobs: ffi_artifact: ffi-windows-msvc-x86_64 steps: - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 - with: - go-version: "1.23" - uses: actions/download-artifact@v8 with: name: ${{ matrix.ffi_artifact }} @@ -137,19 +131,6 @@ jobs: path: target/x86_64-pc-windows-gnu/release/ - run: cmake -S sdks/cpp -B build/cpp - run: cmake --build build/cpp --config Release --target thetadatadx_cpp - - shell: bash - working-directory: sdks/go - run: | - set -euo pipefail - if [ "${{ runner.os }}" = "Linux" ]; then - export LD_LIBRARY_PATH="${{ github.workspace }}/target/release" - elif [ "${{ runner.os }}" = "macOS" ]; then - export DYLD_LIBRARY_PATH="${{ github.workspace }}/target/release" - else - export PATH="${{ github.workspace }}/target/x86_64-pc-windows-gnu/release:$PATH" - export CGO_LDFLAGS="-L${{ github.workspace }}/target/x86_64-pc-windows-gnu/release" - fi - go test ./... build-ffi: name: Build FFI (${{ matrix.os }}) diff --git a/.github/workflows/live.yml b/.github/workflows/live.yml index 8587b703..76a54bf6 100644 --- a/.github/workflows/live.yml +++ b/.github/workflows/live.yml @@ -56,9 +56,6 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.12" - - uses: actions/setup-go@v6 - with: - go-version: "1.23" - name: Materialize creds.txt shell: python env: @@ -92,16 +89,6 @@ jobs: env: LD_LIBRARY_PATH: ${{ github.workspace }}/target/release run: ./build/cpp/thetadatadx_fpss_smoke creds.txt - - shell: bash - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/target/release - CGO_LDFLAGS: -L${{ github.workspace }}/target/release - run: cd sdks/go && go run ./cmd/live_smoke ../../creds.txt - - shell: bash - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/target/release - CGO_LDFLAGS: -L${{ github.workspace }}/target/release - run: cd sdks/go && go run ./cmd/fpss_smoke ../../creds.txt - name: FLATFILES synthetic golden (decoder) # Runs against a hand-built blob in the repo; no live wire. This # is the always-on regression gate for the FIT decoder + CSV @@ -138,9 +125,6 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.12" - - uses: actions/setup-go@v6 - with: - go-version: "1.23" - name: Materialize creds.txt shell: python env: @@ -168,11 +152,6 @@ jobs: env: LD_LIBRARY_PATH: ${{ github.workspace }}/target/release run: ./build/cpp/thetadatadx_validate creds.txt - - shell: bash - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/target/release - CGO_LDFLAGS: -L${{ github.workspace }}/target/release - run: cd sdks/go && go run ./cmd/validate ../../creds.txt release-validation: name: Full Release Validation needs: endpoint-validation @@ -182,9 +161,6 @@ jobs: steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust-deps - - uses: actions/setup-go@v6 - with: - go-version: "1.23" - uses: actions/setup-python@v6 with: python-version: "3.12" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7102e415..a90ddeb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.29] - 2026-05-06 + +### Removed + +- **Go SDK.** The cgo bridge between Rust's C ABI and Go's runtime + carries per-call overhead that masks the upstream throughput this + SDK is engineered for. Users who need Go bindings can build their + own cgo wrapper against the unchanged C ABI in `crates/ffi/` — + header at `sdks/cpp/include/thetadx.h`, all FFI types and free fns + exported as `tdx_*` symbols. + + Closes #481. + ## [8.0.28] - 2026-05-06 ### Breaking diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e54858a3..ee8cb387 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,6 @@ for the FLATFILES coverage matrix. - **Python 3.9+** - for the Python SDK - **maturin** - for building the PyO3 Python bindings (`pip install "maturin>=1.9.4,<2.0"`) - **Node.js 18+** - for the TypeScript/Node.js SDK -- **Go 1.21+** - for the Go SDK Note: `protoc` is required even if you're not modifying `.proto` files, because `build.rs` compiles protos during `cargo build`. @@ -64,7 +63,6 @@ cargo test --manifest-path tools/cli/Cargo.toml # 6. Language SDK smoke checks (if modified) cargo check --manifest-path sdks/python/Cargo.toml (cd sdks/typescript && npm run build) -(cd sdks/go && LD_LIBRARY_PATH=../../target/release go test ./...) c++ -std=c++17 -fsyntax-only -I sdks/cpp/include sdks/cpp/src/thetadx.cpp cmake -S sdks/cpp -B build/cpp cmake --build build/cpp --target thetadatadx_cpp @@ -114,7 +112,6 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) | `ffi` | `ffi/` | | `python` | `sdks/python/` | | `typescript` | `sdks/typescript/` | -| `go` | `sdks/go/` | | `cpp` | `sdks/cpp/` | | `cli` | `tools/cli/` | | `mcp` | `tools/mcp/` | diff --git a/Cargo.lock b/Cargo.lock index 2cf729df..4ed6b8fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3103,7 +3103,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.28" +version = "8.0.29" dependencies = [ "arrow-array", "arrow-schema", @@ -3143,7 +3143,7 @@ dependencies = [ [[package]] name = "thetadatadx-cli" -version = "8.0.28" +version = "8.0.29" dependencies = [ "clap", "comfy-table", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "thetadatadx-ffi" -version = "8.0.28" +version = "8.0.29" dependencies = [ "tdbe", "thetadatadx", diff --git a/README.md b/README.md index 02d39cb2..2bb82434 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ThetaDataDx -Rust SDK for ThetaData market data — single Rust core, five language surfaces (Rust, Python, TypeScript, Go, C++). +Rust SDK for ThetaData market data — single Rust core, four language surfaces (Rust, Python, TypeScript, C++). [![build](https://github.com/userFRM/ThetaDataDx/actions/workflows/ci.yml/badge.svg)](https://github.com/userFRM/ThetaDataDx/actions/workflows/ci.yml) [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) @@ -10,18 +10,18 @@ Rust SDK for ThetaData market data — single Rust core, five language surfaces [![Docs](https://img.shields.io/docsrs/thetadatadx)](https://docs.rs/thetadatadx) [![Discord](https://img.shields.io/badge/Discord-community-5865F2.svg?logo=discord&logoColor=white)](https://discord.thetadata.us/) -`thetadatadx` is a native Rust SDK for [ThetaData](https://thetadata.us) market data. It connects directly to ThetaData's three public surfaces — MDDS (historical request/response over gRPC), FPSS (real-time streaming over TCP), and FLATFILES (whole-universe daily blobs over the legacy MDDS port) — decodes ticks in-process, and exposes a typed API across Rust, Python, TypeScript, Go, and C++ from a single Rust core. No JVM, no subprocess, no IPC serialization. +`thetadatadx` is a native Rust SDK for [ThetaData](https://thetadata.us) market data. It connects directly to ThetaData's three public surfaces — MDDS (historical request/response over gRPC), FPSS (real-time streaming over TCP), and FLATFILES (whole-universe daily blobs over the legacy MDDS port) — decodes ticks in-process, and exposes a typed API across Rust, Python, TypeScript, and C++ from a single Rust core. No JVM, no subprocess, no IPC serialization. Go consumers can build a thin cgo wrapper against the unchanged C ABI in [`ffi/`](ffi/) — header at [`sdks/cpp/include/thetadx.h`](sdks/cpp/include/thetadx.h), all FFI types and free fns exported as `tdx_*` symbols. > [!IMPORTANT] > A valid [ThetaData](https://thetadata.us) subscription is required. The SDK authenticates against ThetaData's Nexus API using your account credentials. ## Highlights -- **Typed everywhere.** 61 ThetaData endpoints exposed as typed methods across all five SDKs; no raw JSON or protobuf on the public surface. +- **Typed everywhere.** 61 ThetaData endpoints exposed as typed methods across all four SDKs; no raw JSON or protobuf on the public surface. - **Arrow-backed DataFrames.** Python `to_arrow()` / `to_pandas()` / `to_polars()` pipe through shared Arrow buffers. - **SPKI-pinned FPSS TLS.** Public-key pinning on the FPSS streaming handshake. - **FIT decoder + SPSC ring buffer** on the FPSS path. Decode cost is measured in the benchmarks under `crates/thetadatadx/benches/`. -- **Shared FFI layer.** Go, C++, and Node.js go through the same `extern "C"` layer; the Python wheel uses PyO3 ABI3 directly. +- **Shared FFI layer.** C++ and Node.js go through the same `extern "C"` layer; the Python wheel uses PyO3 ABI3 directly. The C ABI is also the supported integration path for any third-party Go/C/other-language consumer that wants to roll their own wrapper. - **Covers all three public surfaces.** MDDS gRPC endpoints, FPSS wire format with reconnect semantics, and the FLATFILES daily-blob protocol — every transport speaks directly to ThetaData's production servers from a single client. See the [parity checklist](docs/java-parity-checklist.md) and the [vendor flat-file reference](https://http-docs.thetadata.us/operations/get-v2-flat-file-getting-started.html). - **FLATFILES daily blobs.** Pull whole-universe `(sec_type, req_type, date)` blobs over the legacy MDDS port; decode to vendor-byte CSV, JSONL, or a typed `Vec` in memory. Cross-language coverage is tracked under the binding issues; the Rust core is shipped today. @@ -100,32 +100,6 @@ for (const t of tdx.stockHistoryEOD('AAPL', '20240101', '20240301')) { } ``` -### Go - -```sh -go get github.com/userFRM/ThetaDataDx/sdks/go -``` - -```go -package main - -import ( - "fmt" - thetadata "github.com/userFRM/ThetaDataDx/sdks/go" -) - -func main() { - tdx, err := thetadata.ConnectFromFile("creds.txt", thetadata.Production()) - if err != nil { panic(err) } - defer tdx.Close() - ticks, _ := tdx.StockHistoryEod("AAPL", "20240101", "20240301") - for _, t := range ticks { - fmt.Printf("%d: O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", - t.Date, t.Open, t.High, t.Low, t.Close, t.Volume) - } -} -``` - ### C++ ```cpp @@ -202,7 +176,6 @@ flowchart TB core -->|PyO3 / maturin| python["Python SDK
(pyo3 · Arrow)"] ffi -->|napi-rs| ts["TypeScript SDK
(N-API · BigInt)"] - ffi -->|cgo| go["Go SDK"] ffi -->|extern C| cpp["C++ SDK
(RAII header-only)"] core -->|tonic| rust["Rust consumer
(direct crate)"] @@ -211,17 +184,16 @@ flowchart TB classDef sdkStyle fill:#14532d,stroke:#052e16,color:#fff class thetadatadx,tdbe coreStyle class ffi ffiStyle - class python,ts,go,cpp,rust sdkStyle + class python,ts,cpp,rust sdkStyle ``` | Layer | Crate / package | Purpose | |-------|-----------------|---------| | Encoding / types | [`crates/tdbe`](crates/tdbe/) | Tick structs, FIT/FIE codecs, Greeks, Price | | Core SDK | [`crates/thetadatadx`](crates/thetadatadx/) | MDDS gRPC client, FPSS streaming, auth | -| C FFI | [`ffi/`](ffi/) | Stable `extern "C"` layer consumed by Go, C++, Node.js | +| C FFI | [`ffi/`](ffi/) | Stable `extern "C"` layer consumed by C++, Node.js, and any third-party C/Go consumer | | Python | [`sdks/python`](sdks/python/) | PyO3 / maturin wheel with Arrow DataFrame adapter | | TypeScript | [`sdks/typescript`](sdks/typescript/) | napi-rs prebuilt binary | -| Go | [`sdks/go`](sdks/go/) | CGo bindings over the FFI layer | | C++ | [`sdks/cpp`](sdks/cpp/) | RAII header-only wrapper | | CLI | [`tools/cli`](tools/cli/) | `tdx` CLI — every generated historical endpoint from the command line | | MCP | [`tools/mcp`](tools/mcp/) | MCP server - gives clients access to every generated historical endpoint plus offline tools over JSON-RPC | diff --git a/ROADMAP.md b/ROADMAP.md index 26abd32e..aa794985 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -142,7 +142,6 @@ Server retention window: 7 calendar days. Older history: contact ThetaData sales | REST/WS server (`tools/server`) | Pending | #432 | | Python (`sdks/python`) | Pending | #435 | | TypeScript (`sdks/typescript`) | Pending | #436 | -| Go (`sdks/go`) | Pending | #437 | | C++ (`sdks/cpp`) | Pending | #438 | | `sdk_surface.toml` declarative spec | Pending | #439 | @@ -169,11 +168,10 @@ Server retention window: 7 calendar days. Older history: contact ThetaData sales ### Cross-language parity for `utils` -The Rust SDK exposes `thetadatadx::utils::{conditions, exchange, sequences}` for post-processing tick records. The Python, TypeScript, Go, and C++ SDKs do **not** currently expose any of these helpers. Tracked in issue #424. +The Rust SDK exposes `thetadatadx::utils::{conditions, exchange, sequences}` for post-processing tick records. The Python, TypeScript, and C++ SDKs do **not** currently expose any of these helpers. Tracked in issue #424. - [ ] **Python** — bind `thetadatadx.utils.{conditions, exchange, sequences}` via PyO3. - [ ] **TypeScript** — bind via napi-rs under the same `utils.*` namespace. -- [ ] **Go** — flat helper functions `thetadatadx.UtilsConditionName(code)`, etc. - [ ] **C++** — header at `sdks/cpp/include/thetadx_utils.h` with `extern "C"` bindings plus thin C++ wrappers. ### MDDS endpoint coverage on subscription-tier-blocked rows @@ -193,7 +191,7 @@ The 7 SKIP rows in the MDDS validator are subscription-blocked on the current te ### Cross-SDK parity validation -- [ ] Run the validator matrix through the Python, TypeScript, Go, and C++ SDKs and compare row-for-row against the Rust artifact. Locks in the contract that all four bindings return identical data for every endpoint. +- [ ] Run the validator matrix through the Python, TypeScript, and C++ SDKs and compare row-for-row against the Rust artifact. Locks in the contract that all three bindings return identical data for every endpoint. ### Upstream features diff --git a/crates/thetadatadx/Cargo.toml b/crates/thetadatadx/Cargo.toml index 0070bbe7..914f1f9e 100644 --- a/crates/thetadatadx/Cargo.toml +++ b/crates/thetadatadx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx" -version = "8.0.28" +version = "8.0.29" edition.workspace = true rust-version.workspace = true authors.workspace = true diff --git a/crates/thetadatadx/build_support/endpoints/helpers.rs b/crates/thetadatadx/build_support/endpoints/helpers.rs index f325dd68..5b98341b 100644 --- a/crates/thetadatadx/build_support/endpoints/helpers.rs +++ b/crates/thetadatadx/build_support/endpoints/helpers.rs @@ -30,8 +30,6 @@ use super::model::{GeneratedEndpoint, GeneratedParam}; pub(super) struct TickRender { pub(super) direct: String, pub(super) parser: String, - pub(super) go_struct: String, - pub(super) go_converter: String, pub(super) ffi_array: String, pub(super) ffi_output_variant: String, pub(super) ffi_from_vec_array: String, @@ -63,8 +61,6 @@ struct TickRenderToml { collection: String, direct: String, parser: String, - go_struct: String, - go_converter: String, ffi_array: String, ffi_output_variant: String, ffi_from_vec_array: String, @@ -100,8 +96,6 @@ fn load_render_map() -> HashMap { let entry = TickRender { direct: render.direct, parser: render.parser, - go_struct: render.go_struct, - go_converter: render.go_converter, ffi_array: render.ffi_array, ffi_output_variant: render.ffi_output_variant, ffi_from_vec_array: render.ffi_from_vec_array, @@ -555,17 +549,6 @@ pub(super) fn is_time_arg(param: &GeneratedParam) -> bool { ) } -pub(super) fn go_result_type(return_type: &str) -> String { - if return_type == "StringList" { - return "[]string".into(); - } - format!("[]{}", render_for(return_type).go_struct) -} - -pub(super) fn go_converter_name(return_type: &str) -> String { - render_for(return_type).go_converter.clone() -} - pub(super) fn ffi_array_type(return_type: &str) -> String { if return_type == "StringList" { return "TdxStringArray".into(); @@ -764,15 +747,6 @@ pub(super) fn sdk_method_arg_name(param: &GeneratedParam) -> String { } } -pub(super) fn go_method_arg_decl(param: &GeneratedParam) -> String { - let name = to_camel_case(&sdk_method_arg_name(param)); - if param.param_type == "Symbols" { - format!("{name} []string") - } else { - format!("{name} string") - } -} - pub(super) fn python_method_arg_decl(param: &GeneratedParam) -> String { let name = sdk_method_arg_name(param); format!("{name}: {}", python_string_arg_type(param)) @@ -787,10 +761,6 @@ pub(super) fn cpp_method_arg_decl(param: &GeneratedParam) -> String { } } -pub(super) fn go_c_var_name(param: &GeneratedParam) -> String { - format!("c{}", to_go_exported_name(&sdk_method_arg_name(param))) -} - // ───────────────────────── Validator arg literals ────────────────────────── /// Render a single arg string as a Python literal expression, taking the @@ -802,17 +772,6 @@ pub(super) fn python_arg_literal(param: &GeneratedParam, value: &str) -> String } } -/// Render a single arg string as a Go literal expression. -pub(super) fn go_arg_literal(_param: &GeneratedParam, value: &str) -> String { - // The Go SDK keeps scalar signatures (`StockSnapshotOHLC(symbol string, ...)`) - // and exposes bulk variants via a `*Bulk` suffix (`StockSnapshotOHLCBulk( - // symbols []string, ...)`). Generated validator cells use single-value - // fixtures and therefore must target the scalar form — passing a - // `[]string{...}` literal tripwires the Go type checker on every - // `Symbols`-param endpoint and turns SDK Bindings CI red. - format!("\"{value}\"") -} - /// Render a single arg string as a C++ literal expression. pub(super) fn cpp_arg_literal(param: &GeneratedParam, value: &str) -> String { match param.param_type.as_str() { @@ -853,32 +812,6 @@ pub(super) fn python_builder_kwarg( Some(format!("{name}={literal}")) } -/// Render a Go `WithXxx(value)` option for a builder-bound param. The -/// generated validate.go lives in the same `package thetadatadx` as the -/// `WithXxx` ctors, so no package qualifier is needed. -pub(super) fn go_builder_option( - endpoint: &GeneratedEndpoint, - name: &str, - value: &str, -) -> Option { - let param = builder_param_for(endpoint, name)?; - let with_name = go_with_name_from_param(name); - let literal = match param.param_type.as_str() { - "Bool" => value.to_string(), - "Int" => format!("int32({value})"), - "Float" => value.to_string(), - _ => format!("\"{value}\""), - }; - Some(format!("{with_name}({literal})")) -} - -/// Convert a snake_case param name to the Go `WithXxx` exported ctor, keeping -/// the `DTE`/`NBBO` acronym casing used in the existing hand-rolled options. -pub(super) fn go_with_name_from_param(name: &str) -> String { - let exported = name.split('_').map(go_segment_pascal).collect::(); - format!("With{exported}") -} - /// Render a C++ `.with_(value)` chained setter for a builder-bound param. pub(super) fn cpp_builder_setter( endpoint: &GeneratedEndpoint, diff --git a/crates/thetadatadx/build_support/endpoints/render/build_out.rs b/crates/thetadatadx/build_support/endpoints/render/build_out.rs index d7ac8016..37465f07 100644 --- a/crates/thetadatadx/build_support/endpoints/render/build_out.rs +++ b/crates/thetadatadx/build_support/endpoints/render/build_out.rs @@ -44,19 +44,10 @@ const VALIDATOR_TEMPLATES: &[&str] = &[ "validate_cli/preamble.py.tmpl", "validate_cli/cell.py.tmpl", "validate_cli/postamble.py.tmpl", - "validate_go/preamble.go.tmpl", - "validate_go/cell.go.tmpl", - "validate_go/postamble.go.tmpl", "validate_cpp/preamble.cpp.tmpl", "validate_cpp/cell.cpp.tmpl", "validate_cpp/postamble.cpp.tmpl", // Emitter body templates (W8). - "go/with_timeout_ms.go.tmpl", - "go/with_deadline.go.tmpl", - "go/int_case.go.tmpl", - "go/float_case.go.tmpl", - "go/bool_case.go.tmpl", - "go/string_case.go.tmpl", "cpp/with_timeout_ms.cpp.tmpl", "cpp/with_deadline.cpp.tmpl", "cpp/option_contracts_convert.cpp.tmpl", diff --git a/crates/thetadatadx/build_support/endpoints/render/go.rs b/crates/thetadatadx/build_support/endpoints/render/go.rs deleted file mode 100644 index fc431dcb..00000000 --- a/crates/thetadatadx/build_support/endpoints/render/go.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Go SDK emitter. -//! -//! Produces three outputs: -//! * `sdks/go/endpoint_options.go` — `With` option-builder scaffolding. -//! * `sdks/go/historical.go` — one method per endpoint on `*Client`. -//! * `sdks/go/endpoint_with_options.h.inc` — cgo extern decls for the -//! `_with_options` FFI entry points. - -use std::fmt::Write as _; - -use super::super::helpers::{ - ffi_free_fn, ffi_header_return_type, ffi_option_has_flag, go_c_var_name, go_converter_name, - go_method_arg_decl, go_result_type, is_streaming_endpoint, method_params, sdk_method_arg_name, - to_camel_case, to_go_exported_name, -}; -use super::super::model::{GeneratedEndpoint, GeneratedParam}; - -pub(super) fn render_go_options(params: &[GeneratedParam]) -> String { - let mut out = String::new(); - // Go's tooling (go vet, gofmt, staticcheck) recognizes auto-generated - // files via the regex `^// Code generated .* DO NOT EDIT\.$` - // (https://golang.org/s/generatedcode). The non-Go `@generated` form - // breaks that recognition, so every .go file we emit must follow the - // spec literally. Other languages (Rust/Python/C++) keep the uniform - // `@generated` form since no analogous spec applies there. - out.push_str("// Code generated by build.rs from endpoint_surface.toml; DO NOT EDIT.\n\n"); - out.push_str("package thetadatadx\n\n"); - out.push_str("/*\n#include \"ffi_bridge.h\"\n*/\nimport \"C\"\n\n"); - out.push_str("import (\n"); - out.push_str("\t\"time\"\n"); - out.push_str("\t\"unsafe\"\n"); - out.push_str(")\n\n"); - out.push_str("// EndpointRequestOptions contains the shared optional request fields projected from endpoint_surface.toml.\n"); - out.push_str("// TimeoutMs is the cross-cutting per-call deadline (W3); see WithTimeoutMs.\n"); - out.push_str("type EndpointRequestOptions struct {\n"); - for param in params { - let field_name = to_go_exported_name(¶m.name); - let field_type = match param.param_type.as_str() { - "Int" => "*int32", - "Float" => "*float64", - "Bool" => "*bool", - _ => "*string", - }; - writeln!(out, "\t// {}", param.description).unwrap(); - writeln!(out, "\t{} {}", field_name, field_type).unwrap(); - } - out.push_str("\t// TimeoutMs is the per-call deadline in milliseconds. When set, on\n"); - out.push_str("\t// expiry the in-flight gRPC call is cancelled and the returned error\n"); - out.push_str("\t// carries \"Request deadline exceeded\". The Client handle stays usable\n"); - out.push_str("\t// for subsequent calls. See WithTimeoutMs / WithDeadline.\n"); - out.push_str("\tTimeoutMs *uint64\n"); - out.push_str("}\n\n"); - - out.push_str("// EndpointOption applies one optional request field to an endpoint request.\n"); - out.push_str("type EndpointOption func(*EndpointRequestOptions)\n\n"); - out.push_str( - "func collectEndpointRequestOptions(opts []EndpointOption) *EndpointRequestOptions {\n", - ); - out.push_str("\tif len(opts) == 0 {\n\t\treturn nil\n\t}\n"); - out.push_str("\toptions := &EndpointRequestOptions{}\n"); - out.push_str( - "\tfor _, opt := range opts {\n\t\tif opt != nil {\n\t\t\topt(options)\n\t\t}\n\t}\n", - ); - out.push_str("\treturn options\n}\n\n"); - - for param in params { - let helper_name = format!("With{}", to_go_exported_name(¶m.name)); - let field_name = to_go_exported_name(¶m.name); - let arg_type = match param.param_type.as_str() { - "Int" => "int32", - "Float" => "float64", - "Bool" => "bool", - _ => "string", - }; - writeln!(out, "// {} sets {}.", helper_name, param.name).unwrap(); - writeln!( - out, - "func {}(value {}) EndpointOption {{", - helper_name, arg_type - ) - .unwrap(); - out.push_str("\treturn func(options *EndpointRequestOptions) {\n"); - out.push_str("\t\tvalueCopy := value\n"); - writeln!(out, "\t\toptions.{} = &valueCopy", field_name).unwrap(); - out.push_str("\t}\n"); - out.push_str("}\n\n"); - } - - // Cross-cutting per-call deadline (W3). The Go-idiomatic name is - // WithTimeout (time.Duration) plus WithTimeoutMs (uint64) so callers can - // pick whichever flavor matches their existing code. - out.push_str(include_str!("templates/go/with_timeout_ms.go.tmpl")); - out.push_str(include_str!("templates/go/with_deadline.go.tmpl")); - - out.push_str("func endpointRequestOptionsToC(opts *EndpointRequestOptions) (*C.TdxEndpointRequestOptions, func()) {\n"); - out.push_str("\tif opts == nil {\n\t\treturn nil, func() {}\n\t}\n\n"); - out.push_str("\tcOpts := &C.TdxEndpointRequestOptions{}\n\n"); - out.push_str("\tvar allocations []unsafe.Pointer\n"); - out.push_str("\tfree := func() {\n"); - out.push_str("\t\tfor _, allocation := range allocations {\n"); - out.push_str("\t\t\tC.free(allocation)\n"); - out.push_str("\t\t}\n"); - out.push_str("\t}\n\n"); - for param in params { - let field_name = to_go_exported_name(¶m.name); - let template = match param.param_type.as_str() { - "Int" => include_str!("templates/go/int_case.go.tmpl"), - "Float" => include_str!("templates/go/float_case.go.tmpl"), - "Bool" => include_str!("templates/go/bool_case.go.tmpl"), - _ => include_str!("templates/go/string_case.go.tmpl"), - }; - out.push_str( - &template - .replace("__FIELD__", &field_name) - .replace("__PARAM__", ¶m.name), - ); - } - // Cross-cutting per-call deadline. - out.push_str("\tif opts.TimeoutMs != nil {\n"); - out.push_str("\t\tcOpts.timeout_ms = C.uint64_t(*opts.TimeoutMs)\n"); - out.push_str("\t\tcOpts.has_timeout_ms = 1\n"); - out.push_str("\t}\n"); - out.push_str("\n\treturn cOpts, free\n"); - out.push_str("}\n"); - - out -} - -pub(super) fn render_go_endpoint_with_options_decls(endpoints: &[GeneratedEndpoint]) -> String { - let mut out = String::new(); - out.push_str( - "/* @generated DO NOT EDIT — regenerated by build.rs from endpoint_surface.toml */\n", - ); - // All non-streaming endpoints now have a `_with_options` form (W3) so - // every Go method can pass at least the cross-cutting timeout_ms. - for endpoint in endpoints - .iter() - .filter(|endpoint| !is_streaming_endpoint(endpoint)) - { - let ffi_name = format!("tdx_{}_with_options", endpoint.name); - let return_type = ffi_header_return_type(&endpoint.return_type); - let params = method_params(endpoint); - write!( - out, - "extern {} {}(const TdxClient* client", - return_type, ffi_name - ) - .unwrap(); - for param in ¶ms { - if param.param_type == "Symbols" { - write!(out, ", const char* const* symbols, size_t symbols_len").unwrap(); - } else { - write!(out, ", const char* {}", param.name).unwrap(); - } - } - out.push_str(", const TdxEndpointRequestOptions* options);\n"); - } - out -} - -pub(super) fn render_go_endpoint_ffi_sizes(params: &[GeneratedParam]) -> String { - let mut out = String::new(); - out.push_str("// Code generated by build.rs from endpoint_surface.toml; DO NOT EDIT.\n\n"); - out.push_str("package thetadatadx\n\n"); - writeln!( - out, - "const TdxEndpointRequestOptionsExpectedSize uintptr = {}", - endpoint_request_options_size(params) - ) - .unwrap(); - out -} - -fn endpoint_request_options_size(params: &[GeneratedParam]) -> usize { - let mut layout = CLayout::default(); - for param in params { - match param.param_type.as_str() { - "Int" | "Bool" => layout.push(4, 4), - "Float" => layout.push(8, 8), - _ => layout.push( - std::mem::size_of::<*const ()>(), - std::mem::align_of::<*const ()>(), - ), - } - if ffi_option_has_flag(param) { - layout.push(4, 4); - } - } - layout.push(8, 8); - layout.push(4, 4); - layout.finish() -} - -#[derive(Default)] -struct CLayout { - size: usize, - align: usize, -} - -impl CLayout { - fn push(&mut self, size: usize, align: usize) { - self.align = self.align.max(align); - self.size = align_to(self.size, align) + size; - } - - fn finish(self) -> usize { - align_to(self.size, self.align) - } -} - -fn align_to(value: usize, align: usize) -> usize { - let rem = value % align; - if rem == 0 { - value - } else { - value + align - rem - } -} - -pub(super) fn render_go_historical(endpoints: &[GeneratedEndpoint]) -> String { - let mut out = String::new(); - // Go spec header (see render_go_options for the rationale). - out.push_str("// Code generated by build.rs from endpoint_surface.toml; DO NOT EDIT.\n\n"); - out.push_str("package thetadatadx\n\n"); - out.push_str("/*\n#include \"ffi_bridge.h\"\n*/\nimport \"C\"\n\n"); - out.push_str("import (\n"); - out.push_str("\t\"fmt\"\n"); - out.push_str("\t\"runtime\"\n"); - out.push_str("\t\"unsafe\"\n"); - out.push_str(")\n\n"); - // cgo-thread-local correctness: the FFI stores the last error in a - // Rust `thread_local!`. Go's runtime can migrate a goroutine to a - // different OS thread between successive cgo calls, so reading the - // error slot after a call on a different thread returns stale/empty - // data. Every generated wrapper pins the goroutine to one OS thread - // for the duration of the clear/call/check sequence via - // runtime.LockOSThread. - - for endpoint in endpoints - .iter() - .filter(|endpoint| !is_streaming_endpoint(endpoint)) - { - out.push_str(&render_go_endpoint_method(endpoint)); - out.push('\n'); - } - - out -} - -fn render_go_endpoint_method(endpoint: &GeneratedEndpoint) -> String { - let method_name = to_go_exported_name(&endpoint.name); - let method_params = method_params(endpoint); - let has_symbols = method_params - .iter() - .any(|param| param.param_type == "Symbols"); - let mut out = String::new(); - - if has_symbols { - let mut singular_parts = method_params - .iter() - .map(|param| { - if param.param_type == "Symbols" { - "symbol string".to_string() - } else { - go_method_arg_decl(param) - } - }) - .collect::>(); - singular_parts.push("opts ...EndpointOption".into()); - writeln!( - out, - "func (c *Client) {}({}) ({}, error) {{", - method_name, - singular_parts.join(", "), - go_result_type(&endpoint.return_type) - ) - .unwrap(); - writeln!( - out, - "\treturn c.{}Bulk([]string{{symbol}}, opts...)", - method_name - ) - .unwrap(); - out.push_str("}\n\n"); - } - - let emitted_method_name = if has_symbols { - format!("{method_name}Bulk") - } else { - method_name - }; - - let mut signature_parts = method_params - .iter() - .map(|param| go_method_arg_decl(param)) - .collect::>(); - // Every endpoint accepts opts now (W3): even if no builder params are - // declared, callers can pass WithTimeoutMs / WithDeadline. - signature_parts.push("opts ...EndpointOption".into()); - writeln!( - out, - "func (c *Client) {}({}) ({}, error) {{", - emitted_method_name, - signature_parts.join(", "), - go_result_type(&endpoint.return_type) - ) - .unwrap(); - - // Pin the goroutine to a single OS thread for the whole FFI sequence - // (clear error slot -> cgo call -> read error slot). The error slot - // is a Rust thread_local; reading it on a different OS thread from - // the one that set it would return empty (stale) data. See W3 round-3. - out.push_str("\truntime.LockOSThread()\n"); - out.push_str("\tdefer runtime.UnlockOSThread()\n"); - - if has_symbols { - out.push_str("\tcSymbols, cSymbolsLen := symbolsToCArray(symbols)\n"); - out.push_str("\tdefer freeSymbolArray(cSymbols, cSymbolsLen)\n"); - } - - for param in method_params - .iter() - .filter(|param| param.param_type != "Symbols") - { - let var_name = go_c_var_name(param); - let arg_name = to_camel_case(&sdk_method_arg_name(param)); - writeln!(out, "\t{} := C.CString({})", var_name, arg_name).unwrap(); - writeln!(out, "\tdefer C.free(unsafe.Pointer({}))", var_name).unwrap(); - } - - out.push_str( - "\tcOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts))\n", - ); - out.push_str("\tdefer freeOpts()\n"); - - // Clear any stale FFI error before the call so a successful empty - // result and a failure (timeout, server error) sentinel can be told - // apart by checking the error slot afterwards. W3 round-2 fix — - // see ffi/src/lib.rs::tdx_clear_error. - out.push_str("\tC.tdx_clear_error()\n"); - - write!( - out, - "\tarr := C.tdx_{}_with_options(c.handle", - endpoint.name - ) - .unwrap(); - for param in &method_params { - if param.param_type == "Symbols" { - out.push_str(", cSymbols, cSymbolsLen"); - } else { - write!(out, ", {}", go_c_var_name(param)).unwrap(); - } - } - out.push_str(", cOpts"); - out.push_str(")\n"); - - if endpoint.return_type == "StringList" { - // stringArrayToGo consults `tdx_last_error` to disambiguate - // success-empty from failure-with-empty-sentinel. - out.push_str("\treturn stringArrayToGo(arr)\n"); - out.push_str("}\n"); - return out; - } - - // Tick endpoints have the same shape: empty array means either no rows - // or a failure (e.g. timeout). Consult the error slot directly. - writeln!( - out, - "\tif e := lastError(); e != \"\" {{\n\t\t{}(arr)\n\t\treturn nil, fmt.Errorf(\"thetadatadx: %s\", e)\n\t}}", - ffi_free_fn(&endpoint.return_type) - ) - .unwrap(); - writeln!( - out, - "\tresult := {}(arr)", - go_converter_name(&endpoint.return_type) - ) - .unwrap(); - writeln!(out, "\t{}(arr)", ffi_free_fn(&endpoint.return_type)).unwrap(); - out.push_str("\treturn result, nil\n"); - out.push_str("}\n"); - out -} diff --git a/crates/thetadatadx/build_support/endpoints/render/go_validate.rs b/crates/thetadatadx/build_support/endpoints/render/go_validate.rs deleted file mode 100644 index 81c22be1..00000000 --- a/crates/thetadatadx/build_support/endpoints/render/go_validate.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Live parameter-mode matrix validator generator for the Go SDK. -//! -//! Emits `sdks/go/validate.go`: one goroutine-backed cell per -//! (endpoint, mode), with a select/timeout wrapper and a shared classifier. -//! The function body and helpers live in `templates/validate_go/`. - -use std::fmt::Write as _; - -use super::super::helpers::{ - go_arg_literal, go_builder_option, is_streaming_endpoint, method_params, to_go_exported_name, -}; -use super::super::model::{GeneratedEndpoint, TestFixtures}; -use super::super::modes::test_modes_for; - -/// Generate the Go SDK validator (one row per (endpoint, mode) pair). -pub(super) fn render_go_validate( - endpoints: &[GeneratedEndpoint], - fixtures: &TestFixtures, -) -> String { - let mut out = String::from(include_str!("templates/validate_go/preamble.go.tmpl")); - for endpoint in endpoints - .iter() - .filter(|endpoint| !is_streaming_endpoint(endpoint)) - { - let go_name = to_go_exported_name(&endpoint.name); - let mp = method_params(endpoint); - for mode in test_modes_for(endpoint, fixtures) { - let mut args_parts: Vec = mp - .iter() - .zip(mode.args.iter()) - .map(|(param, value)| go_arg_literal(param, value)) - .collect(); - for (name, value) in &mode.builder_overrides { - if let Some(opt) = go_builder_option(endpoint, name, value) { - args_parts.push(opt); - } - } - // Cross-cutting deadline (W3): SDK enforces and cancels on expiry. - // Bulk-chain / all-strike cells use `slowModeTimeoutMs` since a - // full option chain payload legitimately takes longer than 60s. - let timeout_sym = if matches!(mode.name.as_str(), "all_strikes_one_exp" | "bulk_chain") - { - "slowModeTimeoutMs" - } else { - "perCellTimeoutMs" - }; - args_parts.push(format!("WithTimeoutMs({timeout_sym})")); - let args = args_parts.join(", "); - write!( - out, - include_str!("templates/validate_go/cell.go.tmpl"), - endpoint = endpoint.name, - mode = mode.name, - min_tier = mode.min_tier, - rationale = mode.rationale, - go_method_name = go_name, - args = args, - ) - .unwrap(); - } - out.push('\n'); - } - out.push_str(include_str!("templates/validate_go/postamble.go.tmpl")); - out -} diff --git a/crates/thetadatadx/build_support/endpoints/render/mod.rs b/crates/thetadatadx/build_support/endpoints/render/mod.rs index f168c45a..b99bc6aa 100644 --- a/crates/thetadatadx/build_support/endpoints/render/mod.rs +++ b/crates/thetadatadx/build_support/endpoints/render/mod.rs @@ -12,8 +12,6 @@ mod cpp; mod cpp_validate; mod enums; mod ffi; -mod go; -mod go_validate; mod mdds; mod python; mod python_validate; diff --git a/crates/thetadatadx/build_support/endpoints/render/sdk_files.rs b/crates/thetadatadx/build_support/endpoints/render/sdk_files.rs index 49188256..b688fb95 100644 --- a/crates/thetadatadx/build_support/endpoints/render/sdk_files.rs +++ b/crates/thetadatadx/build_support/endpoints/render/sdk_files.rs @@ -10,10 +10,7 @@ use std::path::Path; use super::super::helpers::collect_builder_params; use super::super::parser::{load_endpoint_specs, validate_test_fixtures}; -use super::{ - cli_validate, cpp, cpp_validate, enums, ffi, go, go_validate, python, python_validate, - typescript, -}; +use super::{cli_validate, cpp, cpp_validate, enums, ffi, python, python_validate, typescript}; struct GeneratedSourceFile { relative_path: &'static str, @@ -81,26 +78,6 @@ fn render_sdk_generated_files() -> Result, Box Result, Box 200 { - detail = detail[:200] - } - rec.Detail = detail - rec.RowCount = 0 - return append(records, rec) -} diff --git a/crates/thetadatadx/build_support/endpoints/render/templates/validate_go/preamble.go.tmpl b/crates/thetadatadx/build_support/endpoints/render/templates/validate_go/preamble.go.tmpl deleted file mode 100644 index 4da6b723..00000000 --- a/crates/thetadatadx/build_support/endpoints/render/templates/validate_go/preamble.go.tmpl +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by generate_sdk_surfaces from endpoint_surface.toml; DO NOT EDIT. - -package thetadatadx - -import ( - "fmt" - "reflect" - "strings" - "time" -) - -// perCellTimeoutMs caps each live cell at 60 seconds; slowModeTimeoutMs -// applies to bulk-chain / all-strike cells whose full option chain -// payload legitimately takes longer than a minute. The Rust SDK -// enforces the deadline via tokio::time::timeout and cancels the -// in-flight gRPC stream on expiry; the *Client handle stays usable. -// -const ( - perCellTimeoutMs uint64 = 60_000 - slowModeTimeoutMs uint64 = 180_000 -) - -// CellRecord is one row of the validator's per-cell JSON artifact used -// by the cross-language agreement check. `Status` is PASS|SKIP|FAIL. -// `Rationale` is the one-sentence description from the generator -// (rationale_for_mode in build_support/endpoints/modes.rs); it surfaces -// in scripts/validate_agreement.py disagreement output so a FAIL line -// carries the feature description, not just the mode name. -type CellRecord struct { - Endpoint string `json:"endpoint"` - Mode string `json:"mode"` - Rationale string `json:"rationale"` - Status string `json:"status"` - RowCount int `json:"row_count"` - DurationMS int64 `json:"duration_ms"` - Detail string `json:"detail"` -} - -// goRowCount returns len() for slices, 1 for non-nil non-slice, 0 for -// nil. Uses reflect so it works across all SDK tick types uniformly -// without a huge switch. -func goRowCount(v interface{}) int { - if v == nil { - return 0 - } - rv := reflect.ValueOf(v) - if rv.Kind() == reflect.Slice { - return rv.Len() - } - return 1 -} - -// ValidateAllEndpoints runs the live parameter-mode matrix against `c`. -// Every cell is attempted against production; the server is the ground -// truth for what the account can access. Cells whose documented min_tier -// exceeds the live account tier come back as a permission error and are -// classified as SKIP: tier-permission. Real configuration bugs surface -// as FAIL. Cells that don't finish within 60 seconds are cancelled by -// the SDK and classified as FAIL with "timeout after 60s". Returns -// (pass, skip, fail, records) — there is no `hadTimeout` flag any more -// because the SDK cleans up after itself; subsequent cells run normally -// on the same `*Client`. See issues #287, #290 and W3. -func ValidateAllEndpoints(c *Client) (int, int, int, []CellRecord) { - pass, skip, fail := 0, 0, 0 - records := make([]CellRecord, 0) - diff --git a/crates/thetadatadx/build_support/fpss_events/ffi_c.rs b/crates/thetadatadx/build_support/fpss_events/ffi_c.rs index 6b5c5d18..b5b308c9 100644 --- a/crates/thetadatadx/build_support/fpss_events/ffi_c.rs +++ b/crates/thetadatadx/build_support/fpss_events/ffi_c.rs @@ -1,5 +1,4 @@ -//! C mirror header emitter (`sdks/go/fpss_event_structs.h.inc` + -//! `sdks/cpp/include/fpss_event_structs.h.inc`). +//! C mirror header emitter (`sdks/cpp/include/fpss_event_structs.h.inc`). use std::fmt::Write as _; @@ -28,22 +27,20 @@ typedef struct {\n\ } /// Emit the C mirror of the Rust FFI event structs. `#include`'d from -/// `sdks/go/ffi_bridge.h` for Go's cgo consumption AND from /// `sdks/cpp/include/thetadx.h` for the C++ SDK. Keeps field order and /// padding identical to the Rust `#[repr(C)]` layout by using the same /// schema column ordering on both sides — the old hand-written C++ block /// in `thetadx.h` drifted from Rust (wrong field order in `TdxFpssEvent` /// silently corrupted every FPSS event the C++ SDK read), so the -/// generator now owns both copies. +/// generator now owns this copy. pub(super) fn render_c_fpss_event_header(schema: &Schema) -> String { let mut out = String::new(); out.push_str( "/* @generated DO NOT EDIT — regenerated by build.rs from fpss_event_schema.toml */\n", ); out.push_str("/* C mirror of the Rust FFI FPSS event structs. #include'd from\n"); - out.push_str(" * sdks/go/ffi_bridge.h (Go cgo) and sdks/cpp/include/thetadx.h\n"); - out.push_str(" * (C++ SDK). Plain C typedefs — both surfaces see byte-identical\n"); - out.push_str(" * layout. Do not hand-edit. */\n\n"); + out.push_str(" * sdks/cpp/include/thetadx.h (C++ SDK). Plain C typedefs.\n"); + out.push_str(" * Do not hand-edit. */\n\n"); // `` is needed for the `bool` type used by the Contract // struct's `has_*` tagged-optional flags. Safe to include repeatedly diff --git a/crates/thetadatadx/build_support/fpss_events/mod.rs b/crates/thetadatadx/build_support/fpss_events/mod.rs index ceeba49c..f50c516e 100644 --- a/crates/thetadatadx/build_support/fpss_events/mod.rs +++ b/crates/thetadatadx/build_support/fpss_events/mod.rs @@ -26,8 +26,7 @@ //! TypeScript SDK crates. //! * [`python`] / [`typescript`] — per-SDK typed event classes + dispatcher. //! * [`ffi_rust`] — `#[repr(C)]` structs + converter for the Rust FFI crate. -//! * [`ffi_c`] — C mirror header `#include`'d from both Go cgo and C++ SDK. -//! * [`go_structs`] — Go-idiomatic public types + kind/control constants. +//! * [`ffi_c`] — C mirror header `#include`'d from the C++ SDK. // Reason: the fpss_events/ tree is reached through two compilation // contexts — `build.rs` (which only needs the schema loader + emitters @@ -48,7 +47,6 @@ mod common; mod cpp_asserts; mod ffi_c; mod ffi_rust; -mod go_structs; mod layout; mod python; mod schema; @@ -122,15 +120,9 @@ fn render_sdk_generated_files() -> Result, Box Result, Box String { out } -pub(super) fn pascal_case(value: &str) -> String { - let mut out = String::new(); - for part in value.split('_').filter(|part| !part.is_empty()) { - let mut chars = part.chars(); - if let Some(first) = chars.next() { - out.push(first.to_ascii_uppercase()); - out.extend(chars); - } - } - out -} - -pub(super) fn lower_camel_case(value: &str) -> String { - let pascal = pascal_case(value); - let mut chars = pascal.chars(); - let Some(first) = chars.next() else { - return String::new(); - }; - let mut out = String::new(); - out.push(first.to_ascii_lowercase()); - out.extend(chars); - out -} - -pub(super) fn go_exported_name(name: &str) -> String { - pascal_case(name) -} - -pub(super) fn go_param_name(name: &str) -> String { - lower_camel_case(name) -} - -pub(super) fn go_c_var(name: &str) -> String { - format!("c{}", pascal_case(name)) -} - pub(super) fn push_rust_doc_comment(out: &mut String, indent: &str, doc: &str) { for line in doc.lines() { writeln!(out, "{indent}/// {line}").unwrap(); @@ -134,17 +98,6 @@ pub(super) fn python_type(param_type: ParamType) -> &'static str { } } -pub(super) fn go_type(param_type: ParamType) -> &'static str { - match param_type { - ParamType::String => "string", - ParamType::F64 => "float64", - ParamType::I32 => "int", - ParamType::U64 => "uint64", - ParamType::CredentialsRef => "*Credentials", - ParamType::ConfigRef => "*Config", - } -} - pub(super) fn cpp_type(param_type: ParamType) -> &'static str { match param_type { ParamType::String => "const std::string&", diff --git a/crates/thetadatadx/build_support/sdk_surface/go.rs b/crates/thetadatadx/build_support/sdk_surface/go.rs deleted file mode 100644 index 530f0e17..00000000 --- a/crates/thetadatadx/build_support/sdk_surface/go.rs +++ /dev/null @@ -1,393 +0,0 @@ -//! Go FPSS methods + utility functions (cgo bindings). -//! -//! Also hosts [`inject_os_thread_pin`]: a post-pass that inserts -//! `runtime.LockOSThread()` + deferred unlock at the top of any emitted -//! Go method whose body reads the FFI's thread-local error slot. - -use std::fmt::Write as _; - -use super::common::{go_c_var, go_exported_name, go_param_name, go_type, greek_result_fields}; -use super::spec::{MethodKind, MethodSpec, TlsReaderMarker, UtilityKind, UtilitySpec}; - -pub(super) fn render_go_fpss_methods( - methods: &[&MethodSpec], - tls_reader_markers: &[TlsReaderMarker], -) -> String { - let mut out = String::new(); - // Go spec header: `^// Code generated .* DO NOT EDIT\.$` — see - // https://golang.org/s/generatedcode. Required for go vet / gofmt / - // staticcheck to recognize this as machine-generated. Non-Go - // emitters keep the uniform `@generated` form. - out.push_str("// Code generated by build.rs from sdk_surface.toml; DO NOT EDIT.\n\n"); - out.push_str("package thetadatadx\n\n"); - out.push_str("/*\n#include \"ffi_bridge.h\"\n*/\nimport \"C\"\n\n"); - out.push_str("import (\n\t\"fmt\"\n\t\"runtime\"\n\t\"unsafe\"\n)\n\n"); - for method in methods { - out.push_str(&inject_os_thread_pin( - &go_fpss_method(method), - tls_reader_markers, - )); - out.push('\n'); - } - out -} - -pub(super) fn render_go_utility_functions( - utilities: &[&UtilitySpec], - tls_reader_markers: &[TlsReaderMarker], -) -> String { - let mut out = String::new(); - // Go spec header (see render_go_fpss_methods for the rationale). - out.push_str("// Code generated by build.rs from sdk_surface.toml; DO NOT EDIT.\n\n"); - out.push_str("package thetadatadx\n\n"); - out.push_str("/*\n#include \n#include \"ffi_bridge.h\"\n*/\nimport \"C\"\n\n"); - out.push_str("import (\n\t\"fmt\"\n\t\"runtime\"\n\t\"unsafe\"\n)\n\n"); - for utility in utilities { - out.push_str(&inject_os_thread_pin( - &go_utility_function(utility), - tls_reader_markers, - )); - out.push('\n'); - } - out -} - -fn go_fpss_method(method: &MethodSpec) -> String { - let mut out = String::new(); - let exported_name = go_exported_name(&method.name); - writeln!(out, "// {exported_name} {}", method.doc).unwrap(); - match method.kind { - MethodKind::StockContractCall | MethodKind::FullCall => { - let param = &method.params[0]; - let go_name = go_param_name(¶m.name); - let c_var = go_c_var(¶m.name); - writeln!( - out, - "func (f *FpssClient) {exported_name}({go_name} {}) (int, error) {{", - go_type(param.param_type) - ) - .unwrap(); - writeln!(out, " {c_var} := C.CString({go_name})").unwrap(); - writeln!(out, " defer C.free(unsafe.Pointer({c_var}))").unwrap(); - writeln!( - out, - " return f.fpssCall(C.tdx_fpss_{}(f.handle, {c_var}))", - method.ffi_call.as_deref().unwrap() - ) - .unwrap(); - out.push_str("}\n"); - } - MethodKind::OptionContractCall => { - let params = method - .params - .iter() - .map(|param| { - format!( - "{} {}", - go_param_name(¶m.name), - go_type(param.param_type) - ) - }) - .collect::>() - .join(", "); - writeln!( - out, - "func (f *FpssClient) {exported_name}({params}) (int, error) {{" - ) - .unwrap(); - for param in &method.params { - writeln!( - out, - " {} := C.CString({})", - go_c_var(¶m.name), - go_param_name(¶m.name) - ) - .unwrap(); - } - for param in &method.params { - writeln!( - out, - " defer C.free(unsafe.Pointer({}))", - go_c_var(¶m.name) - ) - .unwrap(); - } - let ffi_args = method - .params - .iter() - .map(|param| go_c_var(¶m.name)) - .collect::>() - .join(", "); - writeln!( - out, - " return f.fpssCall(C.tdx_fpss_{}(f.handle, {ffi_args}))", - method.ffi_call.as_deref().unwrap() - ) - .unwrap(); - out.push_str("}\n"); - } - MethodKind::IsAuthenticated => { - writeln!(out, "func (f *FpssClient) {exported_name}() bool {{").unwrap(); - out.push_str(" return C.tdx_fpss_is_authenticated(f.handle) != 0\n"); - out.push_str("}\n"); - } - MethodKind::ContractLookup => { - let param = &method.params[0]; - let name = go_param_name(¶m.name); - writeln!( - out, - "func (f *FpssClient) {exported_name}({name} {}) (string, error) {{", - go_type(param.param_type) - ) - .unwrap(); - writeln!( - out, - " cstr := C.tdx_fpss_contract_lookup(f.handle, C.int({name}))" - ) - .unwrap(); - out.push_str(" if cstr == nil {\n"); - out.push_str(" if msg := lastError(); msg != \"\" {\n"); - out.push_str(" return \"\", fmt.Errorf(\"thetadatadx: %s\", msg)\n"); - out.push_str(" }\n"); - out.push_str(" return \"\", nil\n"); - out.push_str(" }\n"); - out.push_str(" goStr := C.GoString(cstr)\n"); - out.push_str(" C.tdx_string_free(cstr)\n"); - out.push_str(" return goStr, nil\n"); - out.push_str("}\n"); - } - MethodKind::ContractMap => { - writeln!( - out, - "func (f *FpssClient) {exported_name}() (map[int32]string, error) {{" - ) - .unwrap(); - out.push_str(" arr := C.tdx_fpss_contract_map(f.handle)\n"); - out.push_str(" if arr == nil {\n"); - out.push_str(" return nil, fmt.Errorf(\"thetadatadx: %s\", lastError())\n"); - out.push_str(" }\n"); - out.push_str(" defer C.tdx_contract_map_array_free(arr)\n"); - out.push_str(" result := make(map[int32]string, int(arr.len))\n"); - out.push_str(" if arr.data == nil || arr.len == 0 {\n"); - out.push_str(" return result, nil\n"); - out.push_str(" }\n"); - out.push_str(" entries := unsafe.Slice(arr.data, int(arr.len))\n"); - out.push_str(" for _, entry := range entries {\n"); - out.push_str(" value := \"\"\n"); - out.push_str(" if entry.contract != nil {\n"); - out.push_str(" value = C.GoString(entry.contract)\n"); - out.push_str(" }\n"); - out.push_str(" result[int32(entry.id)] = value\n"); - out.push_str(" }\n"); - out.push_str(" return result, nil\n"); - out.push_str("}\n"); - } - MethodKind::ActiveSubscriptions => { - writeln!( - out, - "func (f *FpssClient) {exported_name}() ([]Subscription, error) {{" - ) - .unwrap(); - out.push_str(" arr := C.tdx_fpss_active_subscriptions(f.handle)\n"); - out.push_str(" if arr == nil {\n"); - out.push_str(" return nil, fmt.Errorf(\"thetadatadx: %s\", lastError())\n"); - out.push_str(" }\n"); - out.push_str(" defer C.tdx_subscription_array_free(arr)\n"); - out.push_str(" n := int(arr.len)\n"); - out.push_str(" if n == 0 || arr.data == nil {\n"); - out.push_str(" return nil, nil\n"); - out.push_str(" }\n"); - out.push_str(" subs := unsafe.Slice(arr.data, n)\n"); - out.push_str(" result := make([]Subscription, n)\n"); - out.push_str(" for i := 0; i < n; i++ {\n"); - out.push_str(" if subs[i].kind != nil {\n"); - out.push_str(" result[i].Kind = C.GoString(subs[i].kind)\n"); - out.push_str(" }\n"); - out.push_str(" if subs[i].contract != nil {\n"); - out.push_str(" result[i].Contract = C.GoString(subs[i].contract)\n"); - out.push_str(" }\n"); - out.push_str(" }\n"); - out.push_str(" return result, nil\n"); - out.push_str("}\n"); - } - MethodKind::NextEvent => { - let param = &method.params[0]; - let name = go_param_name(¶m.name); - writeln!( - out, - "func (f *FpssClient) {exported_name}({name} {}) (*FpssEvent, error) {{", - go_type(param.param_type) - ) - .unwrap(); - writeln!( - out, - " raw := C.tdx_fpss_next_event(f.handle, C.uint64_t({name}))" - ) - .unwrap(); - out.push_str(include_str!("templates/go/next_event_body.go.tmpl")); - } - MethodKind::Reconnect => { - writeln!(out, "func (f *FpssClient) {exported_name}() error {{").unwrap(); - out.push_str(" rc := C.tdx_fpss_reconnect(f.handle)\n"); - out.push_str(" if rc < 0 {\n"); - out.push_str(" return fmt.Errorf(\"thetadatadx: %s\", lastError())\n"); - out.push_str(" }\n"); - out.push_str(" return nil\n"); - out.push_str("}\n"); - } - MethodKind::Shutdown => { - writeln!(out, "func (f *FpssClient) {exported_name}() {{").unwrap(); - out.push_str(" if f.handle != nil {\n"); - out.push_str(" C.tdx_fpss_shutdown(f.handle)\n"); - out.push_str(" }\n"); - out.push_str("}\n"); - } - other => panic!("unsupported Go method kind: {other:?}"), - } - out -} - -fn go_greeks_field_name(field: &str) -> &'static str { - match field { - "value" => "Value", - "iv" => "IV", - "iv_error" => "IVError", - "delta" => "Delta", - "gamma" => "Gamma", - "theta" => "Theta", - "vega" => "Vega", - "rho" => "Rho", - "vanna" => "Vanna", - "charm" => "Charm", - "vomma" => "Vomma", - "veta" => "Veta", - "vera" => "Vera", - "speed" => "Speed", - "zomma" => "Zomma", - "color" => "Color", - "ultima" => "Ultima", - "d1" => "D1", - "d2" => "D2", - "dual_delta" => "DualDelta", - "dual_gamma" => "DualGamma", - "epsilon" => "Epsilon", - "lambda" => "Lambda", - other => panic!("unknown Greeks field: {other}"), - } -} - -fn go_utility_function(utility: &UtilitySpec) -> String { - let mut out = String::new(); - let exported_name = go_exported_name(&utility.name); - writeln!(out, "// {exported_name} {}", utility.doc).unwrap(); - match utility.kind { - UtilityKind::AllGreeks => { - let params = utility - .params - .iter() - .map(|param| { - format!( - "{} {}", - go_param_name(¶m.name), - go_type(param.param_type) - ) - }) - .collect::>() - .join(", "); - writeln!(out, "func {exported_name}({params}) (*Greeks, error) {{").unwrap(); - out.push_str(" cRight := C.CString(right)\n"); - out.push_str(" defer C.free(unsafe.Pointer(cRight))\n"); - out.push_str(" ptr := C.tdx_all_greeks(C.double(spot), C.double(strike), C.double(rate), C.double(divYield), C.double(tte), C.double(optionPrice), cRight)\n"); - out.push_str(" if ptr == nil {\n"); - out.push_str(" return nil, fmt.Errorf(\"thetadatadx: %s\", lastError())\n"); - out.push_str(" }\n"); - out.push_str(" defer C.tdx_greeks_result_free(ptr)\n"); - out.push_str(" return &Greeks{\n"); - for (field, _) in greek_result_fields() { - writeln!( - out, - " {}: float64(ptr.{field}),", - go_greeks_field_name(field) - ) - .unwrap(); - } - out.push_str(" }, nil\n"); - out.push_str("}\n"); - } - UtilityKind::ImpliedVolatility => { - let params = utility - .params - .iter() - .map(|param| { - format!( - "{} {}", - go_param_name(¶m.name), - go_type(param.param_type) - ) - }) - .collect::>() - .join(", "); - writeln!( - out, - "func {exported_name}({params}) (float64, float64, error) {{" - ) - .unwrap(); - out.push_str(" cRight := C.CString(right)\n"); - out.push_str(" defer C.free(unsafe.Pointer(cRight))\n"); - out.push_str(" var iv, ivErr C.double\n"); - out.push_str(" rc := C.tdx_implied_volatility(C.double(spot), C.double(strike), C.double(rate), C.double(divYield), C.double(tte), C.double(optionPrice), cRight, &iv, &ivErr)\n"); - out.push_str(" if rc != 0 {\n"); - out.push_str(" return 0, 0, fmt.Errorf(\"thetadatadx: %s\", lastError())\n"); - out.push_str(" }\n"); - out.push_str(" return float64(iv), float64(ivErr), nil\n"); - out.push_str("}\n"); - } - other => panic!("unsupported Go utility kind: {other:?}"), - } - out -} - -/// Insert `runtime.LockOSThread()` + deferred unlock at the top of every -/// Go method body in `src` whose body reads the FFI's thread-local error -/// slot (any substring in `sdk_surface.toml`'s -/// `go_ffi.tls_reader_markers`). Methods without TLS reads pass through -/// unchanged. -fn inject_os_thread_pin(src: &str, tls_reader_markers: &[TlsReaderMarker]) -> String { - let mut out = String::new(); - let lines: Vec<&str> = src.split_inclusive('\n').collect(); - let mut i = 0; - while i < lines.len() { - let line = lines[i]; - if line.starts_with("func ") && line.trim_end().ends_with('{') { - let mut j = i + 1; - let mut touches_tls = false; - while j < lines.len() { - let l = lines[j]; - if l.starts_with('}') { - break; - } - if tls_reader_markers - .iter() - .any(|marker| l.contains(&marker.substring)) - { - touches_tls = true; - } - j += 1; - } - out.push_str(line); - if touches_tls { - out.push_str(" runtime.LockOSThread()\n"); - out.push_str(" defer runtime.UnlockOSThread()\n"); - } - let end = j.min(lines.len().saturating_sub(1)); - for body_line in lines.iter().skip(i + 1).take(end.saturating_sub(i)) { - out.push_str(body_line); - } - i = j + 1; - continue; - } - out.push_str(line); - i += 1; - } - out -} diff --git a/crates/thetadatadx/build_support/sdk_surface/mod.rs b/crates/thetadatadx/build_support/sdk_surface/mod.rs index 9692c180..6c68c302 100644 --- a/crates/thetadatadx/build_support/sdk_surface/mod.rs +++ b/crates/thetadatadx/build_support/sdk_surface/mod.rs @@ -14,7 +14,7 @@ //! * [`spec`] — TOML-backed data types + validation. //! * [`common`] — shared renderer helpers (string/case helpers, type maps, //! generated-header, greek/param layouts). -//! * [`python`] / [`typescript`] / [`go`] / [`cpp`] / [`mcp`] / [`cli`] — +//! * [`python`] / [`typescript`] / [`cpp`] / [`mcp`] / [`cli`] — //! one file per render target. // Reason: shared between build.rs and generate_sdk_surfaces binary via #[path]; not all @@ -26,7 +26,6 @@ use std::path::Path; mod cli; mod common; mod cpp; -mod go; mod mcp; mod python; mod spec; @@ -76,11 +75,6 @@ fn render_sdk_generated_files() -> Result, Box = spec - .methods - .iter() - .filter(|method| method.targets.contains(&MethodTarget::GoFpss)) - .collect(); let cpp_fpss_methods: Vec<&MethodSpec> = spec .methods .iter() @@ -101,11 +95,6 @@ fn render_sdk_generated_files() -> Result, Box = spec - .utilities - .iter() - .filter(|utility| utility.targets.contains(&UtilityTarget::Go)) - .collect(); let cpp_utilities: Vec<&UtilitySpec> = spec .utilities .iter() @@ -122,11 +111,6 @@ fn render_sdk_generated_files() -> Result, Box Result, Box, #[serde(default)] pub(super) utilities: Vec, - /// Go-side FFI configuration. Holds the TLS-reader marker SSOT that - /// drives both `inject_os_thread_pin` (build-time body rewriter) and - /// the generated `tlsReaderMarkers` list consumed by the static-audit - /// test in `sdks/go/timeout_pin_test.go`. - #[serde(default)] - pub(super) go_ffi: GoFfiSpec, } #[derive(Debug, Clone, Deserialize)] @@ -68,7 +62,6 @@ pub(super) enum MethodKind { #[serde(rename_all = "snake_case")] pub(super) enum MethodTarget { PythonUnified, - GoFpss, CppFpss, CppLifecycle, TypescriptNapi, @@ -104,7 +97,6 @@ pub(super) enum UtilityKind { #[serde(rename_all = "snake_case")] pub(super) enum UtilityTarget { Python, - Go, Cpp, Mcp, Cli, @@ -138,23 +130,6 @@ pub(super) enum ParamType { ConfigRef, } -#[derive(Debug, Clone, Default, Deserialize)] -#[serde(deny_unknown_fields)] -pub(super) struct GoFfiSpec { - #[serde(default)] - pub(super) tls_reader_markers: Vec, -} - -/// A substring that, when present on a Go source line, identifies an FFI -/// thread-local error read. The enclosing function must have executed -/// `runtime.LockOSThread()` + `defer runtime.UnlockOSThread()` before -/// reaching such a line. -#[derive(Debug, Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub(super) struct TlsReaderMarker { - pub(super) substring: String, -} - /// Rendering shape shared by the `MethodKind` match arms in /// `validate_method_spec`. Name extracted to keep /// `clippy::type_complexity` happy and document the structure: @@ -200,22 +175,6 @@ pub(super) fn validate_spec(spec: &SdkSurfaceSpec) -> Result<(), Box Result<(), Box Result<(), Box (Some("is_streaming"), &[PY, TS], true, &[]), MethodKind::StockContractCall => ( None, - &[PY, TS, GO, CPP], + &[PY, TS, CPP], false, &[("symbol", ParamType::String)], ), MethodKind::OptionContractCall => ( None, - &[PY, TS, GO, CPP], + &[PY, TS, CPP], false, &[ ("symbol", ParamType::String), @@ -273,30 +231,30 @@ fn validate_method_spec(method: &MethodSpec) -> Result<(), Box ( None, - &[PY, TS, GO, CPP], + &[PY, TS, CPP], false, &[("sec_type", ParamType::String)], ), - MethodKind::ContractMap => (Some("contract_map"), &[PY, TS, GO, CPP], false, &[]), + MethodKind::ContractMap => (Some("contract_map"), &[PY, TS, CPP], false, &[]), MethodKind::ContractLookup => ( Some("contract_lookup"), - &[PY, TS, GO, CPP], + &[PY, TS, CPP], false, &[("id", ParamType::I32)], ), MethodKind::ActiveSubscriptions => { - (Some("active_subscriptions"), &[PY, TS, GO, CPP], false, &[]) + (Some("active_subscriptions"), &[PY, TS, CPP], false, &[]) } MethodKind::NextEvent => ( Some("next_event"), - &[PY, TS, GO, CPP], + &[PY, TS, CPP], false, &[("timeout_ms", ParamType::U64)], ), - MethodKind::Reconnect => (Some("reconnect"), &[PY, TS, GO, CPP], false, &[]), + MethodKind::Reconnect => (Some("reconnect"), &[PY, TS, CPP], false, &[]), MethodKind::StopStreaming => (Some("stop_streaming"), &[PY, TS], true, &[]), - MethodKind::Shutdown => (Some("shutdown"), &[PY, TS, GO, CPP], false, &[]), - MethodKind::IsAuthenticated => (Some("is_authenticated"), &[GO, CPP], false, &[]), + MethodKind::Shutdown => (Some("shutdown"), &[PY, TS, CPP], false, &[]), + MethodKind::IsAuthenticated => (Some("is_authenticated"), &[CPP], false, &[]), MethodKind::FpssConnect => ( Some("connect"), &[CPP], @@ -404,7 +362,7 @@ fn validate_utility_spec(utility: &UtilitySpec) -> Result<(), Box Result<(), Box ("ping", &[Mcp], true, &[]), UtilityKind::AllGreeks => ( "all_greeks", - &[Python, Go, Cpp, Mcp, Cli], + &[Python, Cpp, Mcp, Cli], false, &greeks_params, ), UtilityKind::ImpliedVolatility => ( "implied_volatility", - &[Python, Go, Cpp, Mcp, Cli], + &[Python, Cpp, Mcp, Cli], false, &greeks_params, ), diff --git a/crates/thetadatadx/build_support/ticks/cpp.rs b/crates/thetadatadx/build_support/ticks/cpp.rs index 54f8ac71..6ae8c289 100644 --- a/crates/thetadatadx/build_support/ticks/cpp.rs +++ b/crates/thetadatadx/build_support/ticks/cpp.rs @@ -2,7 +2,7 @@ use std::fmt::Write as _; -use super::go::{tick_ffi_offsets, tick_ffi_size_and_align}; +use super::layout::{tick_ffi_offsets, tick_ffi_size_and_align}; use super::schema::Schema; use super::sorted_type_names; diff --git a/crates/thetadatadx/build_support/ticks/layout.rs b/crates/thetadatadx/build_support/ticks/layout.rs new file mode 100644 index 00000000..b79c522d --- /dev/null +++ b/crates/thetadatadx/build_support/ticks/layout.rs @@ -0,0 +1,78 @@ +//! Tick FFI layout computation — `(field, offset)` pairs and `(size, align)` +//! for each `#[repr(C, align(N))]` tick struct described in +//! `tick_schema.toml`. +//! +//! Used by the C++ layout-assert emitter (`build_support/ticks/cpp.rs`) and +//! the tdbe layout-assert emitter (`build_support/ticks/tdbe_structs.rs`) +//! to verify Rust ↔ C++ ↔ tdbe struct ABI agreement at build time. + +use super::schema::TickTypeDef; + +/// `(field, byte_offset)` pairs for every column the parser fills, in +/// declaration order, including the `contract_id` triple and +/// `QuoteTick.midpoint` tail. +pub(super) fn tick_ffi_offsets(type_name: &str, def: &TickTypeDef) -> Vec<(String, usize)> { + let mut offsets = Vec::new(); + let mut size = 0usize; + for (field_name, field_type) in tick_ffi_fields(type_name, def) { + let (field_size, field_align) = tick_ffi_field_layout(field_type); + size = align_to(size, field_align); + offsets.push((field_name.to_string(), size)); + size += field_size; + } + offsets +} + +/// Compute `(size, alignment)` of an FFI tick struct from its +/// `tick_schema.toml` row. Mirrors `#[repr(C, align(N))]` — alignment is +/// the max of the schema's `align` directive and every field's natural +/// alignment, and size is rounded up to a multiple of that alignment to +/// reproduce Rust's struct tail padding. +pub(super) fn tick_ffi_size_and_align(type_name: &str, def: &TickTypeDef) -> (usize, usize) { + let mut size = 0usize; + let mut struct_align = def.align.unwrap_or(1) as usize; + for (_, field_type) in tick_ffi_fields(type_name, def) { + let (field_size, field_align) = tick_ffi_field_layout(field_type); + struct_align = struct_align.max(field_align); + size = align_to(size, field_align) + field_size; + } + (align_to(size, struct_align), struct_align) +} + +fn tick_ffi_fields<'a>(type_name: &'a str, def: &'a TickTypeDef) -> Vec<(&'a str, &'a str)> { + let mut fields = def + .columns + .iter() + .map(|column| (column.field.as_str(), column.r#type.as_str())) + .collect::>(); + if def.contract_id { + fields.push(("expiration", "i32")); + fields.push(("strike", "price")); + fields.push(("right", "i32")); + } + if type_name == "QuoteTick" { + fields.push(("midpoint", "price")); + } + fields +} + +fn tick_ffi_field_layout(kind: &str) -> (usize, usize) { + match kind { + "i32" | "eod_num" | "eod_date" => (4, 4), + "i64" | "eod_num64" | "f64" | "price" | "eod_price" => (8, 8), + "String" => ( + std::mem::size_of::<*const ()>(), + std::mem::align_of::<*const ()>(), + ), + other => panic!("unsupported tick FFI field type: {other}"), + } +} + +fn align_to(value: usize, align: usize) -> usize { + let rem = value % align; + if rem == 0 { + value + } else { + value + align - rem + } +} diff --git a/crates/thetadatadx/build_support/ticks/mod.rs b/crates/thetadatadx/build_support/ticks/mod.rs index 65c987dc..e7a6c204 100644 --- a/crates/thetadatadx/build_support/ticks/mod.rs +++ b/crates/thetadatadx/build_support/ticks/mod.rs @@ -13,7 +13,8 @@ //! * [`python_arrow`] / [`python_classes`] — Python `#[pyclass]` struct + Arrow //! columnar pipeline. //! * [`typescript`] — napi-rs `#[napi(object)]` tick types. -//! * [`go`] — Go FFI converters + public structs. +//! * [`layout`] — schema-driven `#[repr(C)]` size/align/offset math shared +//! by the C++ static-assert emitter and the `tdbe` layout-guard emitter. //! * [`cli_headers`] — `tools/cli/src/raw_headers_generated.rs` constants. //! //! # Removed surfaces (audit trail) @@ -40,7 +41,7 @@ use schema::Schema; mod cli_headers; mod cpp; -mod go; +mod layout; mod parser; mod python_arrow; mod python_classes; @@ -113,18 +114,6 @@ fn render_sdk_generated_files( relative_path: "crates/thetadatadx/src/frames_generated.rs", contents: rust_frames::render_rust_frames(&schema), }, - GeneratedSourceFile { - relative_path: "sdks/go/tick_converters.go", - contents: go::render_go_tick_converters(&schema), - }, - GeneratedSourceFile { - relative_path: "sdks/go/tick_ffi_sizes_generated.go", - contents: go::render_go_tick_ffi_sizes(&schema), - }, - GeneratedSourceFile { - relative_path: "sdks/go/ffi_layout_generated_test.go", - contents: go::render_go_tick_ffi_layout_tests(&schema), - }, GeneratedSourceFile { // tdbe tick structs -- `#[repr(C, align(N))]` definitions // emitted from the schema. Hand-written `tick.rs` `pub use`s @@ -146,10 +135,6 @@ fn render_sdk_generated_files( relative_path: "sdks/cpp/include/tick_layout_asserts.hpp.inc", contents: cpp::render_cpp_tick_layout_asserts(&schema), }, - GeneratedSourceFile { - relative_path: "sdks/go/tick_structs.go", - contents: go::render_go_tick_structs(&schema), - }, GeneratedSourceFile { relative_path: "tools/cli/src/raw_headers_generated.rs", contents: cli_headers::render_cli_raw_headers(&schema), diff --git a/crates/thetadatadx/build_support/ticks/schema.rs b/crates/thetadatadx/build_support/ticks/schema.rs index 4bc6d836..0f1a2b27 100644 --- a/crates/thetadatadx/build_support/ticks/schema.rs +++ b/crates/thetadatadx/build_support/ticks/schema.rs @@ -52,8 +52,6 @@ pub(crate) struct TickRenderDef { pub(crate) collection: String, pub(crate) direct: String, pub(crate) parser: String, - pub(crate) go_struct: String, - pub(crate) go_converter: String, pub(crate) ffi_array: String, pub(crate) ffi_output_variant: String, pub(crate) ffi_from_vec_array: String, @@ -80,7 +78,7 @@ pub(super) fn load_schema() -> Result> { /// Borrow the render block of a schema type by name. Panics with the /// available keys when the type is missing -- a missing tick type is a /// build-time bug. Used by every ticks/* emitter that previously kept a -/// hand-coded match arm per tick type for FFI / Python / TS / Go binding +/// hand-coded match arm per tick type for FFI / Python / TS binding /// names. pub(crate) fn render_for_type<'a>(schema: &'a Schema, type_name: &str) -> &'a TickRenderDef { schema diff --git a/crates/thetadatadx/build_support/ticks/tdbe_structs.rs b/crates/thetadatadx/build_support/ticks/tdbe_structs.rs index f3576588..162979e1 100644 --- a/crates/thetadatadx/build_support/ticks/tdbe_structs.rs +++ b/crates/thetadatadx/build_support/ticks/tdbe_structs.rs @@ -21,7 +21,7 @@ use std::fmt::Write as _; -use super::go::{tick_ffi_offsets, tick_ffi_size_and_align}; +use super::layout::{tick_ffi_offsets, tick_ffi_size_and_align}; use super::schema::{Schema, TickTypeDef}; use super::sorted_type_names; @@ -58,14 +58,12 @@ pub(super) fn render_tdbe_tick_structs(schema: &Schema) -> String { /// * `align_of::()` (catches `align(N)` drift), /// * `offset_of!(T, field)` for every column the parser fills, plus /// the `contract_id` triple and `QuoteTick.midpoint` tail. This is the -/// ABI that the C-mirror in `sdks/cpp/include/thetadx.h`, the C-mirror -/// in `sdks/go/tick_ffi_mirrors.go`, and the layout-assert `*.hpp.inc` -/// all index into via `offsetof()`. +/// ABI that the C-mirror in `sdks/cpp/include/thetadx.h` and the +/// layout-assert `*.hpp.inc` all index into via `offsetof()`. /// /// `OptionContract` is skipped: its FFI mirror carries a `*const c_char` /// pointer (`String` schema column) so it never crosses the C ABI as a -/// plain `#[repr(C)]` array element. Go FFI also skips it for the same -/// reason -- see `tick_ffi_offsets` callsite in `go.rs`. +/// plain `#[repr(C)]` array element. pub(super) fn render_tdbe_layout_asserts(schema: &Schema) -> String { let mut out = String::new(); out.push_str( diff --git a/crates/thetadatadx/endpoint_surface.toml b/crates/thetadatadx/endpoint_surface.toml index 01517ee5..d93f1a42 100644 --- a/crates/thetadatadx/endpoint_surface.toml +++ b/crates/thetadatadx/endpoint_surface.toml @@ -83,7 +83,7 @@ variants = [ ] # Global request-level options attached to every endpoint via -# `TdxEndpointRequestOptions` (FFI) / `EndpointOption` (Go) / +# `TdxEndpointRequestOptions` (FFI) / # `with_timeout_ms` (Rust/Python/C++). These are NOT per-endpoint # builder params — they apply uniformly across the surface — but the # FFI struct layout still needs to mirror this list, so we declare @@ -1879,7 +1879,7 @@ returns = "QuoteTicks" # per-language live-validator renderers emit. Every mode in # `build_support/endpoints/modes.rs` reads its fixture values from this block # — there are no hardcoded fixture literals in Rust. Editing one row here -# propagates to every generated validator (CLI, Python, Go, C++) the next +# propagates to every generated validator (CLI, Python, C++) the next # time `generate_sdk_surfaces` runs. # # `parser.rs::validate_test_fixtures` enforces fail-fast at build time: diff --git a/crates/thetadatadx/sdk_surface.toml b/crates/thetadatadx/sdk_surface.toml index 9d20a5cb..e9c1d072 100644 --- a/crates/thetadatadx/sdk_surface.toml +++ b/crates/thetadatadx/sdk_surface.toml @@ -32,7 +32,7 @@ kind = "stock_contract_call" doc = "Subscribe to real-time quote data for a stock symbol." runtime_call = "subscribe_quotes" ffi_call = "subscribe_quotes" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "symbol", type = "string", doc = "Stock symbol." }] [[methods]] @@ -41,7 +41,7 @@ kind = "stock_contract_call" doc = "Subscribe to real-time trade data for a stock symbol." runtime_call = "subscribe_trades" ffi_call = "subscribe_trades" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "symbol", type = "string", doc = "Stock symbol." }] [[methods]] @@ -50,7 +50,7 @@ kind = "stock_contract_call" doc = "Subscribe to open interest data for a stock symbol." runtime_call = "subscribe_open_interest" ffi_call = "subscribe_open_interest" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "symbol", type = "string", doc = "Stock symbol." }] [[methods]] @@ -59,7 +59,7 @@ kind = "option_contract_call" doc = "Subscribe to quote data for an option contract." runtime_call = "subscribe_quotes" ffi_call = "subscribe_option_quotes" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [ { name = "symbol", type = "string", doc = "Underlying symbol." }, { name = "expiration", type = "string", doc = "Option expiration." }, @@ -73,7 +73,7 @@ kind = "option_contract_call" doc = "Subscribe to trade data for an option contract." runtime_call = "subscribe_trades" ffi_call = "subscribe_option_trades" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [ { name = "symbol", type = "string", doc = "Underlying symbol." }, { name = "expiration", type = "string", doc = "Option expiration." }, @@ -87,7 +87,7 @@ kind = "option_contract_call" doc = "Subscribe to open interest data for an option contract." runtime_call = "subscribe_open_interest" ffi_call = "subscribe_option_open_interest" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [ { name = "symbol", type = "string", doc = "Underlying symbol." }, { name = "expiration", type = "string", doc = "Option expiration." }, @@ -101,7 +101,7 @@ kind = "full_call" doc = "Subscribe to all trades for a security type." runtime_call = "subscribe_full_trades" ffi_call = "subscribe_full_trades" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "sec_type", type = "string", doc = "Security type: STOCK, OPTION, or INDEX." }] [[methods]] @@ -110,7 +110,7 @@ kind = "full_call" doc = "Subscribe to all open interest for a security type." runtime_call = "subscribe_full_open_interest" ffi_call = "subscribe_full_open_interest" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "sec_type", type = "string", doc = "Security type: STOCK, OPTION, or INDEX." }] [[methods]] @@ -119,7 +119,7 @@ kind = "stock_contract_call" doc = "Unsubscribe from quote data for a stock symbol." runtime_call = "unsubscribe_quotes" ffi_call = "unsubscribe_quotes" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "symbol", type = "string", doc = "Stock symbol." }] [[methods]] @@ -128,7 +128,7 @@ kind = "stock_contract_call" doc = "Unsubscribe from trade data for a stock symbol." runtime_call = "unsubscribe_trades" ffi_call = "unsubscribe_trades" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "symbol", type = "string", doc = "Stock symbol." }] [[methods]] @@ -137,7 +137,7 @@ kind = "stock_contract_call" doc = "Unsubscribe from open interest data for a stock symbol." runtime_call = "unsubscribe_open_interest" ffi_call = "unsubscribe_open_interest" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "symbol", type = "string", doc = "Stock symbol." }] [[methods]] @@ -146,7 +146,7 @@ kind = "option_contract_call" doc = "Unsubscribe from quote data for an option contract." runtime_call = "unsubscribe_quotes" ffi_call = "unsubscribe_option_quotes" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [ { name = "symbol", type = "string", doc = "Underlying symbol." }, { name = "expiration", type = "string", doc = "Option expiration." }, @@ -160,7 +160,7 @@ kind = "option_contract_call" doc = "Unsubscribe from trade data for an option contract." runtime_call = "unsubscribe_trades" ffi_call = "unsubscribe_option_trades" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [ { name = "symbol", type = "string", doc = "Underlying symbol." }, { name = "expiration", type = "string", doc = "Option expiration." }, @@ -174,7 +174,7 @@ kind = "option_contract_call" doc = "Unsubscribe from open interest data for an option contract." runtime_call = "unsubscribe_open_interest" ffi_call = "unsubscribe_option_open_interest" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [ { name = "symbol", type = "string", doc = "Underlying symbol." }, { name = "expiration", type = "string", doc = "Option expiration." }, @@ -188,7 +188,7 @@ kind = "full_call" doc = "Unsubscribe from all trades for a security type." runtime_call = "unsubscribe_full_trades" ffi_call = "unsubscribe_full_trades" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "sec_type", type = "string", doc = "Security type: STOCK, OPTION, or INDEX." }] [[methods]] @@ -197,40 +197,40 @@ kind = "full_call" doc = "Unsubscribe from all open interest for a security type." runtime_call = "unsubscribe_full_open_interest" ffi_call = "unsubscribe_full_open_interest" -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "sec_type", type = "string", doc = "Security type: STOCK, OPTION, or INDEX." }] [[methods]] name = "contract_map" kind = "contract_map" doc = "Get the current contract map keyed by server-assigned contract ID." -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] [[methods]] name = "contract_lookup" kind = "contract_lookup" doc = "Look up a single contract by its server-assigned ID." -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "id", type = "i32", doc = "Server-assigned contract ID." }] [[methods]] name = "active_subscriptions" kind = "active_subscriptions" doc = "Get a snapshot of currently active subscriptions." -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] [[methods]] name = "next_event" kind = "next_event" doc = "Poll for the next FPSS event." -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] params = [{ name = "timeout_ms", type = "u64", doc = "Maximum time to wait in milliseconds." }] [[methods]] name = "reconnect" kind = "reconnect" doc = "Reconnect streaming and re-subscribe all previous subscriptions." -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] [[methods]] name = "stop_streaming" @@ -242,13 +242,13 @@ targets = ["python_unified", "typescript_napi"] name = "shutdown" kind = "shutdown" doc = "Shut down the FPSS streaming connection." -targets = ["python_unified", "typescript_napi", "go_fpss", "cpp_fpss"] +targets = ["python_unified", "typescript_napi", "cpp_fpss"] [[methods]] name = "is_authenticated" kind = "is_authenticated" doc = "Return true if the FPSS client is currently authenticated." -targets = ["go_fpss", "cpp_fpss"] +targets = ["cpp_fpss"] [[methods]] name = "connect" @@ -329,7 +329,7 @@ doc = "Compute all 23 Black-Scholes Greeks + IV in one call." cli_about = "Compute Black-Scholes Greeks (offline, no server needed)" mcp_description = "Compute all 23 Black-Scholes Greeks OFFLINE (no ThetaData server needed). Returns value, delta, gamma, theta, vega, rho, IV, vanna, charm, vomma, veta, speed, zomma, color, ultima, d1, d2, dual_delta, dual_gamma, epsilon, lambda, vera." cli_name = "greeks" -targets = ["python", "go", "cpp", "mcp", "cli"] +targets = ["python", "cpp", "mcp", "cli"] params = [ { name = "spot", type = "f64", doc = "Spot price.", mcp_description = "Spot price (underlying)" }, { name = "strike", type = "f64", doc = "Strike price." }, @@ -347,7 +347,7 @@ doc = "Compute implied volatility via bisection." cli_about = "Compute implied volatility only (offline, no server needed)" mcp_description = "Compute implied volatility OFFLINE using bisection (no ThetaData server needed). Returns IV and error." cli_name = "iv" -targets = ["python", "go", "cpp", "mcp", "cli"] +targets = ["python", "cpp", "mcp", "cli"] params = [ { name = "spot", type = "f64", doc = "Spot price.", mcp_description = "Spot price (underlying)" }, { name = "strike", type = "f64", doc = "Strike price." }, @@ -358,30 +358,3 @@ params = [ { name = "right", type = "string", doc = "Option side: C/P or call/put (case-insensitive).", mcp_description = "Option side: \"C\"/\"P\" or \"call\"/\"put\" (case-insensitive)", enum_values = ["C", "P", "c", "p", "call", "put", "CALL", "PUT", "Call", "Put"] }, ] -# ───────────────────────────────────────────────────────────────────────────── -# FFI thread-local-error reader markers (W3 / W7c) -# Substrings that, when present on a Go source line, identify an FFI -# thread-local error read. `inject_os_thread_pin` requires the enclosing -# function to pin its goroutine before reaching such a line. -# ───────────────────────────────────────────────────────────────────────────── - -# Go-side wrapper that reads LAST_ERROR after a failing FFI call. -[[go_ffi.tls_reader_markers]] -substring = "lastError(" - -# Lower-level wrapper returning the raw *C.char from LAST_ERROR. -[[go_ffi.tls_reader_markers]] -substring = "lastErrorRaw(" - -# FPSS cgo invocation followed by an inline LAST_ERROR check. -[[go_ffi.tls_reader_markers]] -substring = "f.fpssCall(" - -# Direct cgo call into the C FFI's tdx_last_error symbol. -[[go_ffi.tls_reader_markers]] -substring = "C.tdx_last_error(" - -# Helper that itself reads LAST_ERROR — callers must pin before invoking -# (the helper also self-pins as a safety net). -[[go_ffi.tls_reader_markers]] -substring = "stringArrayToGo(" diff --git a/crates/thetadatadx/tick_schema.toml b/crates/thetadatadx/tick_schema.toml index be2be237..64d8a5ad 100644 --- a/crates/thetadatadx/tick_schema.toml +++ b/crates/thetadatadx/tick_schema.toml @@ -4,7 +4,7 @@ # # build.rs reads this file and generates: # - DataTable parsers -> $OUT_DIR/decode_generated.rs -# - per-language SDK surfaces -> sdks/{python,typescript,go,cpp}/... +# - per-language SDK surfaces -> sdks/{python,typescript,cpp}/... # (driven entirely by [types.X.render]) # # Column types: @@ -34,12 +34,10 @@ # by `endpoint_surface.toml::returns`. Must be unique. # direct -- Rust direct-client tick type # parser -- generated parser fn name (matches `parser` above) -# go_struct -- Go SDK struct name -# go_converter -- Go SDK converter fn (cgo bridge) # ffi_array -- FFI array struct (e.g. "TdxGreeksAllTickArray") # ffi_output_variant -- thetadatadx::EndpointOutput variant name # ffi_from_vec_array -- FFI array struct used in Tdx*::from_vec calls -# ffi_header_return -- return type in generated C/Go header +# ffi_header_return -- return type in generated C header # ffi_free_fn -- C-side free fn name (without C. prefix) # cpp_value -- C++ value type name # python_converter -- legacy Python dict converter (PyDict) @@ -84,8 +82,6 @@ columns = [ collection = "TradeTicks" direct = "TradeTick" parser = "parse_trade_ticks" -go_struct = "TradeTick" -go_converter = "convertTradeTicks" ffi_array = "TdxTradeTickArray" ffi_output_variant = "TradeTicks" ffi_from_vec_array = "TdxTradeTickArray" @@ -125,8 +121,6 @@ columns = [ collection = "QuoteTicks" direct = "QuoteTick" parser = "parse_quote_ticks" -go_struct = "QuoteTick" -go_converter = "convertQuoteTicks" ffi_array = "TdxQuoteTickArray" ffi_output_variant = "QuoteTicks" ffi_from_vec_array = "TdxQuoteTickArray" @@ -164,8 +158,6 @@ columns = [ collection = "OhlcTicks" direct = "OhlcTick" parser = "parse_ohlc_ticks" -go_struct = "OhlcTick" -go_converter = "convertOhlcTicks" ffi_array = "TdxOhlcTickArray" ffi_output_variant = "OhlcTicks" ffi_from_vec_array = "TdxOhlcTickArray" @@ -213,8 +205,6 @@ columns = [ collection = "EodTicks" direct = "EodTick" parser = "parse_eod_ticks" -go_struct = "EodTick" -go_converter = "convertEodTicks" ffi_array = "TdxEodTickArray" ffi_output_variant = "EodTicks" ffi_from_vec_array = "TdxEodTickArray" @@ -251,8 +241,6 @@ columns = [ collection = "OpenInterestTicks" direct = "OpenInterestTick" parser = "parse_open_interest_ticks" -go_struct = "OpenInterestTick" -go_converter = "convertOpenInterestTicks" ffi_array = "TdxOpenInterestTickArray" ffi_output_variant = "OpenInterestTicks" ffi_from_vec_array = "TdxOpenInterestTickArray" @@ -306,8 +294,6 @@ columns = [ collection = "TradeQuoteTicks" direct = "TradeQuoteTick" parser = "parse_trade_quote_ticks" -go_struct = "TradeQuoteTick" -go_converter = "convertTradeQuoteTicks" ffi_array = "TdxTradeQuoteTickArray" ffi_output_variant = "TradeQuoteTicks" ffi_from_vec_array = "TdxTradeQuoteTickArray" @@ -342,8 +328,6 @@ columns = [ collection = "MarketValueTicks" direct = "MarketValueTick" parser = "parse_market_value_ticks" -go_struct = "MarketValueTick" -go_converter = "convertMarketValueTicks" ffi_array = "TdxMarketValueTickArray" ffi_output_variant = "MarketValueTicks" ffi_from_vec_array = "TdxMarketValueTickArray" @@ -415,8 +399,6 @@ columns = [ collection = "GreeksAllTicks" direct = "GreeksAllTick" parser = "parse_greeks_all_ticks" -go_struct = "GreeksAllTick" -go_converter = "convertGreeksAllTicks" ffi_array = "TdxGreeksAllTickArray" ffi_output_variant = "GreeksAllTicks" ffi_from_vec_array = "TdxGreeksAllTickArray" @@ -465,8 +447,6 @@ columns = [ collection = "GreeksFirstOrderTicks" direct = "GreeksFirstOrderTick" parser = "parse_greeks_first_order_ticks" -go_struct = "GreeksFirstOrderTick" -go_converter = "convertGreeksFirstOrderTicks" ffi_array = "TdxGreeksFirstOrderTickArray" ffi_output_variant = "GreeksFirstOrderTicks" ffi_from_vec_array = "TdxGreeksFirstOrderTickArray" @@ -514,8 +494,6 @@ columns = [ collection = "GreeksSecondOrderTicks" direct = "GreeksSecondOrderTick" parser = "parse_greeks_second_order_ticks" -go_struct = "GreeksSecondOrderTick" -go_converter = "convertGreeksSecondOrderTicks" ffi_array = "TdxGreeksSecondOrderTickArray" ffi_output_variant = "GreeksSecondOrderTicks" ffi_from_vec_array = "TdxGreeksSecondOrderTickArray" @@ -563,8 +541,6 @@ columns = [ collection = "GreeksThirdOrderTicks" direct = "GreeksThirdOrderTick" parser = "parse_greeks_third_order_ticks" -go_struct = "GreeksThirdOrderTick" -go_converter = "convertGreeksThirdOrderTicks" ffi_array = "TdxGreeksThirdOrderTickArray" ffi_output_variant = "GreeksThirdOrderTicks" ffi_from_vec_array = "TdxGreeksThirdOrderTickArray" @@ -598,8 +574,6 @@ columns = [ collection = "IvTicks" direct = "IvTick" parser = "parse_iv_ticks" -go_struct = "IVTick" -go_converter = "convertIvTicks" ffi_array = "TdxIvTickArray" ffi_output_variant = "IvTicks" ffi_from_vec_array = "TdxIvTickArray" @@ -631,8 +605,6 @@ columns = [ collection = "PriceTicks" direct = "PriceTick" parser = "parse_price_ticks" -go_struct = "PriceTick" -go_converter = "convertPriceTicks" ffi_array = "TdxPriceTickArray" ffi_output_variant = "PriceTicks" ffi_from_vec_array = "TdxPriceTickArray" @@ -666,8 +638,6 @@ columns = [ collection = "CalendarDays" direct = "CalendarDay" parser = "parse_calendar_days_v3" -go_struct = "CalendarDay" -go_converter = "convertCalendarDays" ffi_array = "TdxCalendarDayArray" ffi_output_variant = "CalendarDays" ffi_from_vec_array = "TdxCalendarDayArray" @@ -699,8 +669,6 @@ columns = [ collection = "InterestRateTicks" direct = "InterestRateTick" parser = "parse_interest_rate_ticks" -go_struct = "InterestRateTick" -go_converter = "convertInterestRateTicks" ffi_array = "TdxInterestRateTickArray" ffi_output_variant = "InterestRateTicks" ffi_from_vec_array = "TdxInterestRateTickArray" @@ -732,8 +700,6 @@ columns = [ collection = "OptionContracts" direct = "OptionContract" parser = "parse_option_contracts_v3" -go_struct = "OptionContract" -go_converter = "convertOptionContracts" ffi_array = "TdxOptionContractArray" ffi_output_variant = "OptionContracts" ffi_from_vec_array = "TdxOptionContractArray" diff --git a/docs-site/docs/api-reference.md b/docs-site/docs/api-reference.md index e8e97619..5c8dbbba 100644 --- a/docs-site/docs/api-reference.md +++ b/docs-site/docs/api-reference.md @@ -1,11 +1,11 @@ --- title: API Reference -description: Complete API reference for the ThetaDataDx SDK covering all endpoints, types, and Greeks functions across Rust, Python, TypeScript/Node.js, Go, and C++. +description: Complete API reference for the ThetaDataDx SDK covering all endpoints, types, and Greeks functions across Rust, Python, TypeScript/Node.js, and C++. --- # API Reference -ThetaDataDx provides a unified client for accessing ThetaData market data across three public surfaces: historical request/response (MDDS over gRPC), real-time streaming (FPSS over TCP), and whole-universe daily blobs (FLATFILES over the legacy MDDS port). The SDK ships native bindings for Rust, Python, TypeScript/Node.js, Go, and C++, all backed by the same compiled Rust core. +ThetaDataDx provides a unified client for accessing ThetaData market data across three public surfaces: historical request/response (MDDS over gRPC), real-time streaming (FPSS over TCP), and whole-universe daily blobs (FLATFILES over the legacy MDDS port). The SDK ships native bindings for Rust, Python, TypeScript/Node.js, and C++, all backed by the same compiled Rust core. Complete typed historical surface + 4 streaming variants + the FLATFILES daily-blob surface (Rust today; cross-language bindings tracked under the FLATFILES roadmap section) + 23 Greeks functions + IV solver. @@ -29,16 +29,6 @@ import { ThetaDataDx } from 'thetadatadx'; const tdx = await ThetaDataDx.connectFromFile('creds.txt'); ``` -```go [Go] -creds, err := thetadatadx.CredentialsFromFile("creds.txt") -defer creds.Close() - -config := thetadatadx.ProductionConfig() -defer config.Close() - -client, err := thetadatadx.Connect(creds, config) -defer client.Close() -``` ```cpp [C++] auto creds = tdx::Credentials::from_file("creds.txt"); auto client = tdx::Client::connect(creds, tdx::Config::production()); @@ -63,9 +53,6 @@ symbols = tdx.stock_list_symbols() ```typescript [TypeScript] const symbols = tdx.stockListSymbols(); ``` -```go [Go] -symbols, err := client.StockListSymbols() -``` ```cpp [C++] auto symbols = client.stock_list_symbols(); ``` @@ -91,9 +78,6 @@ dates = tdx.stock_list_dates("TRADE", "AAPL") ```typescript [TypeScript] const dates = tdx.stockListDates('TRADE', 'AAPL'); ``` -```go [Go] -dates, err := client.StockListDates("TRADE", "AAPL") -``` ```cpp [C++] auto dates = client.stock_list_dates("TRADE", "AAPL"); ``` @@ -122,9 +106,6 @@ bars = tdx.stock_snapshot_ohlc(["AAPL", "MSFT"]) ```typescript [TypeScript] const bars = tdx.stockSnapshotOhlc(['AAPL', 'MSFT']); ``` -```go [Go] -bars, err := client.StockSnapshotOHLC([]string{"AAPL", "MSFT"}) -``` ```cpp [C++] auto bars = client.stock_snapshot_ohlc({"AAPL", "MSFT"}); ``` @@ -154,9 +135,6 @@ trades = tdx.stock_snapshot_trade(["AAPL"]) ```typescript [TypeScript] const trades = tdx.stockSnapshotTrade(['AAPL']); ``` -```go [Go] -trades, err := client.StockSnapshotTrade([]string{"AAPL"}) -``` ```cpp [C++] auto trades = client.stock_snapshot_trade({"AAPL"}); ``` @@ -186,9 +164,6 @@ quotes = tdx.stock_snapshot_quote(["AAPL"]) ```typescript [TypeScript] const quotes = tdx.stockSnapshotQuote(['AAPL']); ``` -```go [Go] -quotes, err := client.StockSnapshotQuote([]string{"AAPL"}) -``` ```cpp [C++] auto quotes = client.stock_snapshot_quote({"AAPL"}); ``` @@ -218,9 +193,6 @@ mv = tdx.stock_snapshot_market_value(["AAPL"]) ```typescript [TypeScript] const mv = tdx.stockSnapshotMarketValue(['AAPL']); ``` -```go [Go] -mv, err := client.StockSnapshotMarketValue([]string{"AAPL"}) -``` ```cpp [C++] auto mv = client.stock_snapshot_market_value({"AAPL"}); ``` @@ -250,9 +222,6 @@ eod = tdx.stock_history_eod("AAPL", "20240101", "20240301") ```typescript [TypeScript] const eod = tdx.stockHistoryEOD('AAPL', '20240101', '20240301'); ``` -```go [Go] -eod, err := client.StockHistoryEOD("AAPL", "20240101", "20240301") -``` ```cpp [C++] auto eod = client.stock_history_eod("AAPL", "20240101", "20240301"); ``` @@ -282,9 +251,6 @@ bars = tdx.stock_history_ohlc("AAPL", "20240315", "60000") ```typescript [TypeScript] const bars = tdx.stockHistoryOHLC('AAPL', '20240315', '60000'); ``` -```go [Go] -bars, err := client.StockHistoryOHLC("AAPL", "20240315", "60000") -``` ```cpp [C++] auto bars = client.stock_history_ohlc("AAPL", "20240315", "60000"); ``` @@ -317,9 +283,6 @@ bars = tdx.stock_history_ohlc_range("AAPL", "20240101", "20240301", "60000") ```typescript [TypeScript] const bars = tdx.stockHistoryOHLCRange('AAPL', '20240101', '20240301', '60000'); ``` -```go [Go] -bars, err := client.StockHistoryOHLCRange("AAPL", "20240101", "20240301", "60000") -``` ```cpp [C++] auto bars = client.stock_history_ohlc_range("AAPL", "20240101", "20240301", "60000"); ``` @@ -350,9 +313,6 @@ trades = tdx.stock_history_trade("AAPL", "20240315") ```typescript [TypeScript] const trades = tdx.stockHistoryTrade('AAPL', '20240315'); ``` -```go [Go] -trades, err := client.StockHistoryTrade("AAPL", "20240315") -``` ```cpp [C++] auto trades = client.stock_history_trade("AAPL", "20240315"); ``` @@ -386,9 +346,6 @@ quotes = tdx.stock_history_quote("AAPL", "20240315", "60000") ```typescript [TypeScript] const quotes = tdx.stockHistoryQuote('AAPL', '20240315', '60000'); ``` -```go [Go] -quotes, err := client.StockHistoryQuote("AAPL", "20240315", "60000") -``` ```cpp [C++] auto quotes = client.stock_history_quote("AAPL", "20240315", "60000"); ``` @@ -423,9 +380,6 @@ tq = tdx.stock_history_trade_quote("AAPL", "20240315") ```typescript [TypeScript] const tq = tdx.stockHistoryTradeQuote('AAPL', '20240315'); ``` -```go [Go] -tq, err := client.StockHistoryTradeQuote("AAPL", "20240315") -``` ```cpp [C++] auto tq = client.stock_history_trade_quote("AAPL", "20240315"); ``` @@ -460,9 +414,6 @@ trades = tdx.stock_at_time_trade("AAPL", "20240101", "20240301", "09:30:00.000") ```typescript [TypeScript] const trades = tdx.stockAtTimeTrade('AAPL', '20240101', '20240301', '09:30:00.000'); ``` -```go [Go] -trades, err := client.StockAtTimeTrade("AAPL", "20240101", "20240301", "09:30:00.000") -``` ```cpp [C++] auto trades = client.stock_at_time_trade("AAPL", "20240101", "20240301", "09:30:00.000"); ``` @@ -494,9 +445,6 @@ quotes = tdx.stock_at_time_quote("AAPL", "20240101", "20240301", "09:30:00.000") ```typescript [TypeScript] const quotes = tdx.stockAtTimeQuote('AAPL', '20240101', '20240301', '09:30:00.000'); ``` -```go [Go] -quotes, err := client.StockAtTimeQuote("AAPL", "20240101", "20240301", "09:30:00.000") -``` ```cpp [C++] auto quotes = client.stock_at_time_quote("AAPL", "20240101", "20240301", "09:30:00.000"); ``` @@ -537,9 +485,6 @@ symbols = tdx.option_list_symbols() ```typescript [TypeScript] const symbols = tdx.optionListSymbols(); ``` -```go [Go] -symbols, err := client.OptionListSymbols() -``` ```cpp [C++] auto symbols = client.option_list_symbols(); ``` @@ -565,9 +510,6 @@ dates = tdx.option_list_dates("TRADE", "SPY", "20241220", "500", "C") ```typescript [TypeScript] const dates = tdx.optionListDates('TRADE', 'SPY', '20241220', '500', 'C'); ``` -```go [Go] -dates, err := client.OptionListDates("TRADE", "SPY", "20241220", "500", "C") -``` ```cpp [C++] auto dates = client.option_list_dates("TRADE", "SPY", "20241220", "500", "C"); ``` @@ -599,9 +541,6 @@ exps = tdx.option_list_expirations("SPY") ```typescript [TypeScript] const exps = tdx.optionListExpirations('SPY'); ``` -```go [Go] -exps, err := client.OptionListExpirations("SPY") -``` ```cpp [C++] auto exps = client.option_list_expirations("SPY"); ``` @@ -629,9 +568,6 @@ strikes = tdx.option_list_strikes("SPY", "20241220") ```typescript [TypeScript] const strikes = tdx.optionListStrikes('SPY', '20241220'); ``` -```go [Go] -strikes, err := client.OptionListStrikes("SPY", "20241220") -``` ```cpp [C++] auto strikes = client.option_list_strikes("SPY", "20241220"); ``` @@ -660,9 +596,6 @@ contracts = tdx.option_list_contracts("TRADE", "SPY", "20240315") ```typescript [TypeScript] const contracts = tdx.optionListContracts('TRADE', 'SPY', '20240315'); ``` -```go [Go] -contracts, err := client.OptionListContracts("TRADE", "SPY", "20240315") -``` ```cpp [C++] auto contracts = client.option_list_contracts("TRADE", "SPY", "20240315"); ``` @@ -693,9 +626,6 @@ bars = tdx.option_snapshot_ohlc("SPY", "20241220", "500", "C") ```typescript [TypeScript] const bars = tdx.optionSnapshotOhlc('SPY', '20241220', '500', 'C'); ``` -```go [Go] -bars, err := client.OptionSnapshotOHLC("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto bars = client.option_snapshot_ohlc("SPY", "20241220", "500", "C"); ``` @@ -729,9 +659,6 @@ trades = tdx.option_snapshot_trade("SPY", "20241220", "500", "C") ```typescript [TypeScript] const trades = tdx.optionSnapshotTrade('SPY', '20241220', '500', 'C'); ``` -```go [Go] -trades, err := client.OptionSnapshotTrade("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto trades = client.option_snapshot_trade("SPY", "20241220", "500", "C"); ``` @@ -764,9 +691,6 @@ quotes = tdx.option_snapshot_quote("SPY", "20241220", "500", "C") ```typescript [TypeScript] const quotes = tdx.optionSnapshotQuote('SPY', '20241220', '500', 'C'); ``` -```go [Go] -quotes, err := client.OptionSnapshotQuote("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto quotes = client.option_snapshot_quote("SPY", "20241220", "500", "C"); ``` @@ -800,9 +724,6 @@ oi = tdx.option_snapshot_open_interest("SPY", "20241220", "500", "C") ```typescript [TypeScript] const oi = tdx.optionSnapshotOpenInterest('SPY', '20241220', '500', 'C'); ``` -```go [Go] -oi, err := client.OptionSnapshotOpenInterest("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto oi = client.option_snapshot_open_interest("SPY", "20241220", "500", "C"); ``` @@ -836,9 +757,6 @@ mv = tdx.option_snapshot_market_value("SPY", "20241220", "500", "C") ```typescript [TypeScript] const mv = tdx.optionSnapshotMarketValue('SPY', '20241220', '500', 'C'); ``` -```go [Go] -mv, err := client.OptionSnapshotMarketValue("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto mv = client.option_snapshot_market_value("SPY", "20241220", "500", "C"); ``` @@ -872,9 +790,6 @@ iv = tdx.option_snapshot_greeks_implied_volatility("SPY", "20241220", "500", "C" ```typescript [TypeScript] const iv = tdx.optionSnapshotGreeksImpliedVolatility('SPY', '20241220', '500', 'C'); ``` -```go [Go] -iv, err := client.OptionSnapshotGreeksIV("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto iv = client.option_snapshot_greeks_implied_volatility("SPY", "20241220", "500", "C"); ``` @@ -916,9 +831,6 @@ greeks = tdx.option_snapshot_greeks_all("SPY", "20241220", "500", "C") ```typescript [TypeScript] const greeks = tdx.optionSnapshotGreeksAll('SPY', '20241220', '500', 'C'); ``` -```go [Go] -greeks, err := client.OptionSnapshotGreeksAll("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto greeks = client.option_snapshot_greeks_all("SPY", "20241220", "500", "C"); ``` @@ -960,9 +872,6 @@ g = tdx.option_snapshot_greeks_first_order("SPY", "20241220", "500", "C") ```typescript [TypeScript] const g = tdx.optionSnapshotGreeksFirstOrder('SPY', '20241220', '500', 'C'); ``` -```go [Go] -g, err := client.OptionSnapshotGreeksFirstOrder("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto g = client.option_snapshot_greeks_first_order("SPY", "20241220", "500", "C"); ``` @@ -990,9 +899,6 @@ g = tdx.option_snapshot_greeks_second_order("SPY", "20241220", "500", "C") ```typescript [TypeScript] const g = tdx.optionSnapshotGreeksSecondOrder('SPY', '20241220', '500', 'C'); ``` -```go [Go] -g, err := client.OptionSnapshotGreeksSecondOrder("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto g = client.option_snapshot_greeks_second_order("SPY", "20241220", "500", "C"); ``` @@ -1020,9 +926,6 @@ g = tdx.option_snapshot_greeks_third_order("SPY", "20241220", "500", "C") ```typescript [TypeScript] const g = tdx.optionSnapshotGreeksThirdOrder('SPY', '20241220', '500', 'C'); ``` -```go [Go] -g, err := client.OptionSnapshotGreeksThirdOrder("SPY", "20241220", "500", "C") -``` ```cpp [C++] auto g = client.option_snapshot_greeks_third_order("SPY", "20241220", "500", "C"); ``` @@ -1052,9 +955,6 @@ eod = tdx.option_history_eod("SPY", "20241220", "500", "C", "20240101", "2024030 ```typescript [TypeScript] const eod = tdx.optionHistoryEOD('SPY', '20241220', '500', 'C', '20240101', '20240301'); ``` -```go [Go] -eod, err := client.OptionHistoryEOD("SPY", "20241220", "500", "C", "20240101", "20240301") -``` ```cpp [C++] auto eod = client.option_history_eod("SPY", "20241220", "500", "C", "20240101", "20240301"); ``` @@ -1091,9 +991,6 @@ bars = tdx.option_history_ohlc("SPY", "20241220", "500", "C", "20240315", "60000 ```typescript [TypeScript] const bars = tdx.optionHistoryOHLC('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -bars, err := client.OptionHistoryOHLC("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto bars = client.option_history_ohlc("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1129,9 +1026,6 @@ trades = tdx.option_history_trade("SPY", "20241220", "500", "C", "20240315") ```typescript [TypeScript] const trades = tdx.optionHistoryTrade('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -trades, err := client.OptionHistoryTrade("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto trades = client.option_history_trade("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1171,9 +1065,6 @@ quotes = tdx.option_history_quote("SPY", "20241220", "500", "C", "20240315", "60 ```typescript [TypeScript] const quotes = tdx.optionHistoryQuote('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -quotes, err := client.OptionHistoryQuote("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto quotes = client.option_history_quote("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1210,9 +1101,6 @@ tq = tdx.option_history_trade_quote("SPY", "20241220", "500", "C", "20240315") ```typescript [TypeScript] const tq = tdx.optionHistoryTradeQuote('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -tq, err := client.OptionHistoryTradeQuote("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto tq = client.option_history_trade_quote("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1251,9 +1139,6 @@ oi = tdx.option_history_open_interest("SPY", "20241220", "500", "C", "20240315") ```typescript [TypeScript] const oi = tdx.optionHistoryOpenInterest('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -oi, err := client.OptionHistoryOpenInterest("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto oi = client.option_history_open_interest("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1289,9 +1174,6 @@ g = tdx.option_history_greeks_eod("SPY", "20241220", "500", "C", "20240101", "20 ```typescript [TypeScript] const g = tdx.optionHistoryGreeksEod('SPY', '20241220', '500', 'C', '20240101', '20240301'); ``` -```go [Go] -g, err := client.OptionHistoryGreeksEOD("SPY", "20241220", "500", "C", "20240101", "20240301") -``` ```cpp [C++] auto g = client.option_history_greeks_eod("SPY", "20241220", "500", "C", "20240101", "20240301"); ``` @@ -1335,9 +1217,6 @@ g = tdx.option_history_greeks_all("SPY", "20241220", "500", "C", "20240315", "60 ```typescript [TypeScript] const g = tdx.optionHistoryGreeksAll('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -g, err := client.OptionHistoryGreeksAll("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto g = client.option_history_greeks_all("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1377,9 +1256,6 @@ g = tdx.option_history_trade_greeks_all("SPY", "20241220", "500", "C", "20240315 ```typescript [TypeScript] const g = tdx.optionHistoryTradeGreeksAll('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -g, err := client.OptionHistoryTradeGreeksAll("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto g = client.option_history_trade_greeks_all("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1423,9 +1299,6 @@ g = tdx.option_history_greeks_first_order("SPY", "20241220", "500", "C", "202403 ```typescript [TypeScript] const g = tdx.optionHistoryGreeksFirstOrder('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -g, err := client.OptionHistoryGreeksFirstOrder("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto g = client.option_history_greeks_first_order("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1455,9 +1328,6 @@ g = tdx.option_history_trade_greeks_first_order("SPY", "20241220", "500", "C", " ```typescript [TypeScript] const g = tdx.optionHistoryTradeGreeksFirstOrder('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -g, err := client.OptionHistoryTradeGreeksFirstOrder("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto g = client.option_history_trade_greeks_first_order("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1487,9 +1357,6 @@ g = tdx.option_history_greeks_second_order("SPY", "20241220", "500", "C", "20240 ```typescript [TypeScript] const g = tdx.optionHistoryGreeksSecondOrder('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -g, err := client.OptionHistoryGreeksSecondOrder("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto g = client.option_history_greeks_second_order("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1519,9 +1386,6 @@ g = tdx.option_history_trade_greeks_second_order("SPY", "20241220", "500", "C", ```typescript [TypeScript] const g = tdx.optionHistoryTradeGreeksSecondOrder('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -g, err := client.OptionHistoryTradeGreeksSecondOrder("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto g = client.option_history_trade_greeks_second_order("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1551,9 +1415,6 @@ g = tdx.option_history_greeks_third_order("SPY", "20241220", "500", "C", "202403 ```typescript [TypeScript] const g = tdx.optionHistoryGreeksThirdOrder('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -g, err := client.OptionHistoryGreeksThirdOrder("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto g = client.option_history_greeks_third_order("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1583,9 +1444,6 @@ g = tdx.option_history_trade_greeks_third_order("SPY", "20241220", "500", "C", " ```typescript [TypeScript] const g = tdx.optionHistoryTradeGreeksThirdOrder('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -g, err := client.OptionHistoryTradeGreeksThirdOrder("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto g = client.option_history_trade_greeks_third_order("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1615,9 +1473,6 @@ iv = tdx.option_history_greeks_implied_volatility("SPY", "20241220", "500", "C", ```typescript [TypeScript] const iv = tdx.optionHistoryGreeksImpliedVolatility('SPY', '20241220', '500', 'C', '20240315', '60000'); ``` -```go [Go] -iv, err := client.OptionHistoryGreeksImpliedVolatility("SPY", "20241220", "500", "C", "20240315", "60000") -``` ```cpp [C++] auto iv = client.option_history_greeks_implied_volatility("SPY", "20241220", "500", "C", "20240315", "60000"); ``` @@ -1647,9 +1502,6 @@ iv = tdx.option_history_trade_greeks_implied_volatility("SPY", "20241220", "500" ```typescript [TypeScript] const iv = tdx.optionHistoryTradeGreeksImpliedVolatility('SPY', '20241220', '500', 'C', '20240315'); ``` -```go [Go] -iv, err := client.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20241220", "500", "C", "20240315") -``` ```cpp [C++] auto iv = client.option_history_trade_greeks_implied_volatility("SPY", "20241220", "500", "C", "20240315"); ``` @@ -1679,9 +1531,6 @@ trades = tdx.option_at_time_trade("SPY", "20241220", "500", "C", "20240101", "20 ```typescript [TypeScript] const trades = tdx.optionAtTimeTrade('SPY', '20241220', '500', 'C', '20240101', '20240301', '09:30:00.000'); ``` -```go [Go] -trades, err := client.OptionAtTimeTrade("SPY", "20241220", "500", "C", "20240101", "20240301", "09:30:00.000") -``` ```cpp [C++] auto trades = client.option_at_time_trade("SPY", "20241220", "500", "C", "20240101", "20240301", "09:30:00.000"); ``` @@ -1719,9 +1568,6 @@ quotes = tdx.option_at_time_quote("SPY", "20241220", "500", "C", "20240101", "20 ```typescript [TypeScript] const quotes = tdx.optionAtTimeQuote('SPY', '20241220', '500', 'C', '20240101', '20240301', '09:30:00.000'); ``` -```go [Go] -quotes, err := client.OptionAtTimeQuote("SPY", "20241220", "500", "C", "20240101", "20240301", "09:30:00.000") -``` ```cpp [C++] auto quotes = client.option_at_time_quote("SPY", "20241220", "500", "C", "20240101", "20240301", "09:30:00.000"); ``` @@ -1759,9 +1605,6 @@ symbols = tdx.index_list_symbols() ```typescript [TypeScript] const symbols = tdx.indexListSymbols(); ``` -```go [Go] -symbols, err := client.IndexListSymbols() -``` ```cpp [C++] auto symbols = client.index_list_symbols(); ``` @@ -1787,9 +1630,6 @@ dates = tdx.index_list_dates("SPX") ```typescript [TypeScript] const dates = tdx.indexListDates('SPX'); ``` -```go [Go] -dates, err := client.IndexListDates("SPX") -``` ```cpp [C++] auto dates = client.index_list_dates("SPX"); ``` @@ -1817,9 +1657,6 @@ bars = tdx.index_snapshot_ohlc(["SPX", "VIX"]) ```typescript [TypeScript] const bars = tdx.indexSnapshotOhlc(['SPX', 'VIX']); ``` -```go [Go] -bars, err := client.IndexSnapshotOHLC([]string{"SPX", "VIX"}) -``` ```cpp [C++] auto bars = client.index_snapshot_ohlc({"SPX", "VIX"}); ``` @@ -1848,9 +1685,6 @@ prices = tdx.index_snapshot_price(["SPX"]) ```typescript [TypeScript] const prices = tdx.indexSnapshotPrice(['SPX']); ``` -```go [Go] -prices, err := client.IndexSnapshotPrice([]string{"SPX"}) -``` ```cpp [C++] auto prices = client.index_snapshot_price({"SPX"}); ``` @@ -1879,9 +1713,6 @@ mv = tdx.index_snapshot_market_value(["SPX"]) ```typescript [TypeScript] const mv = tdx.indexSnapshotMarketValue(['SPX']); ``` -```go [Go] -mv, err := client.IndexSnapshotMarketValue([]string{"SPX"}) -``` ```cpp [C++] auto mv = client.index_snapshot_market_value({"SPX"}); ``` @@ -1910,9 +1741,6 @@ eod = tdx.index_history_eod("SPX", "20240101", "20240301") ```typescript [TypeScript] const eod = tdx.indexHistoryEOD('SPX', '20240101', '20240301'); ``` -```go [Go] -eod, err := client.IndexHistoryEOD("SPX", "20240101", "20240301") -``` ```cpp [C++] auto eod = client.index_history_eod("SPX", "20240101", "20240301"); ``` @@ -1942,9 +1770,6 @@ bars = tdx.index_history_ohlc("SPX", "20240101", "20240301", "60000") ```typescript [TypeScript] const bars = tdx.indexHistoryOHLC('SPX', '20240101', '20240301', '60000'); ``` -```go [Go] -bars, err := client.IndexHistoryOHLC("SPX", "20240101", "20240301", "60000") -``` ```cpp [C++] auto bars = client.index_history_ohlc("SPX", "20240101", "20240301", "60000"); ``` @@ -1977,9 +1802,6 @@ prices = tdx.index_history_price("SPX", "20240315", "60000") ```typescript [TypeScript] const prices = tdx.indexHistoryPrice('SPX', '20240315', '60000'); ``` -```go [Go] -prices, err := client.IndexHistoryPrice("SPX", "20240315", "60000") -``` ```cpp [C++] auto prices = client.index_history_price("SPX", "20240315", "60000"); ``` @@ -2011,9 +1833,6 @@ prices = tdx.index_at_time_price("SPX", "20240101", "20240301", "09:30:00.000") ```typescript [TypeScript] const prices = tdx.indexAtTimePrice('SPX', '20240101', '20240301', '09:30:00.000'); ``` -```go [Go] -prices, err := client.IndexAtTimePrice("SPX", "20240101", "20240301", "09:30:00.000") -``` ```cpp [C++] auto prices = client.index_at_time_price("SPX", "20240101", "20240301", "09:30:00.000"); ``` @@ -2046,9 +1865,6 @@ info = tdx.calendar_open_today() ```typescript [TypeScript] const info = tdx.calendarOpenToday(); ``` -```go [Go] -info, err := client.CalendarOpenToday() -``` ```cpp [C++] auto info = client.calendar_open_today(); ``` @@ -2074,9 +1890,6 @@ info = tdx.calendar_on_date("20240315") ```typescript [TypeScript] const info = tdx.calendarOnDate('20240315'); ``` -```go [Go] -info, err := client.CalendarOnDate("20240315") -``` ```cpp [C++] auto info = client.calendar_on_date("20240315"); ``` @@ -2104,9 +1917,6 @@ cal = tdx.calendar_year("2024") ```typescript [TypeScript] const cal = tdx.calendarYear('2024'); ``` -```go [Go] -cal, err := client.CalendarYear("2024") -``` ```cpp [C++] auto cal = client.calendar_year("2024"); ``` @@ -2136,9 +1946,6 @@ rates = tdx.interest_rate_history_eod("SOFR", "20240101", "20240301") ```typescript [TypeScript] const rates = tdx.interestRateHistoryEOD('SOFR', '20240101', '20240301'); ``` -```go [Go] -rates, err := client.InterestRateHistoryEOD("SOFR", "20240101", "20240301") -``` ```cpp [C++] auto rates = client.interest_rate_history_eod("SOFR", "20240101", "20240301"); ``` @@ -2186,10 +1993,6 @@ print(f"IV: {g['iv']:.4f}, Delta: {g['delta']:.4f}") ```typescript [TypeScript] g = all_greeks(450.0, 455.0, 0.05, 0.015, 30.0 / 365.0, 8.50, 'C') ``` -```go [Go] -g, err := thetadatadx.AllGreeks(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, "C") -fmt.Printf("IV: %.4f, Delta: %.4f\n", g.IV, g.Delta) -``` ```cpp [C++] auto g = tdx::all_greeks(450.0, 455.0, 0.05, 0.015, 30.0 / 365.0, 8.50, "C"); std::cout << "IV: " << g.iv << ", Delta: " << g.delta << std::endl; @@ -2228,9 +2031,6 @@ iv, err = implied_volatility(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, "C") ```typescript [TypeScript] iv, err = implied_volatility(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, 'C') ``` -```go [Go] -iv, ivErr, err := thetadatadx.ImpliedVolatility(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, "C") -``` ```cpp [C++] auto [iv, err] = tdx::implied_volatility(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, "C"); ``` @@ -2308,7 +2108,7 @@ All individual functions share these parameters. Not all functions take `is_call ## Streaming (FPSS) -Real-time market data streaming via FPSS (Fast Protocol Streaming Service) over TLS/TCP. The streaming connection is established lazily and managed through the main client in Rust and Python; Go and C++ use a dedicated `FpssClient`. +Real-time market data streaming via FPSS (Fast Protocol Streaming Service) over TLS/TCP. The streaming connection is established lazily and managed through the main client in Rust and Python; C++ uses a dedicated `FpssClient`. ### Starting the Stream @@ -2328,10 +2128,6 @@ tdx.start_streaming() ```typescript [TypeScript] tdx.startStreaming(); ``` -```go [Go] -fpss := thetadatadx.NewFpssClient(creds, config) -defer fpss.Close() -``` ```cpp [C++] tdx::FpssClient fpss(creds, tdx::Config::production()); ``` @@ -2354,12 +2150,6 @@ tdx.subscribe_trades("AAPL") tdx.subscribeQuotes('AAPL'); tdx.subscribeTrades('AAPL'); ``` -```go [Go] -reqID, err := fpss.SubscribeQuotes("AAPL") -reqID, err := fpss.SubscribeTrades("AAPL") -reqID, err := fpss.SubscribeOpenInterest("AAPL") -reqID, err := fpss.SubscribeFullTrades("STOCK") -``` ```cpp [C++] int req_id = fpss.subscribe_quotes("AAPL"); int req_id = fpss.subscribe_trades("AAPL"); @@ -2394,11 +2184,6 @@ tdx.unsubscribe_open_interest(&Contract::stock("AAPL"))?; ```typescript [TypeScript] // Not exposed in TypeScript - use tdx.stopStreaming() ``` -```go [Go] -fpss.UnsubscribeQuotes("AAPL") -fpss.UnsubscribeTrades("AAPL") -fpss.UnsubscribeOpenInterest("AAPL") -``` ```cpp [C++] fpss.unsubscribe_quotes("AAPL"); fpss.unsubscribe_trades("AAPL"); @@ -2428,9 +2213,6 @@ if (event) { console.log(event); } ``` -```go [Go] -event, err := fpss.NextEvent(5000) // returns *FpssEvent or nil -``` ```cpp [C++] FpssEventPtr event = fpss.next_event(5000); // nullptr on timeout ``` @@ -2448,9 +2230,6 @@ tdx.stop_streaming() ```typescript [TypeScript] tdx.stopStreaming(); ``` -```go [Go] -fpss.Shutdown() -``` ```cpp [C++] fpss.shutdown(); ``` @@ -2461,7 +2240,7 @@ fpss.shutdown(); | Method | Returns | SDK availability | Description | |--------|---------|------------------|-------------| | `is_streaming` | bool | Rust/Python only | Check if the unified streaming connection is live | -| `contract_map` | `HashMap` (Rust), `dict[int, Contract]` (Python), `map[int32]string` (Go), `map` (C++) | All SDKs | Get full contract ID mapping | +| `contract_map` | `HashMap` (Rust), `dict[int, Contract]` (Python), `map` (C++) | All SDKs | Get full contract ID mapping | | `contract_lookup` | string/optional | All SDKs (FFI-based, returns NULL/"" for not-found) | Look up a single contract by server-assigned ID | | `active_subscriptions` | list/typed structs | All SDKs | Get list of active subscriptions | | `subscribe_option_*` / `unsubscribe_option_*` | int | All SDKs | Option-level subscribe/unsubscribe by `(symbol, expiration, strike, right)` | @@ -2547,14 +2326,13 @@ tdx.option_history_quote_stream("SPY", "20241220", "500", "C", "20240315", "0") 10 tick types carry contract identification fields populated by the server on wildcard queries (pass `0` for expiration/strike). On single-contract queries these fields are `0`/empty. -| Field | Type (Rust/FFI) | Type (Go) | Description | -|-------|-----------------|-----------|-------------| -| `expiration` | i32 | int32 | Contract expiration (YYYYMMDD). 0 if absent. | -| `strike` | f64 | float64 | Strike price (decoded to f64). | -| `right` | i32 | string | Contract right. Rust/FFI: 67=Call, 80=Put. Go: `"C"`, `"P"`, `""`. | +| Field | Type (Rust/FFI) | Description | +|-------|-----------------|-------------| +| `expiration` | i32 | Contract expiration (YYYYMMDD). 0 if absent. | +| `strike` | f64 | Strike price (decoded to f64). | +| `right` | i32 | Contract right. 67=Call (`'C'`), 80=Put (`'P'`). | Helper methods (all 10 types): `is_call()`, `is_put()`, `has_contract_id()`. -Go helper: `RightStr(code int32) string` converts raw right codes to `"C"`/`"P"`/`""`. Types with contract ID: TradeTick, QuoteTick, OhlcTick, EodTick, OpenInterestTick, TradeQuoteTick, MarketValueTick, GreeksTick, IvTick. @@ -2578,7 +2356,7 @@ A single trade execution. | `date` | i32 | Date as YYYYMMDD integer | | `expiration` | i32 | Contract expiration (wildcard queries) | | `strike` | f64 | Contract strike (wildcard queries) | -| `right` | i32 (Rust/FFI), string (Go) | Contract right. Rust: C=67/P=80. Go: `"C"`/`"P"`. | +| `right` | i32 | Contract right. C=67/P=80. | Helper methods: `is_cancelled()`, `trade_condition_no_last()`, `price_condition_set_last()`, `regular_trading_hours()`, `is_seller()`, `is_incremental_volume()`, `is_call()`, `is_put()`, `has_contract_id()` @@ -2595,7 +2373,7 @@ An NBBO quote. | `bid_condition` / `ask_condition` | i32 | Condition codes | | `midpoint` | f64 | Pre-computed `(bid + ask) / 2.0` | | `date` | i32 | Date as YYYYMMDD integer | -| `expiration` / `strike` / `right` | i32/f64/i32 (Go: `right` is string) | Contract ID (wildcard queries) | +| `expiration` / `strike` / `right` | i32/f64/i32 | Contract ID (wildcard queries) | Helper methods: `is_call()`, `is_put()`, `has_contract_id()`, plus contract ID helpers @@ -2610,7 +2388,7 @@ An aggregated OHLC bar. | `volume` | i64 | Total volume in bar | | `count` | i64 | Number of trades in bar | | `date` | i32 | Date as YYYYMMDD integer | -| `expiration` / `strike` / `right` | i32/f64/i32 (Go: `right` is string) | Contract ID (wildcard queries) | +| `expiration` / `strike` / `right` | i32/f64/i32 | Contract ID (wildcard queries) | Helper methods: `is_call()`, `is_put()`, `has_contract_id()`, plus contract ID helpers @@ -2629,7 +2407,7 @@ Full end-of-day snapshot with OHLC + closing quote data. | `bid` / `ask` | f64 | Closing bid/ask (decoded) | | `bid_condition` / `ask_condition` | i32 | Closing quote conditions | | `date` | i32 | Date as YYYYMMDD | -| `expiration` / `strike` / `right` | i32/f64/i32 (Go: `right` is string) | Contract ID (wildcard queries) | +| `expiration` / `strike` / `right` | i32/f64/i32 | Contract ID (wildcard queries) | Helper methods: `is_call()`, `is_put()`, `has_contract_id()`, plus contract ID helpers @@ -2646,7 +2424,7 @@ Helper methods: `is_call()`, `is_put()`, `has_contract_id()`, plus contract ID h | `ms_of_day` | i32 | Milliseconds since midnight ET | | `open_interest` | i32 | Open interest count | | `date` | i32 | Date as YYYYMMDD | -| `expiration` / `strike` / `right` | i32/f64/i32 (Go: `right` is string) | Contract ID (wildcard queries) | +| `expiration` / `strike` / `right` | i32/f64/i32 | Contract ID (wildcard queries) | ### GreeksResult @@ -2697,8 +2475,6 @@ Option right: `Call`, `Put` - `from_char('C')` / `from_char('P')` - parse from character - `as_char()` - convert to `'C'` or `'P'` -**Go SDK:** The `Right` field on all public tick structs is a `string` (`"C"`, `"P"`, or `""`) instead of `i32`. Use `RightStr(code int32)` for manual conversion. - ### StreamResponseType Subscription response codes returned in `ReqResponse` control events. @@ -2744,10 +2520,6 @@ tdx.subscribe_quotes("AAPL") # Passed as string symbol to subscribe methods tdx.subscribeQuotes('AAPL'); ``` -```go [Go] -// Passed as string symbol to subscribe methods -fpss.SubscribeQuotes("AAPL") -``` ```cpp [C++] // Passed as string symbol to subscribe methods fpss.subscribe_quotes("AAPL"); @@ -2778,10 +2550,6 @@ Credentials("user@example.com", "password") Credentials.from_file('creds.txt') Credentials('user@example.com', 'password') ``` -```go [Go] -creds, err := thetadatadx.CredentialsFromFile("creds.txt") -creds := thetadatadx.NewCredentials("email@example.com", "password") -``` ```cpp [C++] auto creds = tdx::Credentials::from_file("creds.txt"); auto creds = tdx::Credentials::from_email("email@example.com", "password"); @@ -2814,9 +2582,6 @@ pub enum Error { ```typescript [TypeScript] # All errors raise RuntimeError with descriptive message ``` -```go [Go] -// All methods return (result, error) -``` ```cpp [C++] // All methods throw std::runtime_error on failure ``` diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index 7102e415..a90ddeb9 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.29] - 2026-05-06 + +### Removed + +- **Go SDK.** The cgo bridge between Rust's C ABI and Go's runtime + carries per-call overhead that masks the upstream throughput this + SDK is engineered for. Users who need Go bindings can build their + own cgo wrapper against the unchanged C ABI in `crates/ffi/` — + header at `sdks/cpp/include/thetadx.h`, all FFI types and free fns + exported as `tdx_*` symbols. + + Closes #481. + ## [8.0.28] - 2026-05-06 ### Breaking diff --git a/docs-site/docs/getting-started/first-query.md b/docs-site/docs/getting-started/first-query.md index 933d06d1..a39aab28 100644 --- a/docs-site/docs/getting-started/first-query.md +++ b/docs-site/docs/getting-started/first-query.md @@ -1,6 +1,6 @@ --- title: First Query -description: Run your first ThetaDataDx historical call in Rust, Python, TypeScript, Go, or C++. +description: Run your first ThetaDataDx historical call in Rust, Python, TypeScript, or C++. --- # First Query @@ -47,36 +47,6 @@ for (const tick of eod) { console.log(`${tick.date}: O=${tick.open} H=${tick.high} L=${tick.low} C=${tick.close} V=${tick.volume}`); } ``` -```go [Go] -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, err := thetadatadx.CredentialsFromFile("creds.txt") - if err != nil { log.Fatal(err) } - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - client, err := thetadatadx.Connect(creds, config) - if err != nil { log.Fatal(err) } - defer client.Close() - - eod, err := client.StockHistoryEOD("AAPL", "20240101", "20240301") - if err != nil { log.Fatal(err) } - for _, tick := range eod { - fmt.Printf("%d: O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", - tick.Date, tick.Open, tick.High, tick.Low, tick.Close, tick.Volume) - } -} -``` ```cpp [C++] #include "thetadx.hpp" #include @@ -108,14 +78,14 @@ int main() { ] ``` -Every historical endpoint returns a typed tick list with the same JSON-like shape across all five SDKs. Numeric fields are decoded to `f64` / `double` at parse time — no `Price{value, type}` objects to unpack. +Every historical endpoint returns a typed tick list with the same JSON-like shape across all four SDKs. Numeric fields are decoded to `f64` / `double` at parse time — no `Price{value, type}` objects to unpack. ## What runs under the hood 1. `connect()` loads credentials and authenticates against ThetaData's Nexus endpoint, retrieving a session UUID. 2. The session UUID is attached to an HTTP/2 (`tonic` / `gRPC`) channel to the MDDS datacenter. 3. `stock_history_eod(...)` streams a compressed protobuf `DataTable` response. -4. A Rust decoder turns the `DataTable` into a `Vec` (~86k rows/sec per core on wide-schema data). The Python / TypeScript / Go / C++ bindings expose this slice as the language's native collection. +4. A Rust decoder turns the `DataTable` into a `Vec` (~86k rows/sec per core on wide-schema data). The Python / TypeScript / C++ bindings expose this slice as the language's native collection. ## Next diff --git a/docs-site/docs/getting-started/index.md b/docs-site/docs/getting-started/index.md index 6f99971c..6fa29019 100644 --- a/docs-site/docs/getting-started/index.md +++ b/docs-site/docs/getting-started/index.md @@ -1,15 +1,15 @@ --- title: Getting Started -description: Multi-language ThetaData SDK in Rust, Python, TypeScript, Go, and C++. Install, authenticate, run a first query, stream, and compute Greeks locally. +description: Multi-language ThetaData SDK in Rust, Python, TypeScript, and C++. Install, authenticate, run a first query, stream, and compute Greeks locally. --- # Getting Started -ThetaDataDx is a Rust SDK for ThetaData's MDDS (historical, gRPC) and FPSS (real-time, TCP) servers. The data path — gRPC, protobuf parsing, zstd decompression, FIT decoding, Greeks math — runs inside the `thetadatadx` Rust crate and is exposed in five language surfaces: Rust, Python, TypeScript/Node.js, Go, and C++. +ThetaDataDx is a Rust SDK for ThetaData's MDDS (historical, gRPC) and FPSS (real-time, TCP) servers. The data path — gRPC, protobuf parsing, zstd decompression, FIT decoding, Greeks math — runs inside the `thetadatadx` Rust crate and is exposed in four language surfaces: Rust, Python, TypeScript/Node.js, and C++. Go consumers can build a thin cgo wrapper against the unchanged C ABI in [`ffi/`](https://github.com/userFRM/ThetaDataDx/tree/main/ffi). ## What's on this page -- [Quick Start](./quickstart) — install, authenticate, first historical call, first streaming call, tabbed across all five SDKs +- [Quick Start](./quickstart) — install, authenticate, first historical call, first streaming call, tabbed across all four SDKs - [Installation](./installation) — install for your language - [Authentication](./authentication) — credentials file, environment variables, token lifecycle - [First query](./first-query) — one historical call in every language @@ -23,14 +23,13 @@ ThetaDataDx is a Rust SDK for ThetaData's MDDS (historical, gRPC) and FPSS (real | Requirement | Details | |-------------|---------| | ThetaData account | Email and password from [thetadata.us](https://thetadata.us) | -| Rust toolchain | Required for Go and C++ SDKs (builds the FFI library); not required for Rust/Python/TypeScript on supported platforms | +| Rust toolchain | Required for the C++ SDK (builds the FFI library); not required for Rust/Python/TypeScript on supported platforms | | Python 3.9+ | For the Python SDK; pre-built `abi3` wheels provided | | Node.js 18+ | For the TypeScript/Node.js SDK | -| Go 1.21+ | For the Go SDK; also needs a C compiler for CGo | | C++17 compiler + CMake 3.16+ | For the C++ SDK | ::: tip -The Python SDK ships pre-built `abi3` wheels for common platforms. You do not need a Rust toolchain unless you are building from source or using the Go/C++ SDKs. +The Python SDK ships pre-built `abi3` wheels for common platforms. You do not need a Rust toolchain unless you are building from source or using the C++ SDK. ::: ## Subscription tiers diff --git a/docs-site/docs/getting-started/installation.md b/docs-site/docs/getting-started/installation.md index d01a558c..745b779e 100644 --- a/docs-site/docs/getting-started/installation.md +++ b/docs-site/docs/getting-started/installation.md @@ -1,11 +1,11 @@ --- title: Installation -description: Install ThetaDataDx for Rust, Python, TypeScript/Node.js, Go, or C++. +description: Install ThetaDataDx for Rust, Python, TypeScript/Node.js, or C++. --- # Installation -ThetaDataDx ships five language surfaces from one Rust core. Pick your language and use the one-liner below. +ThetaDataDx ships four language surfaces from one Rust core. Pick your language and use the one-liner below. Go consumers can build their own cgo wrapper against the unchanged C ABI in [`ffi/`](https://github.com/userFRM/ThetaDataDx/tree/main/ffi). ## SDK installation @@ -36,23 +36,6 @@ cd ThetaDataDx/sdks/typescript npm install npm run build ``` -```bash [Go] -# Prerequisites: Go 1.21+, Rust toolchain, C compiler (for CGo) - -# Build the Rust FFI library: -git clone https://github.com/userFRM/ThetaDataDx.git -cd ThetaDataDx -cargo build --release -p thetadatadx-ffi -# Produces target/release/libthetadatadx_ffi.so (Linux) -# or libthetadatadx_ffi.dylib (macOS) - -# On Windows, the Go SDK links against the GNU Rust target instead: -rustup target add x86_64-pc-windows-gnu -cargo build --release --target x86_64-pc-windows-gnu -p thetadatadx-ffi - -# Then add the Go module: -go get github.com/userFRM/thetadatadx/sdks/go -``` ```bash [C++] # Prerequisites: C++17 compiler, CMake 3.16+, Rust toolchain @@ -96,22 +79,7 @@ maturin develop --release Building from source requires a working Rust toolchain. Install it via [rustup.rs](https://rustup.rs). ::: -## Memory management (Go / C++) - -### Go - -Every Go SDK handle that wraps an FFI pointer must be `Close()`d: - -```go -creds, _ := thetadatadx.CredentialsFromFile("creds.txt") -defer creds.Close() - -config := thetadatadx.ProductionConfig() -defer config.Close() - -client, _ := thetadatadx.Connect(creds, config) -defer client.Close() -``` +## Memory management (C++) ### C++ @@ -153,19 +121,6 @@ import { ThetaDataDx } from 'thetadatadx'; const tdx = await ThetaDataDx.connectFromFile('creds.txt'); console.log('Connected successfully'); ``` -```go [Go] -creds, err := thetadatadx.CredentialsFromFile("creds.txt") -if err != nil { log.Fatal(err) } -defer creds.Close() - -config := thetadatadx.ProductionConfig() -defer config.Close() - -client, err := thetadatadx.Connect(creds, config) -if err != nil { log.Fatal(err) } -defer client.Close() -fmt.Println("Connected successfully") -``` ```cpp [C++] auto creds = tdx::Credentials::from_file("creds.txt"); auto client = tdx::Client::connect(creds, tdx::Config::production()); diff --git a/docs-site/docs/getting-started/quickstart.md b/docs-site/docs/getting-started/quickstart.md index 606fa0ac..a3116d0a 100644 --- a/docs-site/docs/getting-started/quickstart.md +++ b/docs-site/docs/getting-started/quickstart.md @@ -5,7 +5,7 @@ description: Install, authenticate, run a first historical call, subscribe to st # Quick Start -One page covering all five SDKs (Rust, Python, TypeScript / Node.js, Go, C++). Each step shows the same workflow tabbed across languages. +One page covering all four SDKs (Rust, Python, TypeScript / Node.js, C++). Each step shows the same workflow tabbed across languages. Go consumers can build a thin cgo wrapper against the unchanged C ABI in [`ffi/`](https://github.com/userFRM/ThetaDataDx/tree/main/ffi). ## Install @@ -30,15 +30,6 @@ pip install thetadatadx[all] # all three ```bash [TypeScript] npm install thetadatadx ``` -```bash [Go] -# Prerequisites: Go 1.21+, Rust toolchain, C compiler - -git clone https://github.com/userFRM/ThetaDataDx.git -cd ThetaDataDx -cargo build --release -p thetadatadx-ffi - -go get github.com/userFRM/thetadatadx/sdks/go -``` ```bash [C++] # Prerequisites: C++17 compiler, CMake 3.16+, Rust toolchain @@ -53,7 +44,7 @@ make ``` ::: -Python wheels are pre-built (`abi3`, Python 3.9+); no Rust toolchain needed on supported platforms. Go and C++ link against the Rust FFI library built once with `cargo build`. +Python wheels are pre-built (`abi3`, Python 3.9+); no Rust toolchain needed on supported platforms. C++ links against the Rust FFI library built once with `cargo build`. ## Authenticate @@ -85,16 +76,6 @@ import { ThetaDataDx } from 'thetadatadx'; // Credentials are passed directly to the connect helpers below. ``` -```go [Go] -import thetadatadx "github.com/userFRM/thetadatadx/sdks/go" - -creds, _ := thetadatadx.CredentialsFromFile("creds.txt") -defer creds.Close() - -// Or from env vars -envCreds, _ := thetadatadx.CredentialsFromEnv("THETA_EMAIL", "THETA_PASS") -defer envCreds.Close() -``` ```cpp [C++] #include "thetadx.hpp" @@ -153,36 +134,6 @@ for (const tick of eod) { console.log(`${tick.date}: O=${tick.open} H=${tick.high} L=${tick.low} C=${tick.close} V=${tick.volume}`); } ``` -```go [Go] -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, err := thetadatadx.CredentialsFromFile("creds.txt") - if err != nil { log.Fatal(err) } - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - client, err := thetadatadx.Connect(creds, config) - if err != nil { log.Fatal(err) } - defer client.Close() - - eod, err := client.StockHistoryEOD("AAPL", "20240101", "20240301") - if err != nil { log.Fatal(err) } - for _, tick := range eod { - fmt.Printf("%d: O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", - tick.Date, tick.Open, tick.High, tick.Low, tick.Close, tick.Volume) - } -} -``` ```cpp [C++] #include "thetadx.hpp" #include @@ -288,51 +239,6 @@ try { tdx.stopStreaming(); } ``` -```go [Go] -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, _ := thetadatadx.CredentialsFromFile("creds.txt") - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - fpss, err := thetadatadx.NewFpssClient(creds, config) - if err != nil { log.Fatal(err) } - defer fpss.Close() - - fpss.SubscribeQuotes("AAPL") - fpss.SubscribeTrades("MSFT") - - for { - event, err := fpss.NextEvent(1000) - if err != nil { - log.Println("Error:", err) - break - } - if event == nil { continue } - - switch event.Kind { - case thetadatadx.FpssQuoteEvent: - q := event.Quote - fmt.Printf("Quote: %d %.2f/%.2f\n", q.ContractID, q.Bid, q.Ask) - case thetadatadx.FpssTradeEvent: - t := event.Trade - fmt.Printf("Trade: %d %.2f x %d\n", t.ContractID, t.Price, t.Size) - } - } - - fpss.Shutdown() -} -``` ```cpp [C++] #include "thetadx.hpp" #include diff --git a/docs-site/docs/index.md b/docs-site/docs/index.md index 782d3f24..d73b8b5f 100644 --- a/docs-site/docs/index.md +++ b/docs-site/docs/index.md @@ -4,7 +4,7 @@ layout: home hero: name: "ThetaDataDx" text: "Rust SDK for ThetaData market data" - tagline: "Three public surfaces — historical request/response (MDDS gRPC), real-time streaming (FPSS), and whole-universe daily blobs (FLATFILES) — plus a local Greeks calculator, exposed in Rust, Python, TypeScript, Go, and C++ from a single Rust core." + tagline: "Three public surfaces — historical request/response (MDDS gRPC), real-time streaming (FPSS), and whole-universe daily blobs (FLATFILES) — plus a local Greeks calculator, exposed in Rust, Python, TypeScript, and C++ from a single Rust core." actions: - theme: brand text: Get Started @@ -19,8 +19,8 @@ hero: features: - icon: src: /icons/globe.svg - title: "Five language surfaces" - details: "One Rust core, five bindings: Rust, Python (PyO3, abi3), TypeScript/Node.js (napi-rs), Go (CGo), C++ (RAII header-only). Same API shape, typed results in each language's idiom." + title: "Four language surfaces" + details: "One Rust core, four bindings: Rust, Python (PyO3, abi3), TypeScript/Node.js (napi-rs), C++ (RAII header-only). Same API shape, typed results in each language's idiom. The C ABI in `ffi/` is also the supported integration path for any third-party Go/C consumer that wants to roll their own wrapper." - icon: src: /icons/bolt.svg title: "Real-time streaming" @@ -56,11 +56,6 @@ pip install thetadatadx[pandas] npm install thetadatadx ``` -```bash [Go] -# Build the FFI library once, then: -go get github.com/userFRM/thetadatadx/sdks/go -``` - ```bash [C++] # Build the FFI library once, then include sdks/cpp/include/thetadx.hpp ``` @@ -109,22 +104,6 @@ for (const q of quotes) { } ``` -```go [Go] -creds, _ := thetadatadx.CredentialsFromFile("creds.txt") -defer creds.Close() - -config := thetadatadx.ProductionConfig() -defer config.Close() - -client, _ := thetadatadx.Connect(creds, config) -defer client.Close() - -quotes, _ := client.StockHistoryQuote("AAPL", "20250115", "60000") -for _, q := range quotes { - fmt.Printf("%d: bid=%.2f ask=%.2f\n", q.Date, q.Bid, q.Ask) -} -``` - ```cpp [C++] #include "thetadx.hpp" @@ -143,7 +122,7 @@ for (const auto& q : quotes) { | Axis | ThetaDataDx | |------|-------------| -| Languages | Rust, Python, TypeScript, Go, C++ | +| Languages | Rust, Python, TypeScript, C++ | | Historical endpoints | Full typed historical surface (plus 4 `_stream` SDK-only variants) | | Real-time streaming | FPSS with SPKI pinning, SPSC ring, reconnect policy | | Local Greeks calculator | 23 Greeks + IV solver in Rust | diff --git a/docs-site/docs/streaming/index.md b/docs-site/docs/streaming/index.md index 8b86f588..7b7a20e5 100644 --- a/docs-site/docs/streaming/index.md +++ b/docs-site/docs/streaming/index.md @@ -16,7 +16,7 @@ graph LR C -->|"Disruptor
ring buffer"| D["Your Application
(callback / poll)"] ``` -Events are decoded from the FIT wire format and delta-decompressed on an I/O thread, then dispatched through an LMAX Disruptor ring buffer to your callback (Rust) or polling queue (Python/TypeScript/Go/C++). Every data event carries a `received_at_ns` nanosecond timestamp captured at frame decode time. +Events are decoded from the FIT wire format and delta-decompressed on an I/O thread, then dispatched through an LMAX Disruptor ring buffer to your callback (Rust) or polling queue (Python/TypeScript/C++). Every data event carries a `received_at_ns` nanosecond timestamp captured at frame decode time. ## Client Model @@ -27,13 +27,12 @@ Streaming is delivered through a **different client surface in each SDK**, by de | **Rust** | `ThetaDataDx` (the main client) | `start_streaming(callback)`, `subscribe_*`, `stop_streaming` are methods on the unified client. The streaming connection is established lazily. | | **Python** | `ThetaDataDx` (the main client) | Same unified client. Call `start_streaming()`, then poll `next_event()`. | | **TypeScript/Node.js** | `ThetaDataDx` (the main client) | Same unified client. Call `startStreaming()`, then poll `nextEvent()`. | -| **Go** | **`FpssClient`** (separate type) | The Go `Client` is historical-only. Construct a standalone `thetadatadx.NewFpssClient(creds, config)` for streaming. | | **C++** | **`tdx::FpssClient`** (separate type) | The C++ `tdx::Client` is historical-only. Construct a standalone `tdx::FpssClient(creds, config)` for streaming. | -This is not a drift or API mismatch -- it is the intentional per-language surface. Rust and Python can afford a unified client because both compile or bind against the same Rust core. Go and C++ use a thin FFI and split the surface to keep each handle's lifetime and memory ownership unambiguous. +This is not a drift or API mismatch -- it is the intentional per-language surface. Rust and Python can afford a unified client because both compile or bind against the same Rust core. C++ uses a thin FFI and splits the surface to keep each handle's lifetime and memory ownership unambiguous. ::: tip -If you are porting code between SDKs: anywhere a Rust, Python, or TypeScript example calls `tdx.subscribe_quotes(...)` / `tdx.subscribeQuotes(...)` on the main client, the Go and C++ equivalents call `fpss.SubscribeQuotes(...)` / `fpss.subscribe_quotes(...)` on a separate `FpssClient` handle. +If you are porting code between SDKs: anywhere a Rust, Python, or TypeScript example calls `tdx.subscribe_quotes(...)` / `tdx.subscribeQuotes(...)` on the main client, the C++ equivalent calls `fpss.subscribe_quotes(...)` on a separate `FpssClient` handle. ::: ## SDK Streaming Models @@ -43,11 +42,10 @@ If you are porting code between SDKs: anywhere a Rust, Python, or TypeScript exa | **Rust** | Synchronous callback | `&FpssEvent` enum | Disruptor ring buffer dispatch. No Tokio on the hot path. | | **Python** | Polling | typed pyclass | `next_event()` returns typed `Quote` / `Trade` / `Ohlcvc` / `OpenInterest` / `Simple` / `RawData` objects. | | **TypeScript/Node.js** | Polling | `object` | `nextEvent()` returns events as JS objects with all fields. | -| **Go** | Polling | `*FpssEvent` struct | `NextEvent()` returns typed Go structs. Price fields pre-decoded to `float64`. | | **C++** | Polling | `FpssEventPtr` | `next_event()` returns `unique_ptr` (RAII). `#[repr(C)]` layout. | ::: warning No JSON in FFI -Go and C++ receive typed `#[repr(C)]` structs directly from Rust -- not JSON. All field access is zero-copy struct member access. +C++ receives typed `#[repr(C)]` structs directly from Rust -- not JSON. All field access is zero-copy struct member access. ::: ## Available Data Streams @@ -137,53 +135,6 @@ while True: tdx.stop_streaming() ``` -```go [Go] -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, _ := thetadatadx.CredentialsFromFile("creds.txt") - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - fpss, _ := thetadatadx.NewFpssClient(creds, config) - defer fpss.Close() - - fpss.SubscribeQuotes("AAPL") - fpss.SubscribeTrades("MSFT") - - for { - event, err := fpss.NextEvent(5000) - if err != nil { - log.Println("Error:", err) - break - } - if event == nil { - continue - } - switch event.Kind { - case thetadatadx.FpssQuoteEvent: - q := event.Quote - fmt.Printf("Quote: contract=%d bid=%.2f ask=%.2f\n", - q.ContractID, q.Bid, q.Ask) - case thetadatadx.FpssTradeEvent: - t := event.Trade - fmt.Printf("Trade: contract=%d price=%.2f size=%d\n", - t.ContractID, t.Price, t.Size) - } - } - - fpss.Shutdown() -} -``` ```cpp [C++] #include "thetadx.hpp" #include diff --git a/docs-site/docs/streaming/latency.md b/docs-site/docs/streaming/latency.md index 110c1165..1700b7aa 100644 --- a/docs-site/docs/streaming/latency.md +++ b/docs-site/docs/streaming/latency.md @@ -112,28 +112,6 @@ while True: f"received_at_ns={received_ns} " f"since_receive={approx_latency_ms:.1f}ms") ``` -```go [Go] -for { - event, err := fpss.NextEvent(5000) - if err != nil { - log.Println("Error:", err) - break - } - if event == nil { - continue - } - - if event.Kind == thetadatadx.FpssQuoteEvent { - q := event.Quote - // ReceivedAtNs is captured at frame decode time on the Rust side. - // For precise wire latency, subtract the exchange epoch ns from - // ReceivedAtNs. The ms_of_day + date -> epoch conversion is best - // done on the Rust side via tdbe::latency::latency_ns(). - fmt.Printf("Quote: bid=%.4f ask=%.4f rx=%dns\n", - q.Bid, q.Ask, q.ReceivedAtNs) - } -} -``` ```cpp [C++] while (true) { auto event = fpss.next_event(5000); @@ -162,7 +140,7 @@ For the absolute lowest latency: 2. **Keep the callback fast** -- the Disruptor callback runs on the consumer thread. Push to your own queue for heavy processing. -3. **Use the Rust SDK directly** -- Python, TypeScript/Node.js, Go, and C++ add an mpsc channel hop between the Disruptor and `next_event()`. +3. **Use the Rust SDK directly** -- Python, TypeScript/Node.js, and C++ add an mpsc channel hop between the Disruptor and `next_event()`. ## Network Physics: Minimum Achievable Latency @@ -189,7 +167,7 @@ For latency-sensitive applications: 1. **Colocate near NJ** -- AWS us-east-1 (N. Virginia) or any NJ/NYC-area datacenter gets sub-5ms 2. **`FpssFlushMode::Immediate`** reduces software batching latency by up to 100ms, but cannot beat physics -3. **Use the Rust SDK directly** -- eliminates the FFI channel hop present in Python/TypeScript/Go/C++ (adds <1ms) +3. **Use the Rust SDK directly** -- eliminates the FFI channel hop present in Python/TypeScript/C++ (adds <1ms) ## Latency Histogram Example @@ -253,64 +231,6 @@ for i, count in enumerate(buckets): if count > 0: print(f"{i*10:>3}-{(i+1)*10:>3}ms: {count} events") ``` -```go [Go] -package main - -import ( - "fmt" - "log" - "time" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, _ := thetadatadx.CredentialsFromFile("creds.txt") - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - fpss, _ := thetadatadx.NewFpssClient(creds, config) - defer fpss.Close() - - fpss.SubscribeQuotes("SPY") - - buckets := make([]uint64, 20) // 0-10ms, 10-20ms, ... - deadline := time.Now().Add(60 * time.Second) - - for time.Now().Before(deadline) { - event, err := fpss.NextEvent(5000) - if err != nil { - log.Println("Error:", err) - break - } - if event == nil { - continue - } - if event.Kind == thetadatadx.FpssQuoteEvent { - // ReceivedAtNs is Rust-side; approximate histogram only - q := event.Quote - delta := int64(time.Now().UnixNano()) - int64(q.ReceivedAtNs) - if delta < 0 { delta = 0 } - latMs := uint64(delta) / 1_000_000 - bucket := latMs / 10 - if bucket > 19 { - bucket = 19 - } - buckets[bucket]++ - } - } - - fpss.Shutdown() - - for i, count := range buckets { - if count > 0 { - fmt.Printf("%3d-%3dms: %d events\n", i*10, (i+1)*10, count) - } - } -} -``` ```cpp [C++] #include "thetadx.hpp" #include @@ -368,6 +288,5 @@ Every `FpssData` variant includes this field: | `OpenInterest` | Present | `u64` | | `Ohlcvc` | Present | `u64` | -In Go: `event.Quote.ReceivedAtNs`, `event.Trade.ReceivedAtNs`, etc. In C++: `event->quote.received_at_ns`, `event->trade.received_at_ns`, etc. In Python: `event.received_at_ns` (integer). diff --git a/docs-site/docs/streaming/reconnection.md b/docs-site/docs/streaming/reconnection.md index a4dabd22..dbca016a 100644 --- a/docs-site/docs/streaming/reconnection.md +++ b/docs-site/docs/streaming/reconnection.md @@ -8,7 +8,7 @@ description: Handle FPSS disconnects, implement reconnection logic with reconnec ## Reconnection APIs Rust exposes `reconnect_streaming(handler)` on the unified `ThetaDataDx` client. -Python, TypeScript/Node.js, Go, and C++ expose `reconnect()` on their public streaming clients. +Python, TypeScript/Node.js, and C++ expose `reconnect()` on their public streaming clients. ## Reconnection with `reconnect_streaming()` (Rust) @@ -50,7 +50,7 @@ match thetadatadx::fpss::reconnect_delay(reason) { `reconnect_streaming()` uses the same `DirectConfig` (including `fpss_hosts`) that was passed at `ThetaDataDx::connect()` time. If hosts change, create a new `ThetaDataDx` instance. ::: -## Reconnection with `reconnect()` (Python, Go, C++) +## Reconnection with `reconnect()` (Python, C++) ::: code-group ```python [Python] @@ -66,36 +66,6 @@ tdx.subscribe_option_quotes("SPY", "20260116", "600", "C") # reconnect() restores the existing subscription set tdx.reconnect() ``` -```go [Go] -package main - -import ( - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, _ := thetadatadx.CredentialsFromFile("creds.txt") - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - fpss, err := thetadatadx.NewFpssClient(creds, config) - if err != nil { - log.Fatal(err) - } - defer fpss.Close() - - fpss.SubscribeQuotes("AAPL") - fpss.SubscribeOptionQuotes("SPY", "20260116", "600", "C") - - if err := fpss.Reconnect(); err != nil { - log.Fatal(err) - } -} -``` ```cpp [C++] #include "thetadx.hpp" @@ -281,68 +251,6 @@ while True: tdx.stop_streaming() ``` -```go [Go] -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, _ := thetadatadx.CredentialsFromFile("creds.txt") - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - fpss, err := thetadatadx.NewFpssClient(creds, config) - if err != nil { - log.Fatal(err) - } - defer fpss.Close() - - // Subscribe to real-time data - fpss.SubscribeQuotes("AAPL") - fpss.SubscribeTrades("AAPL") - - // Process typed events - for { - event, err := fpss.NextEvent(5000) - if err != nil { - log.Println("Error:", err) - break - } - if event == nil { - continue - } - - switch event.Kind { - case thetadatadx.FpssQuoteEvent: - q := event.Quote - // Bid and Ask are pre-decoded to float64 - fmt.Printf("[QUOTE] contract=%d bid=%.4f ask=%.4f rx=%dns\n", - q.ContractID, q.Bid, q.Ask, q.ReceivedAtNs) - - case thetadatadx.FpssTradeEvent: - t := event.Trade - // Price is pre-decoded to float64 - fmt.Printf("[TRADE] contract=%d price=%.4f size=%d\n", - t.ContractID, t.Price, t.Size) - - case thetadatadx.FpssControlEvent: - ctrl := event.Control - if ctrl.Kind == 6 { // Disconnected - fmt.Printf("Disconnected: %s\n", ctrl.Detail) - } - } - } - - fpss.Shutdown() -} -``` ```cpp [C++] #include "thetadx.hpp" #include diff --git a/docs/ATTRIBUTION.md b/docs/ATTRIBUTION.md index 542e861e..cd7e2fd9 100644 --- a/docs/ATTRIBUTION.md +++ b/docs/ATTRIBUTION.md @@ -13,7 +13,7 @@ which is also licensed under the Apache License, Version 2.0. The upstream project publishes the same docstrings as part of its `client.py` module; we consume them through our TOML SSOT so the generator can emit identical prose into every generated surface (Python sync + -`_async`, fluent builders, TypeScript, Rust, C++, Go) without drift. +`_async`, fluent builders, TypeScript, Rust, C++) without drift. The Apache-2.0 license does not require us to reproduce the notice in every generated file; we acknowledge the source here so downstream diff --git a/docs/api-reference.md b/docs/api-reference.md index a7d335e8..50f834aa 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -685,7 +685,7 @@ ThetaDataDx exposes the full typed historical surface plus 4 `_stream` variants. ### FFI Coverage -Every historical endpoint is exposed through the `thetadatadx-ffi` C ABI crate. Each method has a corresponding `extern "C"` function (e.g., `thetadatadx_stock_history_eod`). The Go and C++ SDKs wrap these FFI functions 1:1. +Every historical endpoint is exposed through the `thetadatadx-ffi` C ABI crate. Each method has a corresponding `extern "C"` function (e.g., `thetadatadx_stock_history_eod`). The C++ SDK wraps these FFI functions 1:1; third-party C-interop wrappers (Go via cgo, Swift, Zig, etc.) can do the same against the unchanged ABI. **No JSON crosses the FFI boundary.** All inputs and outputs use typed `#[repr(C)]` structs -- historical endpoints, streaming events, Greeks, and subscriptions alike. `tdx_fpss_next_event` and `tdx_unified_next_event` return `*mut TdxFpssEvent` (a tagged `#[repr(C)]` struct with quote/trade/open_interest/ohlcvc/control/raw_data variants), freed with `tdx_fpss_event_free`. @@ -773,10 +773,9 @@ Install: #### FPSS Event Types (C) The generator emits the layout below; the C++ header `thetadx.h` now -`#include`s `fpss_event_structs.h.inc` (byte-identical to the Go C -header) instead of hand-rolling the struct, and `thetadx.hpp` guards -every field via `static_assert(offsetof / sizeof)` so a future drift -is compile-fatal. +`#include`s `fpss_event_structs.h.inc` instead of hand-rolling the +struct, and `thetadx.hpp` guards every field via +`static_assert(offsetof / sizeof)` so a future drift is compile-fatal. ```c typedef enum { TDX_FPSS_QUOTE=0, TDX_FPSS_TRADE=1, TDX_FPSS_OPEN_INTEREST=2, @@ -820,28 +819,6 @@ while (true) { tdx.stopStreaming(); ``` -### Go SDK: Streaming - -```go -fpss, _ := thetadatadx.NewFpssClient(creds, config) -defer fpss.Close() - -fpss.SubscribeQuotes("AAPL") -for { - event, _ := fpss.NextEvent(5000) // returns *FpssEvent - if event == nil { - continue // timeout - } - switch event.Kind { - case thetadatadx.FpssQuoteEvent: - fmt.Printf("Quote: bid=%d ask=%d\n", event.Quote.Bid, event.Quote.Ask) - case thetadatadx.FpssTradeEvent: - fmt.Printf("Trade: price=%d size=%d\n", event.Trade.Price, event.Trade.Size) - } -} -fpss.Shutdown() -``` - ### C++ SDK: Streaming ```cpp diff --git a/docs/architecture.md b/docs/architecture.md index 7caad56e..57bd9b62 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -491,7 +491,7 @@ Characters are packed pairwise: `byte = (nibble(c1) << 4) | nibble(c2)`. Odd-len ## Cross-Language SDK Surfaces -Every SDK lives over the same Rust core (`thetadatadx` + `tdbe`) — Python via PyO3, TypeScript via napi-rs, Go via CGo over the C FFI, and C++ as an RAII wrapper over the same C FFI. None of the language SDKs reimplement the wire protocol; they expose the Rust parser output through their respective binding layers. +Every SDK lives over the same Rust core (`thetadatadx` + `tdbe`) — Python via PyO3, TypeScript via napi-rs, and C++ as an RAII wrapper over the C FFI. None of the language SDKs reimplement the wire protocol; they expose the Rust parser output through their respective binding layers. The C ABI also serves as the supported integration path for any third-party C-interop language (Go via cgo, Swift, Zig, etc.). ### Typed pyclass surface (Python) diff --git a/docs/java-parity-checklist.md b/docs/java-parity-checklist.md index 19433a5b..a6db883d 100644 --- a/docs/java-parity-checklist.md +++ b/docs/java-parity-checklist.md @@ -199,7 +199,6 @@ empty forms. | Language | Type | Parity | |----------|------|:------:| | Rust core / FFI | `i32` (67=Call, 80=Put, 0=absent). `is_call()`/`is_put()` helpers. | [✗] | -| Go | `string` (`"C"`, `"P"`, `""`) | [✗] | | Python | `string` | [✗] | | Java internal | integer; WS JSON emits string | reference | @@ -244,16 +243,17 @@ Rust SDK defaults to `"09:30:00"`/`"16:00:00"` on all interval endpoints. - **SPKI pinning** — authenticates the FPSS server on its public key alone, not on the expired certificate chain. -- **Typed event surface across 5 SDKs** — Java's API is untyped and +- **Typed event surface across 4 SDKs** — Java's API is untyped and callback-based; the Rust core exposes typed `FpssEvent` variants across - Python / TypeScript / Go / C++ / Rust. + 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 / Go / C++** — one `extern "C"` - ABI shared by all non-Rust SDKs. +- **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` for historical methods. diff --git a/docs/macro-guide.md b/docs/macro-guide.md index 886e49f2..47788e64 100644 --- a/docs/macro-guide.md +++ b/docs/macro-guide.md @@ -172,9 +172,9 @@ For registry-driven endpoints, the SDK/FFI surface is generated from `crates/thetadatadx/sdk_surface.toml`. For tick projection helpers, use `tick_schema.toml`. -You only edit `ffi/src/lib.rs`, `sdks/python/src/lib.rs`, `sdks/go/*.go`, or -`sdks/cpp/*` directly when changing runtime plumbing that is intentionally -outside the checked-in generated surface. +You only edit `ffi/src/lib.rs`, `sdks/python/src/lib.rs`, or `sdks/cpp/*` +directly when changing runtime plumbing that is intentionally outside the +checked-in generated surface. ### 6. Update CHANGELOG.md diff --git a/docs/public-api-redesign.md b/docs/public-api-redesign.md index 02011af5..c350d48d 100644 --- a/docs/public-api-redesign.md +++ b/docs/public-api-redesign.md @@ -3,7 +3,7 @@ ## Purpose This document defines the migration plan for a more ergonomic public API across -Rust, Python, Go, C++, REST, and MCP without sacrificing the current +Rust, Python, C++, REST, and MCP without sacrificing the current single-source-of-truth endpoint model. The intent is not to replace the exact generated endpoint surface. The intent diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 4a6fcfb7..491e3fe3 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-ffi" -version = "8.0.28" +version = "8.0.29" edition.workspace = true rust-version.workspace = true authors.workspace = true diff --git a/ffi/README.md b/ffi/README.md index b272d02f..91735510 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -2,9 +2,9 @@ C FFI layer for `thetadatadx` — exposes the Rust SDK as `extern "C"` functions. -Compiled as both `cdylib` (shared library) and `staticlib` (archive). Consumed by the Go (CGo), C++ (RAII), and TypeScript/Node.js (napi-rs) SDKs. +Compiled as both `cdylib` (shared library) and `staticlib` (archive). Consumed by the C++ (RAII) and TypeScript/Node.js (napi-rs) SDKs, and available to any third-party C/C++/Go/etc. consumer that wants to roll their own wrapper against the `tdx_*` symbols. -> **FLATFILES coverage:** the FFI layer currently exposes the MDDS (historical) and FPSS (streaming) surfaces only. The third surface — FLATFILES whole-universe daily blobs — is shipped in the Rust core (v8.0.17+) and is being added to the C ABI under issue [#434](https://github.com/userFRM/ThetaDataDx/issues/434). The Go (#437) and C++ (#438) bindings track this issue as their upstream blocker. See [`ROADMAP.md`](../ROADMAP.md#flatfiles--binding-coverage) for the per-binding status. +> **FLATFILES coverage:** the FFI layer currently exposes the MDDS (historical) and FPSS (streaming) surfaces only. The third surface — FLATFILES whole-universe daily blobs — is shipped in the Rust core (v8.0.17+) and is being added to the C ABI under issue [#434](https://github.com/userFRM/ThetaDataDx/issues/434). The C++ binding (#438) tracks this issue as its upstream blocker. See [`ROADMAP.md`](../ROADMAP.md#flatfiles--binding-coverage) for the per-binding status. ## Building diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 10ebf0fc..8e606ea5 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -1,15 +1,13 @@ //! Thread-local error slot plus the `tdx_last_error` / `tdx_clear_error` //! FFI accessors and the `require_cstr!` macro used by endpoint wrappers. //! -//! Contract: the error slot is scoped to the OS thread that set it. Higher- -//! level languages whose runtime can migrate a logical execution unit -//! across OS threads (notably Go, where a goroutine can park on one thread -//! and resume on another) MUST pin the execution unit for the duration of -//! a clear/call/check sequence. The generated Go wrappers do this via -//! `runtime.LockOSThread` + deferred unlock (see -//! `crates/thetadatadx/build_support/endpoints/render/go.rs` — -//! `render_go_endpoint_method`). C++ and Python never migrate threads -//! implicitly, so no pinning is needed there. +//! Contract: the error slot is scoped to the OS thread that set it. C++ +//! and Python never migrate threads implicitly, so no pinning is needed +//! there. Any third-party FFI consumer whose runtime can migrate a logical +//! execution unit across OS threads (e.g. Go's goroutines, which can park +//! on one thread and resume on another) MUST pin the execution unit for +//! the duration of a clear/call/check sequence — typically via the host +//! runtime's equivalent of `runtime.LockOSThread` + deferred unlock. use std::ffi::{CStr, CString}; use std::os::raw::c_char; diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 0cada48b..5ca93483 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -7,7 +7,8 @@ //! C FFI layer for `thetadatadx` — exposes the Rust SDK as `extern "C"` functions. //! //! This crate is compiled as both `cdylib` (shared library) and `staticlib` (archive). -//! It is consumed by the Go (`CGo`) and C++ SDKs. +//! It is consumed by the C++ SDK and is the supported integration path for any +//! third-party C/C++/Go/etc. consumer that wants to roll their own wrapper. //! //! # Safety //! diff --git a/scripts/check_docs_consistency.py b/scripts/check_docs_consistency.py index 4a87265a..5611f0ac 100755 --- a/scripts/check_docs_consistency.py +++ b/scripts/check_docs_consistency.py @@ -86,14 +86,6 @@ def fail(message: str) -> None: raise SystemExit(1) -def snake_to_go(name: str) -> str: - acronyms = { - "dte": "DTE", - "nbbo": "NBBO", - } - return "".join(acronyms.get(part, part.capitalize()) for part in name.split("_")) - - def expect_contains(path: Path, snippet: str) -> None: text = path.read_text() if snippet not in text: @@ -172,10 +164,6 @@ def check_static_docs() -> None: expect_contains(sdk_overview, "26 functions: `tdx_fpss_connect`") expect_contains(sdk_overview, "Windows is validated with the GNU Rust target (`x86_64-pc-windows-gnu`)") - go_readme = ROOT / "sdks/go/README.md" - expect_contains(go_readme, "Windows: CI-validated via a GNU-targeted Rust FFI build (`x86_64-pc-windows-gnu`)") - expect_contains(go_readme, "cargo build --release --target x86_64-pc-windows-gnu -p thetadatadx-ffi") - option_docs = list((ROOT / "docs-site/docs/historical/option").rglob("*.md")) strike_docs = option_docs + [ ROOT / "docs-site/docs/api-reference.md", @@ -246,7 +234,6 @@ def check_static_docs() -> None: ROOT / "docs-site/docs/streaming/latency.md", ]: expect_not_contains(streaming_page, "Python does not expose reconnect_streaming() directly.") - expect_not_contains(streaming_page, "Go does not expose reconnect_streaming() directly.") expect_not_contains(streaming_page, "C++ does not expose reconnect_streaming() directly.") expect_not_contains(streaming_page, "| `active_subscriptions` | `() -> std::string` |") expect_contains(ROOT / "docs-site/docs/streaming/events.md", "| `Reconnect` | `() error` |") @@ -260,7 +247,7 @@ def check_static_docs() -> None: ) expect_contains( ROOT / "docs-site/docs/streaming/reconnection.md", - "Python, TypeScript/Node.js, Go, and C++ expose `reconnect()` on their public streaming clients.", + "Python, TypeScript/Node.js, and C++ expose `reconnect()` on their public streaming clients.", ) @@ -328,36 +315,19 @@ def check_endpoint_option_surface() -> None: f"missing={missing or '[]'} extra={extra or '[]'}" ) - for path in [ - ROOT / "sdks/go/endpoint_request_options.h.inc", - ROOT / "sdks/cpp/include/endpoint_request_options.h.inc", - ]: - c_fields = extract_struct_fields( - path, - r"typedef struct \{(.*?)\n\}\s*TdxEndpointRequestOptions;", - r"^\s*(?:const char\*|int32_t|double|uint64_t)\s+([a-z_]+);", - ) - # Exclude has_* sentinel flags (FFI implementation detail, not builder params) - c_fields = {f for f in c_fields if not f.startswith("has_")} - if c_fields != ALL_OPTION_FIELDS: - missing = sorted(ALL_OPTION_FIELDS - c_fields) - extra = sorted(c_fields - ALL_OPTION_FIELDS) - fail( - f"{path.relative_to(ROOT)} endpoint option fields drifted from endpoint_surface.toml. " - f"missing={missing or '[]'} extra={extra or '[]'}" - ) - - go_fields = extract_struct_fields( - ROOT / "sdks/go/endpoint_options.go", - r"type EndpointRequestOptions struct \{(.*?)\n\}", - r"^\s*([A-Z][A-Za-z0-9]+)\s+\*", + c_path = ROOT / "sdks/cpp/include/endpoint_request_options.h.inc" + c_fields = extract_struct_fields( + c_path, + r"typedef struct \{(.*?)\n\}\s*TdxEndpointRequestOptions;", + r"^\s*(?:const char\*|int32_t|double|uint64_t)\s+([a-z_]+);", ) - expected_go_fields = {snake_to_go(name) for name in ALL_OPTION_FIELDS} - if go_fields != expected_go_fields: - missing = sorted(expected_go_fields - go_fields) - extra = sorted(go_fields - expected_go_fields) + # Exclude has_* sentinel flags (FFI implementation detail, not builder params) + c_fields = {f for f in c_fields if not f.startswith("has_")} + if c_fields != ALL_OPTION_FIELDS: + missing = sorted(ALL_OPTION_FIELDS - c_fields) + extra = sorted(c_fields - ALL_OPTION_FIELDS) fail( - "sdks/go/endpoint_options.go EndpointRequestOptions fields drifted from endpoint_surface.toml. " + f"{c_path.relative_to(ROOT)} endpoint option fields drifted from endpoint_surface.toml. " f"missing={missing or '[]'} extra={extra or '[]'}" ) @@ -376,12 +346,9 @@ def check_endpoint_option_surface() -> None: for path in [ ROOT / "ffi/src/lib.rs", - ROOT / "sdks/go/ffi_bridge.h", ROOT / "sdks/cpp/include/thetadx.h", - ROOT / "sdks/go/client.go", ROOT / "sdks/cpp/include/thetadx.hpp", ROOT / "sdks/cpp/src/thetadx.cpp", - ROOT / "sdks/go/README.md", ROOT / "sdks/cpp/README.md", ROOT / "docs-site/docs/historical/option/history/greeks-eod.md", ]: diff --git a/scripts/validate_agreement.py b/scripts/validate_agreement.py index 6478fa6c..aee702dc 100755 --- a/scripts/validate_agreement.py +++ b/scripts/validate_agreement.py @@ -3,7 +3,7 @@ Loads per-language validator artifacts from `artifacts/validator_.json` (written by `scripts/validate_cli.py`, `scripts/validate_python.py`, -`sdks/go/cmd/validate`, and `sdks/cpp` validator) and compares every +and `sdks/cpp` validator) and compares every (endpoint, mode) cell across SDKs on: * status (PASS / SKIP / FAIL) @@ -73,8 +73,8 @@ silently), so we collapse all three to a single unambiguous sentinel 4. date-shaped fields (`date`, `expiration`, or ending in `_date`): value `0` -> `None`. Every SDK emits the sentinel verbatim (Python - `sdks/python/src/tick_columnar.rs:7,38`; Go `sdks/go/tick_structs.go:10,35`; - server `tools/server/src/format.rs:346`). Without this normalization, + `sdks/python/src/tick_columnar.rs:7,38`; server + `tools/server/src/format.rs:346`). Without this normalization, a producer that happens to see `date == 0` (no-data cell, pre-market snapshot) would false-diff against one that serializes the same cell as `null`. @@ -129,7 +129,7 @@ ROOT = Path(__file__).resolve().parents[1] ARTIFACTS_DIR = ROOT / "artifacts" -LANGS = ("python", "cli", "go", "cpp") +LANGS = ("python", "cli", "cpp") # Rounding precision for float comparison in canonicalized first rows. # 6 decimals matches the canonicalization contract documented in the @@ -184,15 +184,15 @@ def load_artifact(lang: str, artifacts_dir: Path) -> list[dict] | None: _STRIKE_FIELD_NAMES: frozenset[str] = frozenset({"strike"}) # Right-shaped field names. Tick types emit `right` as `"C" / "P" / ""` -# (Python `tick_columnar.rs:41`, Go `RightStr`); `OptionContract` uses -# raw int 67/80/0. Empty string OR int 0 are both sentinels -> None. +# (Python `tick_columnar.rs:41`); `OptionContract` uses raw int 67/80/0. +# Empty string OR int 0 are both sentinels -> None. _RIGHT_FIELD_NAMES: frozenset[str] = frozenset({"right"}) # Union of all sentinel-shaped field names for omit-vs-null normalization. -# A producer that omits the field (Go `omitempty`, server skip-when-zero) -# is equivalent to one that emits `null` or the raw sentinel value (Python -# tick_columnar emits 0/`""` verbatim). The consumer strips post-canonical -# `None` values for these fields so all three shapes converge to "absent". +# A producer that omits the field (server skip-when-zero) is equivalent to +# one that emits `null` or the raw sentinel value (Python tick_columnar +# emits 0/`""` verbatim). The consumer strips post-canonical `None` values +# for these fields so all three shapes converge to "absent". _SENTINEL_SHAPED_FIELDS: frozenset[str] = ( _DATE_FIELD_NAMES | _MS_FIELD_NAMES | _STRIKE_FIELD_NAMES | _RIGHT_FIELD_NAMES ) @@ -298,10 +298,9 @@ def _canonicalize_row(value: Any, key: str = "") -> Any: * post-canonical None -> stripped from the dict for LEAF fields whose name is in `_SENTINEL_SHAPED_FIELDS`. This makes producer divergence on omit-vs-null-vs-sentinel-value invisible to the - diff engine (Go `omitempty` strips zero contract-ids; server - skips them in `tools/server/src/format.rs:89`; Python emits raw - `0`/`""`; CLI emits raw `0`/`""` after round-3). All four - shapes converge to "field absent from the dict" post- + diff engine (server skips them in `tools/server/src/format.rs:89`; + Python emits raw `0`/`""`; CLI emits raw `0`/`""` after round-3). + All three shapes converge to "field absent from the dict" post- canonicalization. Sub-dicts and sub-lists are NEVER elided from their parent, even if diff --git a/scripts/validate_release.sh b/scripts/validate_release.sh index 2e2f491a..078d1574 100755 --- a/scripts/validate_release.sh +++ b/scripts/validate_release.sh @@ -5,9 +5,8 @@ # Single script that validates every delivery surface: # 1. CLI — generated validate_cli.py (Rust core) # 2. Python — generated validate_python.py (PyO3 bridge) -# 3. Go — generated validate.go (CGo FFI bridge) -# 4. C++ — generated validate.cpp (C FFI bridge) -# 5. Agreement — cross-language artifact diff (scripts/validate_agreement.py) +# 3. C++ — generated validate.cpp (C FFI bridge) +# 4. Agreement — cross-language artifact diff (scripts/validate_agreement.py) # # Each SDK validator writes a per-cell JSON artifact to # `artifacts/validator_.json`. The agreement step asserts that every @@ -19,7 +18,7 @@ # ./scripts/validate_release.sh /path/to/creds.txt # # Prerequisites: -# Rust, Go, Python, a C++17 toolchain, and CMake +# Rust, Python, a C++17 toolchain, and CMake # # The script will build missing local artifacts as needed. If the Python SDK is # not installed into the current interpreter, it bootstraps a local virtualenv @@ -123,37 +122,15 @@ else fi record "Python" "$py_pass" "$py_skip" "$py_fail" -# ── 3. Go SDK ─────────────────────────────────────────────────────────────── - -section "3/5 Go SDK — live parameter-mode matrix" - -go_pass=0 -go_skip=0 -go_fail=0 - FFI_LIB="$REPO/target/release" if [ ! -f "$FFI_LIB/libthetadatadx_ffi.so" ] && [ ! -f "$FFI_LIB/libthetadatadx_ffi.dylib" ]; then echo "Building FFI library..." cargo build --release -p thetadatadx-ffi --manifest-path "$REPO/Cargo.toml" fi -if [ -f "$FFI_LIB/libthetadatadx_ffi.so" ] || [ -f "$FFI_LIB/libthetadatadx_ffi.dylib" ]; then - go_result=$(cd "$REPO/sdks/go" && CGO_LDFLAGS="-L$FFI_LIB" LD_LIBRARY_PATH="$FFI_LIB" \ - go run ./cmd/validate "$CREDS" 2>&1) - echo "$go_result" - go_counts=$(echo "$go_result" | grep -oP 'COUNTS:\K.*') - go_pass=$(echo "$go_counts" | cut -d: -f1) - go_skip=$(echo "$go_counts" | cut -d: -f2) - go_fail=$(echo "$go_counts" | cut -d: -f3) -else - echo " FFI library build failed." - go_fail=61 -fi -record "Go" "$go_pass" "$go_skip" "$go_fail" - -# ── 4. C++ SDK ────────────────────────────────────────────────────────────── +# ── 3. C++ SDK ────────────────────────────────────────────────────────────── -section "4/5 C++ SDK — live parameter-mode matrix" +section "3/4 C++ SDK — live parameter-mode matrix" cpp_pass=0 cpp_skip=0 @@ -178,9 +155,9 @@ else fi record "C++" "$cpp_pass" "$cpp_skip" "$cpp_fail" -# ── 5. Cross-language agreement ───────────────────────────────────────────── +# ── 4. Cross-language agreement ───────────────────────────────────────────── -section "5/5 Cross-language agreement" +section "4/4 Cross-language agreement" agreement_result=$(python3 "$REPO/scripts/validate_agreement.py" 2>&1) echo "$agreement_result" diff --git a/sdks/README.md b/sdks/README.md index 21ed2032..7e14eb9e 100644 --- a/sdks/README.md +++ b/sdks/README.md @@ -8,7 +8,6 @@ Multi-language SDKs for ThetaDataDx. All are thin bindings over the shared Rust |---|---|---|---|---|---| | **Python** | `pip install thetadatadx` | Full generated historical surface | `ThetaDataDx` | `all_greeks()`, chainable `.to_polars()` / `.to_pandas()` / `.to_arrow()` | [sdks/python/](python/) | | **TypeScript/Node.js** | `npm install thetadatadx` | Full generated historical surface | `ThetaDataDx` | `allGreeks()` | [sdks/typescript/](typescript/) | -| **Go** | `go get github.com/userFRM/thetadatadx/sdks/go` | Full generated historical surface | `FpssClient` | via FFI | [sdks/go/](go/) | | **C++** | CMake `find_library` | Full generated historical surface | `FpssClient` | via FFI | [sdks/cpp/](cpp/) | | **C FFI** | `cargo build --release -p thetadatadx-ffi` | Full generated historical surface | `TdxUnified` / `TdxFpssHandle` | `tdx_all_greeks` | [ffi/](../ffi/) | @@ -19,14 +18,14 @@ Multi-language SDKs for ThetaDataDx. All are thin bindings over the shared Rust | Your Application | +--------+----------+ | - +----------+-------+-------+----------+ - | | | | - +----v----+ +--v------+ +----v----+ +---v-----+ - | Python | | Node.js| | Go | | C++ | - | (PyO3) | | (napi-rs)| | (CGo) | | (C API)| - +---------+ +---------+ +--------+ +--------+ - | | | | - +----------+-------+-------+----------+ + +----------+-------+----------+ + | | | + +----v----+ +--v------+ +---v-----+ + | Python | | Node.js| | C++ | + | (PyO3) | | (napi-rs)| | (C API) | + +---------+ +---------+ +---------+ + | | | + +----------+-------+----------+ | +--------v--------+ | C FFI Layer | @@ -55,13 +54,12 @@ Multi-language SDKs for ThetaDataDx. All are thin bindings over the shared Rust +-----------------+ ``` -The Python SDK uses [PyO3](https://pyo3.rs/) with [Maturin](https://www.maturin.rs/) for direct Rust-to-Python bindings, bypassing the C FFI layer. The TypeScript/Node.js SDK uses [napi-rs](https://napi.rs/) for direct Rust-to-Node.js bindings via a native addon. The Go and C++ SDKs go through the C FFI crate (`thetadatadx-ffi`), which exposes `extern "C"` functions compiled as both a shared library (`cdylib`) and a static archive (`staticlib`). +The Python SDK uses [PyO3](https://pyo3.rs/) with [Maturin](https://www.maturin.rs/) for direct Rust-to-Python bindings, bypassing the C FFI layer. The TypeScript/Node.js SDK uses [napi-rs](https://napi.rs/) for direct Rust-to-Node.js bindings via a native addon. The C++ SDK goes through the C FFI crate (`thetadatadx-ffi`), which exposes `extern "C"` functions compiled as both a shared library (`cdylib`) and a static archive (`staticlib`). ## Validation Matrix - Python: wheel builds and import smoke are validated on Linux x64, macOS arm64 (Apple Silicon), and Windows x64. The package targets the CPython stable ABI (`abi3`) with a minimum version of Python 3.9, so one wheel per platform covers Python 3.9+. - TypeScript/Node.js: pre-built napi-rs addons are shipped for Linux x64 (glibc), macOS arm64 (Apple Silicon), and Windows x64 (MSVC), on Node.js 18+. -- Go: validated on Linux and macOS with the default `target/release` FFI build. Windows is validated with the GNU Rust target (`x86_64-pc-windows-gnu`), because CGo links through MinGW rather than the MSVC import library used by the C++ SDK. - C++: validated with CMake builds on Linux, macOS, and Windows against the generated FFI library. ## Python SDK @@ -123,31 +121,6 @@ const eod = tdx.stockHistoryEOD('AAPL', '20240101', '20240315'); Requires Node.js 18+. See [sdks/typescript/README.md](typescript/README.md) for full documentation. -## Go SDK - -**Binding technology:** CGo wrapping the C FFI layer - -```bash -# Build the FFI library first -cargo build --release -p thetadatadx-ffi - -# Then use the Go module -go get github.com/userFRM/thetadatadx/sdks/go -``` - -```go -import tdx "github.com/userFRM/thetadatadx/sdks/go" - -creds, _ := tdx.NewCredentials("email", "password") -defer creds.Close() -client, _ := tdx.Connect(creds, tdx.ProductionConfig()) -defer client.Close() - -eod, _ := client.StockHistoryEOD("AAPL", "20240101", "20240315") -``` - -Requires Go 1.21+ and a C compiler (for CGo). See [sdks/go/README.md](go/README.md) for full documentation. - ## C++ SDK **Binding technology:** RAII C++ wrappers around the C FFI header (`thetadx.h`) @@ -176,7 +149,7 @@ Requires C++17, CMake 3.16+, and a C compiler. See [sdks/cpp/README.md](cpp/READ ## C FFI Layer -The raw C interface that the Go and C++ SDKs are built on. You can also call it directly from any language with C interop (Swift, Zig, Nim, etc.). +The raw C interface that the C++ SDK is built on. You can also call it directly from any language with C interop (Go via cgo, Swift, Zig, Nim, etc.). ```bash # Build as shared library (.so / .dylib) and static archive (.a) @@ -214,7 +187,4 @@ cd sdks/typescript && npm install && npm run build && cd ../.. # 4. Build the C++ SDK cmake -S sdks/cpp -B build/cpp cmake --build build/cpp --config Release --target thetadatadx_cpp - -# 5. Go SDK - no separate build step; CGo links at compile time -cd sdks/go/examples && go build . && cd ../../.. ``` diff --git a/sdks/cpp/include/fpss_event_structs.h.inc b/sdks/cpp/include/fpss_event_structs.h.inc index 7986d6fd..fc190618 100644 --- a/sdks/cpp/include/fpss_event_structs.h.inc +++ b/sdks/cpp/include/fpss_event_structs.h.inc @@ -1,8 +1,7 @@ /* @generated DO NOT EDIT — regenerated by build.rs from fpss_event_schema.toml */ /* C mirror of the Rust FFI FPSS event structs. #include'd from - * sdks/go/ffi_bridge.h (Go cgo) and sdks/cpp/include/thetadx.h - * (C++ SDK). Plain C typedefs — both surfaces see byte-identical - * layout. Do not hand-edit. */ + * sdks/cpp/include/thetadx.h (C++ SDK). Plain C typedefs. + * Do not hand-edit. */ #include diff --git a/sdks/go/README.md b/sdks/go/README.md deleted file mode 100644 index 107fc307..00000000 --- a/sdks/go/README.md +++ /dev/null @@ -1,400 +0,0 @@ -# thetadatadx (Go) - -Go SDK for ThetaData market data. CGo bindings over the `thetadatadx` Rust crate via the shared C FFI layer. - -Every call crosses the CGo boundary into compiled Rust: gRPC communication, protobuf parsing, zstd decompression, and TCP streaming run inside the `thetadatadx` crate. - -> **FLATFILES coverage:** the Go binding currently exposes the MDDS (historical) and FPSS (streaming) surfaces only. The third surface — FLATFILES whole-universe daily blobs — is shipped in the Rust core (v8.0.17+) and is being wired through the FFI layer (issue [#434](https://github.com/userFRM/ThetaDataDx/issues/434)) into Go under issue [#437](https://github.com/userFRM/ThetaDataDx/issues/437). See [`ROADMAP.md`](../../ROADMAP.md#flatfiles--binding-coverage) for the per-binding status. - -## Prerequisites - -- Go 1.21+ -- Rust toolchain (for building the FFI library) -- C compiler (for CGo) - -## Platform Support - -- Linux: CI-validated -- macOS: CI-validated -- Windows: CI-validated via a GNU-targeted Rust FFI build (`x86_64-pc-windows-gnu`) - -## Building - -First, build the Rust FFI library: - -```bash -# From the repository root -cargo build --release -p thetadatadx-ffi -``` - -This produces `target/release/libthetadatadx_ffi.so` (Linux) or `target/release/libthetadatadx_ffi.dylib` (macOS). - -On Windows, build the GNU-targeted FFI for the Go SDK: - -```powershell -rustup target add x86_64-pc-windows-gnu -cargo build --release --target x86_64-pc-windows-gnu -p thetadatadx-ffi -``` - -That produces the Windows Go-linkable artifacts under `target/x86_64-pc-windows-gnu/release/`. - -Then build or run your Go code: - -```bash -cd sdks/go/examples -go run main.go -``` - -## Quick Start - -```go -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, err := thetadatadx.CredentialsFromFile("creds.txt") - // Or inline: creds, err := thetadatadx.NewCredentials("user@example.com", "your-password") - if err != nil { - log.Fatal(err) - } - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - client, err := thetadatadx.Connect(creds, config) - if err != nil { - log.Fatal(err) - } - defer client.Close() - - eod, err := client.StockHistoryEOD("AAPL", "20240101", "20240301") - if err != nil { - log.Fatal(err) - } - for _, tick := range eod { - // Prices are pre-decoded to float64 -- no manual conversion needed - fmt.Printf("%d: O=%.2f H=%.2f L=%.2f C=%.2f\n", - tick.Date, tick.Open, tick.High, tick.Low, tick.Close) - } -} -``` - -## API - -### Credentials -- `NewCredentials(email, password)` - direct construction -- `CredentialsFromFile(path)` - load from creds.txt - -### Config -- `ProductionConfig()` - ThetaData NJ production servers -- `DevConfig()` - Dev FPSS servers (port 20200, infinite historical replay) -- `StageConfig()` - Stage FPSS servers (port 20100, testing, unstable) - -### Client (Historical Data) - -All data methods return typed Go structs (received as native `#[repr(C)]` struct arrays over FFI). - -```go -client, err := thetadatadx.Connect(creds, config) -defer client.Close() -``` - -#### Stock - List (2) - -| Method | Returns | Description | -|--------|---------|-------------| -| `StockListSymbols()` | `([]string, error)` | All stock symbols | -| `StockListDates(requestType, symbol)` | `([]string, error)` | Available dates for a request type | - -#### Stock - Snapshot (4) - -| Method | Returns | Description | -|--------|---------|-------------| -| `StockSnapshotOHLC(symbols)` | `([]OhlcTick, error)` | Latest OHLC | -| `StockSnapshotTrade(symbols)` | `([]TradeTick, error)` | Latest trade | -| `StockSnapshotQuote(symbols)` | `([]QuoteTick, error)` | Latest quote | -| `StockSnapshotMarketValue(symbols)` | `([]MarketValueTick, error)` | Latest market value | - -#### Stock - History (6) - -| Method | Returns | Description | -|--------|---------|-------------| -| `StockHistoryEOD(symbol, startDate, endDate)` | `([]EodTick, error)` | EOD data | -| `StockHistoryOHLC(symbol, date, interval)` | `([]OhlcTick, error)` | Intraday OHLC. `interval` accepts ms (`"60000"`) or shorthand (`"1m"`). | -| `StockHistoryOHLCRange(symbol, startDate, endDate, interval)` | `([]OhlcTick, error)` | OHLC over date range. `interval` accepts ms or shorthand. | -| `StockHistoryTrade(symbol, date)` | `([]TradeTick, error)` | All trades | -| `StockHistoryQuote(symbol, date, interval)` | `([]QuoteTick, error)` | NBBO quotes. `interval` accepts ms or shorthand. | -| `StockHistoryTradeQuote(symbol, date)` | `([]TradeQuoteTick, error)` | Trade+quote combined | - -#### Stock - At-Time (2) - -| Method | Returns | Description | -|--------|---------|-------------| -| `StockAtTimeTrade(symbol, startDate, endDate, time)` | `([]TradeTick, error)` | Trade at specific time across dates | -| `StockAtTimeQuote(symbol, startDate, endDate, time)` | `([]QuoteTick, error)` | Quote at specific time across dates | - -#### Option - List (5) - -| Method | Returns | Description | -|--------|---------|-------------| -| `OptionListSymbols()` | `([]string, error)` | Option underlyings | -| `OptionListDates(reqType, sym, expiration, strike, right)` | `([]string, error)` | Available dates | -| `OptionListExpirations(symbol)` | `([]string, error)` | Expiration dates | -| `OptionListStrikes(symbol, expiration)` | `([]string, error)` | Strike prices | -| `OptionListContracts(reqType, symbol, date)` | `([]Contract, error)` | All contracts | - -#### Option - Snapshot (10) - -| Method | Returns | Description | -|--------|---------|-------------| -| `OptionSnapshotOHLC(sym, expiration, strike, right)` | `([]OhlcTick, error)` | Latest OHLC | -| `OptionSnapshotTrade(sym, expiration, strike, right)` | `([]TradeTick, error)` | Latest trade | -| `OptionSnapshotQuote(sym, expiration, strike, right)` | `([]QuoteTick, error)` | Latest quote | -| `OptionSnapshotOpenInterest(sym, expiration, strike, right)` | `([]OpenInterestTick, error)` | Latest OI | -| `OptionSnapshotMarketValue(sym, expiration, strike, right)` | `([]MarketValueTick, error)` | Latest market value | -| `OptionSnapshotGreeksImpliedVolatility(sym, expiration, strike, right)` | `([]IVTick, error)` | IV snapshot | -| `OptionSnapshotGreeksAll(sym, expiration, strike, right)` | `([]GreeksTick, error)` | All Greeks snapshot | -| `OptionSnapshotGreeksFirstOrder(sym, expiration, strike, right)` | `([]GreeksTick, error)` | First-order Greeks | -| `OptionSnapshotGreeksSecondOrder(sym, expiration, strike, right)` | `([]GreeksTick, error)` | Second-order Greeks | -| `OptionSnapshotGreeksThirdOrder(sym, expiration, strike, right)` | `([]GreeksTick, error)` | Third-order Greeks | - -#### Option - History (6) - -| Method | Returns | Description | -|--------|---------|-------------| -| `OptionHistoryEOD(sym, expiration, strike, right, startDate, endDate)` | `([]EodTick, error)` | EOD data | -| `OptionHistoryOHLC(sym, expiration, strike, right, date, interval)` | `([]OhlcTick, error)` | OHLC bars | -| `OptionHistoryTrade(sym, expiration, strike, right, date)` | `([]TradeTick, error)` | Trades | -| `OptionHistoryQuote(sym, expiration, strike, right, date, interval)` | `([]QuoteTick, error)` | Quotes | -| `OptionHistoryTradeQuote(sym, expiration, strike, right, date)` | `([]TradeQuoteTick, error)` | Trade+quote combined | -| `OptionHistoryOpenInterest(sym, expiration, strike, right, date)` | `([]OpenInterestTick, error)` | Open interest history | - -#### Option - History Greeks (11) - -| Method | Returns | Description | -|--------|---------|-------------| -| `OptionHistoryGreeksEOD(sym, expiration, strike, right, startDate, endDate)` | `([]GreeksTick, error)` | EOD Greeks | -| `OptionHistoryGreeksEODWithOptions(sym, expiration, strike, right, startDate, endDate, opts)` | `([]GreeksTick, error)` | EOD Greeks with `EndpointRequestOptions`, including builder parameters such as `StrikeRange` | -| `OptionHistoryGreeksAll(sym, expiration, strike, right, date, interval)` | `([]GreeksTick, error)` | All Greeks history | -| `OptionHistoryTradeGreeksAll(sym, expiration, strike, right, date)` | `([]GreeksTick, error)` | Greeks on each trade | -| `OptionHistoryGreeksFirstOrder(sym, expiration, strike, right, date, interval)` | `([]GreeksTick, error)` | First-order Greeks history | -| `OptionHistoryTradeGreeksFirstOrder(sym, expiration, strike, right, date)` | `([]GreeksTick, error)` | First-order on each trade | -| `OptionHistoryGreeksSecondOrder(sym, expiration, strike, right, date, interval)` | `([]GreeksTick, error)` | Second-order Greeks history | -| `OptionHistoryTradeGreeksSecondOrder(sym, expiration, strike, right, date)` | `([]GreeksTick, error)` | Second-order on each trade | -| `OptionHistoryGreeksThirdOrder(sym, expiration, strike, right, date, interval)` | `([]GreeksTick, error)` | Third-order Greeks history | -| `OptionHistoryTradeGreeksThirdOrder(sym, expiration, strike, right, date)` | `([]GreeksTick, error)` | Third-order on each trade | -| `OptionHistoryGreeksImpliedVolatility(sym, expiration, strike, right, date, interval)` | `([]IVTick, error)` | IV history | -| `OptionHistoryTradeGreeksImpliedVolatility(sym, expiration, strike, right, date)` | `([]IVTick, error)` | IV on each trade | - -#### Option - At-Time (2) - -| Method | Returns | Description | -|--------|---------|-------------| -| `OptionAtTimeTrade(sym, expiration, strike, right, startDate, endDate, time)` | `([]TradeTick, error)` | Trade at specific time across dates | -| `OptionAtTimeQuote(sym, expiration, strike, right, startDate, endDate, time)` | `([]QuoteTick, error)` | Quote at specific time across dates | - -#### Index - List (2) - -| Method | Returns | Description | -|--------|---------|-------------| -| `IndexListSymbols()` | `([]string, error)` | Index symbols | -| `IndexListDates(symbol)` | `([]string, error)` | Available dates | - -#### Index - Snapshot (3) - -| Method | Returns | Description | -|--------|---------|-------------| -| `IndexSnapshotOHLC(symbols)` | `([]OhlcTick, error)` | Latest OHLC | -| `IndexSnapshotPrice(symbols)` | `([]PriceTick, error)` | Latest price | -| `IndexSnapshotMarketValue(symbols)` | `([]MarketValueTick, error)` | Latest market value | - -#### Index - History (3) - -| Method | Returns | Description | -|--------|---------|-------------| -| `IndexHistoryEOD(symbol, startDate, endDate)` | `([]EodTick, error)` | EOD data | -| `IndexHistoryOHLC(symbol, startDate, endDate, interval)` | `([]OhlcTick, error)` | OHLC bars | -| `IndexHistoryPrice(symbol, date, interval)` | `([]PriceTick, error)` | Price history | - -#### Index - At-Time (1) - -| Method | Returns | Description | -|--------|---------|-------------| -| `IndexAtTimePrice(symbol, startDate, endDate, time)` | `([]PriceTick, error)` | Price at specific time across dates | - -#### Calendar (3) - -| Method | Returns | Description | -|--------|---------|-------------| -| `CalendarOpenToday()` | `([]CalendarDay, error)` | Is the market open today? | -| `CalendarOnDate(date)` | `([]CalendarDay, error)` | Market schedule for date | -| `CalendarYear(year)` | `([]CalendarDay, error)` | Full calendar for year | - -#### Interest Rate (1) - -| Method | Returns | Description | -|--------|---------|-------------| -| `InterestRateHistoryEOD(symbol, startDate, endDate)` | `([]InterestRate, error)` | Interest rate EOD history | - -### Greeks (Standalone Functions) -- `AllGreeks(spot, strike, rate, divYield, tte, price, right)` - returns `(*Greeks, error)` with 22 fields. `right` accepts `"C"`/`"P"` or `"call"`/`"put"` case-insensitively. -- `ImpliedVolatility(spot, strike, rate, divYield, tte, price, right)` - returns `(iv, errorBound, err)`. Same `right` vocabulary. - -### Types - -#### Core Tick Types - -| Type | Fields | Description | -|------|--------|-------------| -| `EodTick` | MsOfDay, Open, High, Low, Close, Volume, Count, Bid, Ask, Date, **Expiration, Strike (float64), Right (string)** | End-of-day bar | -| `OhlcTick` | MsOfDay, Open, High, Low, Close, Volume, Count, Date, **Expiration, Strike (float64), Right (string)** | OHLC bar | -| `TradeTick` | MsOfDay, Sequence, Condition, Size, Exchange, Price (float64), ConditionFlags, PriceFlags, VolumeType, RecordsBack, Date, **Expiration, Strike (float64), Right (string)** | Individual trade | -| `QuoteTick` | MsOfDay, BidSize, BidExchange, Bid, BidCondition, AskSize, AskExchange, Ask, AskCondition, Midpoint, Date, **Expiration, Strike (float64), Right (string)** | NBBO quote | -| `TradeQuoteTick` | All TradeTick fields + QuoteMsOfDay, BidSize, BidExchange, Bid, BidCondition, AskSize, AskExchange, Ask, AskCondition, Date, **Expiration, Strike (float64), Right (string)** | Combined trade+quote | - -#### Derived Types - -| Type | Fields | Description | -|------|--------|-------------| -| `OpenInterestTick` | MsOfDay, OpenInterest, Date, **Expiration, Strike (float64), Right (string)** | Open interest data point | -| `MarketValueTick` | MsOfDay, MarketBid, MarketAsk, MarketPrice, Date, **Expiration, Strike (float64), Right (string)** | Market value data | -| `GreeksTick` | MsOfDay, ImpliedVolatility, Delta, Gamma, Theta, Vega, Rho, IVError, Vanna, Charm, Vomma, Veta, Speed, Zomma, Color, Ultima, D1, D2, DualDelta, DualGamma, Epsilon, Lambda, Vera, Date, **Expiration, Strike (float64), Right (string)** | Greeks time series | -| `IVTick` | MsOfDay, ImpliedVolatility, IVError, Date, **Expiration, Strike (float64), Right (string)** | Implied volatility data point | -| `PriceTick` | MsOfDay, Price (float64), Date | Price data point (indices) | -| `CalendarDay` | Date, IsOpen, OpenTime, CloseTime, Status | Market calendar day | -| `InterestRate` | Date, Rate | Interest rate data point | -| `Contract` | Symbol, Expiration, Strike (float64), Right (string) | Option contract identifier | - -**Contract identification fields** (bold above): `Expiration`, `Strike` (float64), `Right` (string) are populated by the server on wildcard queries (pass `"0"` for expiration/strike). On single-contract queries these fields are zero/empty. - -**Right field**: In the Go SDK, `Right` is a human-readable `string` (`"C"` for call, `"P"` for put, `""` if not set). On **input**, `right` parameters accept `"call"`, `"put"`, `"C"`, `"P"` (case-insensitive); the server output stays `"C"`/`"P"`. - -## FPSS Streaming - -Real-time market data via ThetaData's FPSS servers. Streaming uses a separate `FpssClient` struct (not the historical `Client`). Events are returned as typed Go structs -- no JSON parsing on the hot path. - -```go -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds, err := thetadatadx.CredentialsFromFile("creds.txt") - if err != nil { - log.Fatal(err) - } - defer creds.Close() - - config := thetadatadx.ProductionConfig() - defer config.Close() - - fpss, err := thetadatadx.NewFpssClient(creds, config) - if err != nil { - log.Fatal(err) - } - defer fpss.Close() - - // Subscribe to real-time quotes - reqID, err := fpss.SubscribeQuotes("AAPL") - if err != nil { - log.Fatal(err) - } - fmt.Printf("Subscribed (req_id=%d)\n", reqID) - - // Poll for events (returns typed *FpssEvent) - for { - event, err := fpss.NextEvent(5000) // 5s timeout - if err != nil { - log.Println("Error:", err) - break - } - if event == nil { - continue // timeout, no event - } - - switch event.Kind { - case thetadatadx.FpssQuoteEvent: - q := event.Quote - fmt.Printf("Quote: bid=%.4f ask=%.4f date=%d\n", q.Bid, q.Ask, q.Date) - case thetadatadx.FpssTradeEvent: - t := event.Trade - fmt.Printf("Trade: price=%.4f size=%d\n", t.Price, t.Size) - case thetadatadx.FpssControlEvent: - c := event.Control - fmt.Printf("Control: kind=%d detail=%s\n", c.Kind, c.Detail) - } - } - - fpss.Shutdown() -} -``` - -All prices in streaming events are `float64` -- decoded during parsing. No `PriceType` in the public API. - -### FpssClient API - -| Method | Signature | Description | -|--------|-----------|-------------| -| `NewFpssClient(creds, config)` | `(*FpssClient, error)` | Connect to FPSS streaming servers | -| `SubscribeQuotes(symbol)` | `(int, error)` | Subscribe to quotes | -| `SubscribeTrades(symbol)` | `(int, error)` | Subscribe to trades | -| `SubscribeOpenInterest(symbol)` | `(int, error)` | Subscribe to open interest | -| `SubscribeOptionQuotes(symbol, expiration, strike, right)` | `(int, error)` | Subscribe to option quotes | -| `SubscribeOptionTrades(symbol, expiration, strike, right)` | `(int, error)` | Subscribe to option trades | -| `SubscribeOptionOpenInterest(symbol, expiration, strike, right)` | `(int, error)` | Subscribe to option open interest | -| `SubscribeFullTrades(secType)` | `(int, error)` | Subscribe to all trades for a security type | -| `SubscribeFullOpenInterest(secType)` | `(int, error)` | Subscribe to all OI for a security type | -| `UnsubscribeQuotes(symbol)` | `(int, error)` | Unsubscribe from quotes | -| `UnsubscribeTrades(symbol)` | `(int, error)` | Unsubscribe from trades | -| `UnsubscribeOpenInterest(symbol)` | `(int, error)` | Unsubscribe from open interest | -| `UnsubscribeOptionQuotes(symbol, expiration, strike, right)` | `(int, error)` | Unsubscribe from option quotes | -| `UnsubscribeOptionTrades(symbol, expiration, strike, right)` | `(int, error)` | Unsubscribe from option trades | -| `UnsubscribeOptionOpenInterest(symbol, expiration, strike, right)` | `(int, error)` | Unsubscribe from option open interest | -| `UnsubscribeFullTrades(secType)` | `(int, error)` | Unsubscribe from all trades for a security type | -| `UnsubscribeFullOpenInterest(secType)` | `(int, error)` | Unsubscribe from all OI for a security type | -| `IsAuthenticated()` | `bool` | Check if FPSS client is authenticated | -| `ContractLookup(id)` | `(string, error)` | Look up contract by server-assigned ID | -| `ContractMap()` | `(map[int32]string, error)` | Get the full contract ID mapping | -| `ActiveSubscriptions()` | `([]Subscription, error)` | List currently active subscriptions | -| `NextEvent(timeoutMs)` | `(*FpssEvent, error)` | Poll next event as typed struct (nil on timeout) | -| `Reconnect()` | `error` | Reconnect streaming and restore subscriptions | -| `Shutdown()` | | Graceful shutdown of streaming | -| `Close()` | | Free the FPSS handle (call after Shutdown) | - -### FPSS Event Types - -| Type | Fields | Used when | -|------|--------|-----------| -| `FpssQuote` | ContractID, MsOfDay, BidSize, BidExchange, Bid (float64), BidCondition, AskSize, AskExchange, Ask (float64), AskCondition, Date, ReceivedAtNs | `Kind == FpssQuoteEvent` | -| `FpssTrade` | ContractID, MsOfDay, Sequence, ExtCondition1-4, Condition, Size, Exchange, Price (float64), ConditionFlags, PriceFlags, VolumeType, RecordsBack, Date, ReceivedAtNs | `Kind == FpssTradeEvent` | -| `FpssOpenInterest` | ContractID, MsOfDay, OpenInterest, Date, ReceivedAtNs | `Kind == FpssOpenInterestEvent` | -| `FpssOhlcvc` | ContractID, MsOfDay, Open/High/Low/Close (float64), Volume (int64), Count (int64), Date, ReceivedAtNs | `Kind == FpssOhlcvcEvent` | -| `FpssControl` | Kind (`FpssCtrl*` constants: 0..=6, 8..=12; 7 reserved), ID, Detail (string) | `Kind == FpssControlEvent` | -| `FpssRawData` | Code (uint8), Payload ([]byte) | `Kind == FpssRawDataEvent` | - -## Architecture - -``` -Go code - | (CGo FFI) - v -libthetadatadx_ffi.so / .a - | (Rust FFI crate) - v -thetadatadx Rust crate - | (tonic gRPC / tokio TCP) - v -ThetaData servers -``` diff --git a/sdks/go/client.go b/sdks/go/client.go deleted file mode 100644 index 5311d046..00000000 --- a/sdks/go/client.go +++ /dev/null @@ -1,62 +0,0 @@ -package thetadatadx - -/* -#include "ffi_bridge.h" -*/ -import "C" - -import ( - "fmt" - "runtime" -) - -// ── Client ── -// Lifecycle: intentionally hand-written (language-specific constructor semantics). - -// Client holds a connection to the ThetaData MDDS server. -type Client struct { - handle *C.TdxClient -} - -/* -// EndpointRequestOptions and EndpointOption helpers are generated in -// endpoint_options.go. -*/ - -// Connect authenticates and connects to ThetaData. -// -// Pins the goroutine to one OS thread across the cgo call + TLS error -// read because the FFI's last-error slot is a Rust thread_local; without -// the pin, Go's runtime could migrate the goroutine and the error read -// would see an empty slot on the wrong thread. -func Connect(creds *Credentials, config *Config) (*Client, error) { - if creds == nil || creds.handle == nil { - return nil, fmt.Errorf("thetadatadx: credentials handle is nil") - } - if config == nil || config.handle == nil { - return nil, fmt.Errorf("thetadatadx: config handle is nil") - } - runtime.LockOSThread() - defer runtime.UnlockOSThread() - h := C.tdx_client_connect(creds.handle, config.handle) - if h == nil { - return nil, fmt.Errorf("thetadatadx: %s", lastError()) - } - return &Client{handle: h}, nil -} - -// Close frees the client handle and disconnects. -func (c *Client) Close() { - if c.handle != nil { - C.tdx_client_free(c.handle) - c.handle = nil - } -} - -/* -// endpointRequestOptionsToC is generated in endpoint_options.go. -// Offline utilities are generated in utilities.go. -// Public tick types are generated in tick_structs.go. -// C-mirror FFI structs live in tick_ffi_mirrors.go. -// Array converters are generated in tick_converters.go. -*/ diff --git a/sdks/go/cmd/fpss_smoke/main.go b/sdks/go/cmd/fpss_smoke/main.go deleted file mode 100644 index c4f6605f..00000000 --- a/sdks/go/cmd/fpss_smoke/main.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "time" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -const ( - fpssSymbol = "AAPL" - fpssOptionSymbol = "SPY" - fpssOptionExpiration = "20260417" - fpssOptionStrike = "550" - fpssOptionRight = "C" -) - -func requireDataEvent(client *thetadatadx.FpssClient, timeout time.Duration) (int32, string, error) { - deadline := time.Now().Add(timeout) - lastKind := "none" - for time.Now().Before(deadline) { - event, err := client.NextEvent(500) - if err != nil { - return 0, lastKind, err - } - if event == nil { - continue - } - switch event.Kind { - case thetadatadx.FpssQuoteEvent: - lastKind = "quote" - if event.Quote != nil { - return event.Quote.ContractID, lastKind, nil - } - case thetadatadx.FpssTradeEvent: - lastKind = "trade" - if event.Trade != nil { - return event.Trade.ContractID, lastKind, nil - } - case thetadatadx.FpssOpenInterestEvent: - lastKind = "open_interest" - if event.OpenInterest != nil { - return event.OpenInterest.ContractID, lastKind, nil - } - case thetadatadx.FpssOhlcvcEvent: - lastKind = "ohlcvc" - if event.Ohlcvc != nil { - return event.Ohlcvc.ContractID, lastKind, nil - } - case thetadatadx.FpssControlEvent: - lastKind = "control" - case thetadatadx.FpssRawDataEvent: - lastKind = "raw_data" - } - } - return 0, lastKind, fmt.Errorf("timed out waiting for FPSS data event (last kind=%s)", lastKind) -} - -func requireDataEventWithRetry( - client *thetadatadx.FpssClient, - timeout time.Duration, - attempts int, -) (int32, string, error) { - var lastErr error - var lastKind string - for attempt := 1; attempt <= attempts; attempt++ { - contractID, kind, err := requireDataEvent(client, timeout) - if err == nil { - return contractID, kind, nil - } - lastErr = err - lastKind = kind - if attempt == attempts { - break - } - log.Printf("fpss smoke retry %d/%d after: %v", attempt, attempts-1, err) - if err := client.Reconnect(); err != nil { - return 0, lastKind, fmt.Errorf("reconnect during retry %d failed: %w", attempt, err) - } - time.Sleep(time.Second) - } - return 0, lastKind, lastErr -} - -func subscriptionsSnapshot(client *thetadatadx.FpssClient) (map[string]struct{}, error) { - subs, err := client.ActiveSubscriptions() - if err != nil { - return nil, err - } - out := make(map[string]struct{}, len(subs)) - for _, sub := range subs { - out[sub.Kind+"|"+sub.Contract] = struct{}{} - } - return out, nil -} - -func sameSubscriptions(a, b map[string]struct{}) bool { - if len(a) != len(b) { - return false - } - for key := range a { - if _, ok := b[key]; !ok { - return false - } - } - return true -} - -func main() { - credsPath := "../../creds.txt" - if len(os.Args) > 1 { - credsPath = os.Args[1] - } - - creds, err := thetadatadx.CredentialsFromFile(credsPath) - if err != nil { - log.Fatalf("creds: %v", err) - } - defer creds.Close() - - cfg := thetadatadx.DevConfig() - cfg.SetReconnectPolicy(1) - cfg.SetDeriveOhlcvc(false) - defer cfg.Close() - - fpss, err := thetadatadx.NewFpssClient(creds, cfg) - if err != nil { - log.Fatalf("connect: %v", err) - } - defer fpss.Close() - defer fpss.Shutdown() - - if _, err := fpss.SubscribeQuotes(fpssSymbol); err != nil { - log.Fatalf("subscribe quotes: %v", err) - } - if _, err := fpss.SubscribeTrades(fpssSymbol); err != nil { - log.Fatalf("subscribe trades: %v", err) - } - if _, err := fpss.SubscribeOptionQuotes( - fpssOptionSymbol, fpssOptionExpiration, fpssOptionStrike, fpssOptionRight, - ); err != nil { - log.Fatalf("subscribe option quotes: %v", err) - } - - expected, err := subscriptionsSnapshot(fpss) - if err != nil { - log.Fatalf("active subscriptions: %v", err) - } - if len(expected) < 3 { - log.Fatalf("expected at least 3 active subscriptions, got %v", expected) - } - - contractID, kind, err := requireDataEventWithRetry(fpss, 20*time.Second, 3) - if err != nil { - log.Fatal(err) - } - if contractID != 0 { - contract, err := fpss.ContractLookup(int(contractID)) - if err != nil { - log.Fatalf("contract lookup after %s event: %v", kind, err) - } - if contract == "" { - log.Fatalf("contract lookup after %s event returned empty string", kind) - } - } - - contractMap, err := fpss.ContractMap() - if err != nil { - log.Fatalf("contract map: %v", err) - } - if len(contractMap) == 0 { - log.Fatal("contract map returned no entries after first data event") - } - - if err := fpss.Reconnect(); err != nil { - log.Fatalf("reconnect: %v", err) - } - - after, err := subscriptionsSnapshot(fpss) - if err != nil { - log.Fatalf("active subscriptions after reconnect: %v", err) - } - if !sameSubscriptions(expected, after) { - log.Fatalf("subscriptions drifted across reconnect: expected %v got %v", expected, after) - } - - contractID, kind, err = requireDataEventWithRetry(fpss, 20*time.Second, 3) - if err != nil { - log.Fatal(err) - } - if contractID != 0 { - contract, err := fpss.ContractLookup(int(contractID)) - if err != nil { - log.Fatalf("contract lookup after reconnect %s event: %v", kind, err) - } - if contract == "" { - log.Fatalf("contract lookup after reconnect %s event returned empty string", kind) - } - } - - fmt.Printf( - "go fpss smoke: ok (symbol=%s, option=%s %s %s %s)\n", - fpssSymbol, - fpssOptionSymbol, - fpssOptionExpiration, - fpssOptionStrike, - fpssOptionRight, - ) -} diff --git a/sdks/go/cmd/live_smoke/main.go b/sdks/go/cmd/live_smoke/main.go deleted file mode 100644 index 94718935..00000000 --- a/sdks/go/cmd/live_smoke/main.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -const ( - histSymbol = "AAPL" - histStart = "20260401" - histEnd = "20260402" - atTime = "09:30" - atTimeLegacy = "34200000" -) - -func main() { - credsPath := "../../creds.txt" - if len(os.Args) > 1 { - credsPath = os.Args[1] - } - - creds, err := thetadatadx.CredentialsFromFile(credsPath) - if err != nil { - log.Fatalf("creds: %v", err) - } - defer creds.Close() - - cfg := thetadatadx.ProductionConfig() - defer cfg.Close() - - client, err := thetadatadx.Connect(creds, cfg) - if err != nil { - log.Fatalf("connect: %v", err) - } - defer client.Close() - - days, err := client.CalendarOpenToday() - if err != nil { - log.Fatalf("calendar_open_today: %v", err) - } - if len(days) == 0 { - log.Fatal("calendar_open_today returned no rows") - } - - eod, err := client.StockHistoryEOD(histSymbol, histStart, histEnd) - if err != nil { - log.Fatalf("stock_history_eod: %v", err) - } - if len(eod) == 0 { - log.Fatal("stock_history_eod returned no rows") - } - - atRows, err := client.StockAtTimeTrade(histSymbol, histStart, histEnd, atTime) - if err != nil { - log.Fatalf("stock_at_time_trade formatted time: %v", err) - } - if len(atRows) == 0 { - log.Fatal("stock_at_time_trade formatted time returned no rows") - } - - legacyRows, err := client.StockAtTimeTrade(histSymbol, histStart, histEnd, atTimeLegacy) - if err != nil { - log.Fatalf("stock_at_time_trade legacy ms: %v", err) - } - if len(legacyRows) == 0 { - log.Fatal("stock_at_time_trade legacy ms returned no rows") - } - - fmt.Println("go live smoke: ok") -} diff --git a/sdks/go/cmd/validate/main.go b/sdks/go/cmd/validate/main.go deleted file mode 100644 index 3af0c4b9..00000000 --- a/sdks/go/cmd/validate/main.go +++ /dev/null @@ -1,80 +0,0 @@ -// Release validation for the Go SDK. -// -// Calls all non-stream endpoints to prove the CGo FFI bridge works end-to-end. -// Run via: -// -// cd sdks/go && LD_LIBRARY_PATH=../../target/release go run ./cmd/validate -// -// On Windows, build the GNU-targeted FFI first and make that directory available -// to CGo and the runtime loader: -// -// rustup target add x86_64-pc-windows-gnu -// cargo build --release --target x86_64-pc-windows-gnu -p thetadatadx-ffi -// cd sdks/go && set CGO_LDFLAGS=-L..\\..\\target\\x86_64-pc-windows-gnu\\release && go run ./cmd/validate -// -// Expects creds.txt at the repository root (two lines: email, password). - -package main - -import ( - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - creds := "../../creds.txt" - if len(os.Args) > 1 { - creds = os.Args[1] - } - - c, err := thetadatadx.CredentialsFromFile(creds) - if err != nil { - log.Fatalf("creds: %v", err) - } - - cfg := thetadatadx.ProductionConfig() - - client, err := thetadatadx.Connect(c, cfg) - if err != nil { - log.Fatalf("connect: %v", err) - } - - pass, skip, fail, records := thetadatadx.ValidateAllEndpoints(client) - fmt.Printf("\nGo: %d PASS, %d SKIP, %d FAIL\n", pass, skip, fail) - fmt.Printf("COUNTS:%d:%d:%d\n", pass, skip, fail) - - // Write JSON artifact for the cross-language agreement check. - // Path is relative to this binary's CWD, which is sdks/go when invoked - // via `go run`. Repo root is two levels up. - artifactPath := filepath.Join("..", "..", "artifacts", "validator_go.json") - if err := os.MkdirAll(filepath.Dir(artifactPath), 0o755); err != nil { - log.Printf("warning: mkdir artifacts: %v", err) - } - artifact := map[string]interface{}{ - "lang": "go", - "records": records, - } - data, err := json.MarshalIndent(artifact, "", " ") - if err != nil { - log.Printf("warning: marshal artifact: %v", err) - } else if err := os.WriteFile(artifactPath, data, 0o644); err != nil { - log.Printf("warning: write artifact: %v", err) - } else { - fmt.Printf("artifact: %s\n", artifactPath) - } - - // W3: every timeout is now cancelled by the SDK before returning, so there - // are no leaked goroutines holding the client handle. Close unconditionally. - client.Close() - cfg.Close() - c.Close() - if fail > 0 { - os.Exit(1) - } - os.Exit(0) -} diff --git a/sdks/go/dropped_events_test.go b/sdks/go/dropped_events_test.go deleted file mode 100644 index 8362254c..00000000 --- a/sdks/go/dropped_events_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package thetadatadx - -import ( - "os" - "testing" -) - -// TestDroppedEventsCallableOnNilClient pins the contract from audit -// finding A-04: `FpssClient.DroppedEvents()` must be safe to call -// before / after `Close()` and on a nil receiver. Parity with the -// Python / TypeScript / C++ getters, all of which return 0 instead -// of panicking on an uninitialised handle. This is the defensive -// path — Go consumers often defer `client.Close()` then still touch -// metrics in a post-shutdown cleanup hook, and a panic there would -// mask the real failure. -func TestDroppedEventsCallableOnNilClient(t *testing.T) { - var f *FpssClient - if got := f.DroppedEvents(); got != 0 { - t.Errorf("nil FpssClient.DroppedEvents() = %d, want 0", got) - } - - empty := &FpssClient{handle: nil} - if got := empty.DroppedEvents(); got != 0 { - t.Errorf("closed FpssClient.DroppedEvents() = %d, want 0", got) - } -} - -// TestDroppedEventsSurvivesReconnect is the live-credential sibling of -// the Python / TypeScript dropped_events tests. Gated on -// THETADX_TEST_CREDS because `NewFpssClient` needs a real FPSS -// handshake. Pins the A-02/A-04 contract: the counter stays readable -// across reconnect and is monotonically non-decreasing. -func TestDroppedEventsSurvivesReconnect(t *testing.T) { - credsPath := os.Getenv("THETADX_TEST_CREDS") - if credsPath == "" { - t.Skip("set THETADX_TEST_CREDS=path/to/creds.txt to enable this live test") - } - - creds, err := CredentialsFromFile(credsPath) - if err != nil { - t.Fatalf("creds: %v", err) - } - defer creds.Close() - - cfg := ProductionConfig() - defer cfg.Close() - - client, err := NewFpssClient(creds, cfg) - if err != nil { - t.Fatalf("connect: %v", err) - } - defer client.Close() - - pre := client.DroppedEvents() - - if err := client.Reconnect(); err != nil { - t.Fatalf("reconnect: %v", err) - } - post := client.DroppedEvents() - - // Monotonically non-decreasing across reconnect. A reset would - // mean the closure-local counter regression was reintroduced. - if post < pre { - t.Errorf("DroppedEvents() reset across reconnect: pre=%d post=%d", pre, post) - } -} diff --git a/sdks/go/endpoint_ffi_sizes_generated.go b/sdks/go/endpoint_ffi_sizes_generated.go deleted file mode 100644 index d7f1c5a5..00000000 --- a/sdks/go/endpoint_ffi_sizes_generated.go +++ /dev/null @@ -1,5 +0,0 @@ -// Code generated by build.rs from endpoint_surface.toml; DO NOT EDIT. - -package thetadatadx - -const TdxEndpointRequestOptionsExpectedSize uintptr = 192 diff --git a/sdks/go/endpoint_options.go b/sdks/go/endpoint_options.go deleted file mode 100644 index 7a28f42f..00000000 --- a/sdks/go/endpoint_options.go +++ /dev/null @@ -1,379 +0,0 @@ -// Code generated by build.rs from endpoint_surface.toml; DO NOT EDIT. - -package thetadatadx - -/* -#include "ffi_bridge.h" -*/ -import "C" - -import ( - "time" - "unsafe" -) - -// EndpointRequestOptions contains the shared optional request fields projected from endpoint_surface.toml. -// TimeoutMs is the cross-cutting per-call deadline (W3); see WithTimeoutMs. -type EndpointRequestOptions struct { - // Venue/exchange filter. Accepted values: `nqb`, `utp_cta`. - Venue *string - // Minimum time filter - MinTime *string - // Interval preset or millisecond string. Defaults to `1s` when omitted — matching the upstream ThetaData Python library. Accepted values: `tick`, `10ms`, `100ms`, `500ms`, `1s`, `5s`, `10s`, `15s`, `30s`, `1m`, `5m`, `10m`, `15m`, `30m`, `1h`. - Interval *string - // Start time filter - StartTime *string - // End time filter - EndTime *string - // Start date YYYYMMDD - StartDate *string - // End date YYYYMMDD - EndDate *string - // When true, quotes whose timestamp equals the trade timestamp are excluded; only quotes strictly before the trade are paired. - Exclusive *bool - // Strike price in dollars as a string (e.g. 500 or 17.5). Use `*` for wildcard selection. - Strike *string - // Option side. Accepted values: `call`, `put`, `both`. - Right *string - // Maximum days to expiration - MaxDTE *int32 - // Strike range filter - StrikeRange *int32 - // Annual dividend - AnnualDividend *float64 - // Risk-free-rate source used in the Greeks calculation. Accepted values: `sofr`, `treasury_m1`, `treasury_m3`, `treasury_m6`, `treasury_y1`, `treasury_y2`, `treasury_y3`, `treasury_y5`, `treasury_y7`, `treasury_y10`, `treasury_y20`, `treasury_y30`. - RateType *string - // Rate value - RateValue *float64 - // Stock price - StockPrice *float64 - // Greeks model version. Accepted values: `latest`, `1`. - Version *string - // When true, calculate Greeks against the option market value (mid-price) instead of the NBBO bid/ask pair. - UseMarketValue *bool - // When true, use the NBBO-derived underlyer price as the Greeks input instead of the last trade. - UnderlyerUseNBBO *bool - // TimeoutMs is the per-call deadline in milliseconds. When set, on - // expiry the in-flight gRPC call is cancelled and the returned error - // carries "Request deadline exceeded". The Client handle stays usable - // for subsequent calls. See WithTimeoutMs / WithDeadline. - TimeoutMs *uint64 -} - -// EndpointOption applies one optional request field to an endpoint request. -type EndpointOption func(*EndpointRequestOptions) - -func collectEndpointRequestOptions(opts []EndpointOption) *EndpointRequestOptions { - if len(opts) == 0 { - return nil - } - options := &EndpointRequestOptions{} - for _, opt := range opts { - if opt != nil { - opt(options) - } - } - return options -} - -// WithVenue sets venue. -func WithVenue(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.Venue = &valueCopy - } -} - -// WithMinTime sets min_time. -func WithMinTime(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.MinTime = &valueCopy - } -} - -// WithInterval sets interval. -func WithInterval(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.Interval = &valueCopy - } -} - -// WithStartTime sets start_time. -func WithStartTime(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.StartTime = &valueCopy - } -} - -// WithEndTime sets end_time. -func WithEndTime(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.EndTime = &valueCopy - } -} - -// WithStartDate sets start_date. -func WithStartDate(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.StartDate = &valueCopy - } -} - -// WithEndDate sets end_date. -func WithEndDate(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.EndDate = &valueCopy - } -} - -// WithExclusive sets exclusive. -func WithExclusive(value bool) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.Exclusive = &valueCopy - } -} - -// WithStrike sets strike. -func WithStrike(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.Strike = &valueCopy - } -} - -// WithRight sets right. -func WithRight(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.Right = &valueCopy - } -} - -// WithMaxDTE sets max_dte. -func WithMaxDTE(value int32) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.MaxDTE = &valueCopy - } -} - -// WithStrikeRange sets strike_range. -func WithStrikeRange(value int32) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.StrikeRange = &valueCopy - } -} - -// WithAnnualDividend sets annual_dividend. -func WithAnnualDividend(value float64) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.AnnualDividend = &valueCopy - } -} - -// WithRateType sets rate_type. -func WithRateType(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.RateType = &valueCopy - } -} - -// WithRateValue sets rate_value. -func WithRateValue(value float64) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.RateValue = &valueCopy - } -} - -// WithStockPrice sets stock_price. -func WithStockPrice(value float64) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.StockPrice = &valueCopy - } -} - -// WithVersion sets version. -func WithVersion(value string) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.Version = &valueCopy - } -} - -// WithUseMarketValue sets use_market_value. -func WithUseMarketValue(value bool) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.UseMarketValue = &valueCopy - } -} - -// WithUnderlyerUseNBBO sets underlyer_use_nbbo. -func WithUnderlyerUseNBBO(value bool) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.UnderlyerUseNBBO = &valueCopy - } -} - -// WithTimeoutMs sets the per-call deadline in milliseconds. On expiry -// the in-flight gRPC call is cancelled (the underlying stream is -// dropped, freeing the request semaphore) and the call returns an -// error containing "Request deadline exceeded". -func WithTimeoutMs(value uint64) EndpointOption { - return func(options *EndpointRequestOptions) { - valueCopy := value - options.TimeoutMs = &valueCopy - } -} - -// WithDeadline sets the per-call deadline as a time.Duration; see WithTimeoutMs. -// A negative duration means "deadline already in the past" and clamps -// to 1 ms (immediate expiration). Without the clamp, the conversion -// to uint64 would silently wrap to a multi-century value, the opposite -// of the caller's intent. -func WithDeadline(value time.Duration) EndpointOption { - var ms uint64 - if value < 0 { - ms = 1 - } else { - ms = uint64(value / time.Millisecond) - } - return func(options *EndpointRequestOptions) { - msCopy := ms - options.TimeoutMs = &msCopy - } -} - -func endpointRequestOptionsToC(opts *EndpointRequestOptions) (*C.TdxEndpointRequestOptions, func()) { - if opts == nil { - return nil, func() {} - } - - cOpts := &C.TdxEndpointRequestOptions{} - - var allocations []unsafe.Pointer - free := func() { - for _, allocation := range allocations { - C.free(allocation) - } - } - - if opts.Venue != nil { - value := C.CString(*opts.Venue) - cOpts.venue = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.MinTime != nil { - value := C.CString(*opts.MinTime) - cOpts.min_time = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.Interval != nil { - value := C.CString(*opts.Interval) - cOpts.interval = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.StartTime != nil { - value := C.CString(*opts.StartTime) - cOpts.start_time = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.EndTime != nil { - value := C.CString(*opts.EndTime) - cOpts.end_time = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.StartDate != nil { - value := C.CString(*opts.StartDate) - cOpts.start_date = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.EndDate != nil { - value := C.CString(*opts.EndDate) - cOpts.end_date = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.Exclusive != nil { - if *opts.Exclusive { - cOpts.exclusive = 1 - } else { - cOpts.exclusive = 0 - } - cOpts.has_exclusive = 1 - } - if opts.Strike != nil { - value := C.CString(*opts.Strike) - cOpts.strike = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.Right != nil { - value := C.CString(*opts.Right) - cOpts.right = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.MaxDTE != nil { - cOpts.max_dte = C.int32_t(*opts.MaxDTE) - cOpts.has_max_dte = 1 - } - if opts.StrikeRange != nil { - cOpts.strike_range = C.int32_t(*opts.StrikeRange) - cOpts.has_strike_range = 1 - } - if opts.AnnualDividend != nil { - cOpts.annual_dividend = C.double(*opts.AnnualDividend) - cOpts.has_annual_dividend = 1 - } - if opts.RateType != nil { - value := C.CString(*opts.RateType) - cOpts.rate_type = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.RateValue != nil { - cOpts.rate_value = C.double(*opts.RateValue) - cOpts.has_rate_value = 1 - } - if opts.StockPrice != nil { - cOpts.stock_price = C.double(*opts.StockPrice) - cOpts.has_stock_price = 1 - } - if opts.Version != nil { - value := C.CString(*opts.Version) - cOpts.version = value - allocations = append(allocations, unsafe.Pointer(value)) - } - if opts.UseMarketValue != nil { - if *opts.UseMarketValue { - cOpts.use_market_value = 1 - } else { - cOpts.use_market_value = 0 - } - cOpts.has_use_market_value = 1 - } - if opts.UnderlyerUseNBBO != nil { - if *opts.UnderlyerUseNBBO { - cOpts.underlyer_use_nbbo = 1 - } else { - cOpts.underlyer_use_nbbo = 0 - } - cOpts.has_underlyer_use_nbbo = 1 - } - if opts.TimeoutMs != nil { - cOpts.timeout_ms = C.uint64_t(*opts.TimeoutMs) - cOpts.has_timeout_ms = 1 - } - - return cOpts, free -} diff --git a/sdks/go/endpoint_request_options.h.inc b/sdks/go/endpoint_request_options.h.inc deleted file mode 100644 index 56c311b8..00000000 --- a/sdks/go/endpoint_request_options.h.inc +++ /dev/null @@ -1,33 +0,0 @@ -/* @generated DO NOT EDIT — regenerated by build.rs from endpoint_surface.toml */ -typedef struct { - const char* venue; - const char* min_time; - const char* interval; - const char* start_time; - const char* end_time; - const char* start_date; - const char* end_date; - int32_t exclusive; - int32_t has_exclusive; - const char* strike; - const char* right; - int32_t max_dte; - int32_t has_max_dte; - int32_t strike_range; - int32_t has_strike_range; - double annual_dividend; - int32_t has_annual_dividend; - const char* rate_type; - double rate_value; - int32_t has_rate_value; - double stock_price; - int32_t has_stock_price; - const char* version; - int32_t use_market_value; - int32_t has_use_market_value; - int32_t underlyer_use_nbbo; - int32_t has_underlyer_use_nbbo; - /* Per-call deadline (W3). Set has_timeout_ms = 1 to apply. */ - uint64_t timeout_ms; - int32_t has_timeout_ms; -} TdxEndpointRequestOptions; diff --git a/sdks/go/endpoint_with_options.h.inc b/sdks/go/endpoint_with_options.h.inc deleted file mode 100644 index 2e42ea4f..00000000 --- a/sdks/go/endpoint_with_options.h.inc +++ /dev/null @@ -1,62 +0,0 @@ -/* @generated DO NOT EDIT — regenerated by build.rs from endpoint_surface.toml */ -extern TdxStringArray tdx_stock_list_symbols_with_options(const TdxClient* client, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_stock_list_dates_with_options(const TdxClient* client, const char* request_type, const char* symbol, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_snapshot_ohlc_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_snapshot_trade_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_snapshot_quote_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_snapshot_market_value_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_history_eod_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_history_ohlc_with_options(const TdxClient* client, const char* symbol, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_history_trade_with_options(const TdxClient* client, const char* symbol, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_history_quote_with_options(const TdxClient* client, const char* symbol, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_history_trade_quote_with_options(const TdxClient* client, const char* symbol, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_at_time_trade_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* time_of_day, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_at_time_quote_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* time_of_day, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_option_list_symbols_with_options(const TdxClient* client, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_option_list_dates_with_options(const TdxClient* client, const char* request_type, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_option_list_expirations_with_options(const TdxClient* client, const char* symbol, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_option_list_strikes_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxOptionContractArray tdx_option_list_contracts_with_options(const TdxClient* client, const char* request_type, const char* symbol, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_ohlc_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_trade_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_quote_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_open_interest_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_market_value_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_greeks_implied_volatility_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_greeks_all_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_greeks_first_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_greeks_second_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_snapshot_greeks_third_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_eod_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_ohlc_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_quote_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_quote_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_open_interest_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_greeks_eod_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_greeks_all_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_greeks_all_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_greeks_first_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_greeks_first_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_greeks_second_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_greeks_second_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_greeks_third_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_greeks_third_order_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_greeks_implied_volatility_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_history_trade_greeks_implied_volatility_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_at_time_trade_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* start_date, const char* end_date, const char* time_of_day, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_option_at_time_quote_with_options(const TdxClient* client, const char* symbol, const char* expiration, const char* start_date, const char* end_date, const char* time_of_day, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_index_list_symbols_with_options(const TdxClient* client, const TdxEndpointRequestOptions* options); -extern TdxStringArray tdx_index_list_dates_with_options(const TdxClient* client, const char* symbol, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_snapshot_ohlc_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_snapshot_price_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_snapshot_market_value_with_options(const TdxClient* client, const char* const* symbols, size_t symbols_len, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_history_eod_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_history_ohlc_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_history_price_with_options(const TdxClient* client, const char* symbol, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_index_at_time_price_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* time_of_day, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_calendar_open_today_with_options(const TdxClient* client, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_calendar_on_date_with_options(const TdxClient* client, const char* date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_calendar_year_with_options(const TdxClient* client, const char* year, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_interest_rate_history_eod_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); -extern TdxTickArray tdx_stock_history_ohlc_range_with_options(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const TdxEndpointRequestOptions* options); diff --git a/sdks/go/examples/main.go b/sdks/go/examples/main.go deleted file mode 100644 index 0223af17..00000000 --- a/sdks/go/examples/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "fmt" - "log" - - thetadatadx "github.com/userFRM/thetadatadx/sdks/go" -) - -func main() { - // Load credentials from creds.txt (line 1 = email, line 2 = password) - creds, err := thetadatadx.CredentialsFromFile("creds.txt") - if err != nil { - log.Fatalf("Failed to load credentials: %v", err) - } - defer creds.Close() - - // Connect to ThetaData production servers - config := thetadatadx.ProductionConfig() - defer config.Close() - - client, err := thetadatadx.Connect(creds, config) - if err != nil { - log.Fatalf("Failed to connect: %v", err) - } - defer client.Close() - - // Fetch end-of-day data - eod, err := client.StockHistoryEOD("AAPL", "20240101", "20240301") - if err != nil { - log.Fatalf("Failed to fetch EOD: %v", err) - } - fmt.Printf("Got %d EOD ticks for AAPL\n", len(eod)) - for _, tick := range eod { - fmt.Printf(" %d: O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", - tick.Date, tick.Open, tick.High, tick.Low, tick.Close, tick.Volume) - } - - // Greeks calculator (no server connection needed) - greeks, err := thetadatadx.AllGreeks(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, "C") - if err != nil { - log.Fatalf("Failed to compute greeks: %v", err) - } - fmt.Printf("\nGreeks: IV=%.4f Delta=%.4f Gamma=%.6f Theta=%.4f\n", - greeks.IV, greeks.Delta, greeks.Gamma, greeks.Theta) - - // Implied volatility - iv, ivErr, err := thetadatadx.ImpliedVolatility(450.0, 455.0, 0.05, 0.015, 30.0/365.0, 8.50, "C") - if err != nil { - log.Fatalf("Failed to compute IV: %v", err) - } - fmt.Printf("IV=%.6f (error=%.2e)\n", iv, ivErr) -} diff --git a/sdks/go/ffi_bridge.h b/sdks/go/ffi_bridge.h deleted file mode 100644 index 33406855..00000000 --- a/sdks/go/ffi_bridge.h +++ /dev/null @@ -1,259 +0,0 @@ -#ifndef THETADATADX_GO_FFI_BRIDGE_H -#define THETADATADX_GO_FFI_BRIDGE_H - -#include -#include -#include - -/* Opaque handles */ -typedef struct TdxCredentials TdxCredentials; -typedef struct TdxClient TdxClient; -typedef struct TdxConfig TdxConfig; -typedef struct TdxFpssHandle TdxFpssHandle; -typedef struct TdxUnified TdxUnified; - -/* Generic array layouts used by the Go bindings. */ -typedef struct { const void* data; size_t len; } TdxTickArray; -typedef struct { const void* data; size_t len; } TdxStringArray; -typedef struct { const void* data; size_t len; } TdxOptionContractArray; - -/* Generated request-options bridge shared with Rust FFI. */ -#include "endpoint_request_options.h.inc" - -/* Error */ -extern const char* tdx_last_error(void); -extern void tdx_clear_error(void); -extern void tdx_string_free(char* s); - -/* Credentials */ -extern TdxCredentials* tdx_credentials_new(const char* email, const char* password); -extern TdxCredentials* tdx_credentials_from_file(const char* path); -extern void tdx_credentials_free(TdxCredentials* creds); - -/* Config */ -extern TdxConfig* tdx_config_production(void); -extern TdxConfig* tdx_config_dev(void); -extern TdxConfig* tdx_config_stage(void); -extern void tdx_config_free(TdxConfig* config); -extern void tdx_config_set_reconnect_policy(TdxConfig* config, int policy); -extern void tdx_config_set_flush_mode(TdxConfig* config, int mode); -extern void tdx_config_set_derive_ohlcvc(TdxConfig* config, int enabled); - -/* Client */ -extern TdxClient* tdx_client_connect(const TdxCredentials* creds, const TdxConfig* config); -extern void tdx_client_free(TdxClient* client); - -/* Free functions */ -extern void tdx_eod_tick_array_free(TdxTickArray arr); -extern void tdx_ohlc_tick_array_free(TdxTickArray arr); -extern void tdx_trade_tick_array_free(TdxTickArray arr); -extern void tdx_quote_tick_array_free(TdxTickArray arr); -extern void tdx_greeks_all_tick_array_free(TdxTickArray arr); -extern void tdx_greeks_first_order_tick_array_free(TdxTickArray arr); -extern void tdx_greeks_second_order_tick_array_free(TdxTickArray arr); -extern void tdx_greeks_third_order_tick_array_free(TdxTickArray arr); -extern void tdx_iv_tick_array_free(TdxTickArray arr); -extern void tdx_price_tick_array_free(TdxTickArray arr); -extern void tdx_open_interest_tick_array_free(TdxTickArray arr); -extern void tdx_market_value_tick_array_free(TdxTickArray arr); -extern void tdx_calendar_day_array_free(TdxTickArray arr); -extern void tdx_interest_rate_tick_array_free(TdxTickArray arr); -extern void tdx_trade_quote_tick_array_free(TdxTickArray arr); -extern void tdx_option_contract_array_free(TdxOptionContractArray arr); -extern void tdx_string_array_free(TdxStringArray arr); - -/* Historical endpoints */ -extern TdxStringArray tdx_stock_list_symbols(const TdxClient* client); -extern TdxStringArray tdx_stock_list_dates(const TdxClient* client, const char* request_type, const char* symbol); -extern TdxTickArray tdx_stock_snapshot_ohlc(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_stock_snapshot_trade(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_stock_snapshot_quote(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_stock_snapshot_market_value(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_stock_history_eod(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date); -extern TdxTickArray tdx_stock_history_ohlc(const TdxClient* client, const char* symbol, const char* date, const char* interval); -extern TdxTickArray tdx_stock_history_ohlc_range(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* interval); -extern TdxTickArray tdx_stock_history_trade(const TdxClient* client, const char* symbol, const char* date); -extern TdxTickArray tdx_stock_history_quote(const TdxClient* client, const char* symbol, const char* date, const char* interval); -extern TdxTickArray tdx_stock_history_trade_quote(const TdxClient* client, const char* symbol, const char* date); -extern TdxTickArray tdx_stock_at_time_trade(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* time_of_day); -extern TdxTickArray tdx_stock_at_time_quote(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* time_of_day); -extern TdxStringArray tdx_option_list_symbols(const TdxClient* client); -extern TdxStringArray tdx_option_list_dates(const TdxClient* client, const char* request_type, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxStringArray tdx_option_list_expirations(const TdxClient* client, const char* symbol); -extern TdxStringArray tdx_option_list_strikes(const TdxClient* client, const char* symbol, const char* expiration); -extern TdxOptionContractArray tdx_option_list_contracts(const TdxClient* client, const char* request_type, const char* symbol, const char* date); -extern TdxTickArray tdx_option_snapshot_ohlc(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_trade(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_quote(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_open_interest(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_market_value(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_greeks_implied_volatility(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_greeks_all(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_greeks_first_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_greeks_second_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_snapshot_greeks_third_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxTickArray tdx_option_history_eod(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* start_date, const char* end_date); -extern TdxTickArray tdx_option_history_ohlc(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_quote(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade_quote(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_open_interest(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_greeks_eod(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* start_date, const char* end_date); -extern TdxTickArray tdx_option_history_greeks_all(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade_greeks_all(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_greeks_first_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade_greeks_first_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_greeks_second_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade_greeks_second_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_greeks_third_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade_greeks_third_order(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_history_greeks_implied_volatility(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date, const char* interval); -extern TdxTickArray tdx_option_history_trade_greeks_implied_volatility(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* date); -extern TdxTickArray tdx_option_at_time_trade(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* start_date, const char* end_date, const char* time_of_day); -extern TdxTickArray tdx_option_at_time_quote(const TdxClient* client, const char* symbol, const char* expiration, const char* strike, const char* right, const char* start_date, const char* end_date, const char* time_of_day); -extern TdxStringArray tdx_index_list_symbols(const TdxClient* client); -extern TdxStringArray tdx_index_list_dates(const TdxClient* client, const char* symbol); -extern TdxTickArray tdx_index_snapshot_ohlc(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_index_snapshot_price(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_index_snapshot_market_value(const TdxClient* client, const char* const* symbols, size_t symbols_len); -extern TdxTickArray tdx_index_history_eod(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date); -extern TdxTickArray tdx_index_history_ohlc(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* interval); -extern TdxTickArray tdx_index_history_price(const TdxClient* client, const char* symbol, const char* date, const char* interval); -extern TdxTickArray tdx_index_at_time_price(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date, const char* time_of_day); -extern TdxTickArray tdx_calendar_open_today(const TdxClient* client); -extern TdxTickArray tdx_calendar_on_date(const TdxClient* client, const char* date); -extern TdxTickArray tdx_calendar_year(const TdxClient* client, const char* year); -extern TdxTickArray tdx_interest_rate_history_eod(const TdxClient* client, const char* symbol, const char* start_date, const char* end_date); -/* Generated option-aware endpoint declarations. */ -#include "endpoint_with_options.h.inc" - -/* Greeks */ -typedef struct { - double value; - double delta; - double gamma; - double theta; - double vega; - double rho; - double epsilon; - double lambda; - double vanna; - double charm; - double vomma; - double veta; - double vera; - double speed; - double zomma; - double color; - double ultima; - double iv; - double iv_error; - double d1; - double d2; - double dual_delta; - double dual_gamma; -} TdxGreeksResult; - -extern TdxGreeksResult* tdx_all_greeks(double spot, double strike, double rate, double div_yield, double tte, double option_price, const char* right); -extern void tdx_greeks_result_free(TdxGreeksResult* result); -extern int tdx_implied_volatility(double spot, double strike, double rate, double div_yield, double tte, double option_price, const char* right, double* out_iv, double* out_error); - -/* FPSS subscription metadata */ -typedef struct { - const char* kind; - const char* contract; -} TdxSubscription; - -typedef struct { - const TdxSubscription* data; - size_t len; -} TdxSubscriptionArray; - -typedef struct { - int32_t id; - const char* contract; -} TdxContractMapEntry; - -typedef struct { - const TdxContractMapEntry* data; - size_t len; -} TdxContractMapArray; - -extern void tdx_subscription_array_free(TdxSubscriptionArray* arr); -extern void tdx_contract_map_array_free(TdxContractMapArray* arr); - -/* FPSS events — generated from fpss_event_schema.toml */ -#include "fpss_event_structs.h.inc" - -/* FPSS client */ -extern TdxFpssHandle* tdx_fpss_connect(const TdxCredentials* creds, const TdxConfig* config); -extern int tdx_fpss_subscribe_quotes(const TdxFpssHandle* h, const char* symbol); -extern int tdx_fpss_subscribe_trades(const TdxFpssHandle* h, const char* symbol); -extern int tdx_fpss_subscribe_open_interest(const TdxFpssHandle* h, const char* symbol); -extern int tdx_fpss_subscribe_full_trades(const TdxFpssHandle* h, const char* sec_type); -extern int tdx_fpss_subscribe_full_open_interest(const TdxFpssHandle* h, const char* sec_type); -extern int tdx_fpss_unsubscribe_quotes(const TdxFpssHandle* h, const char* symbol); -extern int tdx_fpss_unsubscribe_trades(const TdxFpssHandle* h, const char* symbol); -extern int tdx_fpss_unsubscribe_open_interest(const TdxFpssHandle* h, const char* symbol); -extern int tdx_fpss_unsubscribe_full_trades(const TdxFpssHandle* h, const char* sec_type); -extern int tdx_fpss_unsubscribe_full_open_interest(const TdxFpssHandle* h, const char* sec_type); -extern int tdx_fpss_subscribe_option_quotes(const TdxFpssHandle* h, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_fpss_subscribe_option_trades(const TdxFpssHandle* h, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_fpss_subscribe_option_open_interest(const TdxFpssHandle* h, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_fpss_unsubscribe_option_quotes(const TdxFpssHandle* h, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_fpss_unsubscribe_option_trades(const TdxFpssHandle* h, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_fpss_unsubscribe_option_open_interest(const TdxFpssHandle* h, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_fpss_is_authenticated(const TdxFpssHandle* h); -/* Look up a contract by server-assigned ID. Returns string or NULL. - * NULL with empty tdx_last_error() means "not found". NULL with non-empty - * tdx_last_error() means a real error occurred. Caller must free with tdx_string_free. */ -extern char* tdx_fpss_contract_lookup(const TdxFpssHandle* h, int id); -extern TdxContractMapArray* tdx_fpss_contract_map(const TdxFpssHandle* h); -extern TdxSubscriptionArray* tdx_fpss_active_subscriptions(const TdxFpssHandle* h); -extern TdxFpssEvent* tdx_fpss_next_event(const TdxFpssHandle* h, uint64_t timeout_ms); -extern void tdx_fpss_event_free(TdxFpssEvent* event); -extern int tdx_fpss_reconnect(const TdxFpssHandle* h); -/* Cumulative count of FPSS events dropped because the internal receiver - * was gone when the callback tried to deliver. Survives reconnect. Returns - * 0 if the handle is null. */ -extern uint64_t tdx_fpss_dropped_events(const TdxFpssHandle* h); -extern void tdx_fpss_shutdown(const TdxFpssHandle* h); -extern void tdx_fpss_free(TdxFpssHandle* h); - -/* Unified client -- historical + streaming through one handle */ -extern TdxUnified* tdx_unified_connect(const TdxCredentials* creds, const TdxConfig* config); -extern int tdx_unified_start_streaming(const TdxUnified* handle); -extern int tdx_unified_subscribe_quotes(const TdxUnified* handle, const char* symbol); -extern int tdx_unified_subscribe_trades(const TdxUnified* handle, const char* symbol); -extern int tdx_unified_unsubscribe_quotes(const TdxUnified* handle, const char* symbol); -extern int tdx_unified_unsubscribe_trades(const TdxUnified* handle, const char* symbol); -extern int tdx_unified_subscribe_open_interest(const TdxUnified* handle, const char* symbol); -extern int tdx_unified_unsubscribe_open_interest(const TdxUnified* handle, const char* symbol); -extern int tdx_unified_subscribe_full_trades(const TdxUnified* handle, const char* sec_type); -extern int tdx_unified_subscribe_full_open_interest(const TdxUnified* handle, const char* sec_type); -extern int tdx_unified_unsubscribe_full_trades(const TdxUnified* handle, const char* sec_type); -extern int tdx_unified_unsubscribe_full_open_interest(const TdxUnified* handle, const char* sec_type); -extern int tdx_unified_subscribe_option_quotes(const TdxUnified* handle, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_unified_subscribe_option_trades(const TdxUnified* handle, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_unified_subscribe_option_open_interest(const TdxUnified* handle, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_unified_unsubscribe_option_quotes(const TdxUnified* handle, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_unified_unsubscribe_option_trades(const TdxUnified* handle, const char* symbol, const char* expiration, const char* strike, const char* right); -extern int tdx_unified_unsubscribe_option_open_interest(const TdxUnified* handle, const char* symbol, const char* expiration, const char* strike, const char* right); -extern TdxContractMapArray* tdx_unified_contract_map(const TdxUnified* handle); -extern int tdx_unified_reconnect(const TdxUnified* handle); -extern int tdx_unified_is_streaming(const TdxUnified* handle); -/* Look up a contract by ID. Returns string or NULL. - * NULL with empty tdx_last_error() means "not found". NULL with non-empty - * tdx_last_error() means a real error occurred. Caller must free with tdx_string_free. */ -extern char* tdx_unified_contract_lookup(const TdxUnified* handle, int id); -extern TdxSubscriptionArray* tdx_unified_active_subscriptions(const TdxUnified* handle); -extern TdxFpssEvent* tdx_unified_next_event(const TdxUnified* handle, uint64_t timeout_ms); -extern const TdxClient* tdx_unified_historical(const TdxUnified* handle); -extern void tdx_unified_stop_streaming(const TdxUnified* handle); -/* Cumulative count of FPSS events dropped because the internal receiver - * was gone when the callback tried to deliver. Survives reconnect. Returns - * 0 if the handle is null. */ -extern uint64_t tdx_unified_dropped_events(const TdxUnified* handle); -extern void tdx_unified_free(TdxUnified* handle); - -#endif diff --git a/sdks/go/ffi_layout_generated_test.go b/sdks/go/ffi_layout_generated_test.go deleted file mode 100644 index f1189e10..00000000 --- a/sdks/go/ffi_layout_generated_test.go +++ /dev/null @@ -1,272 +0,0 @@ -// Code generated by build.rs from tick_schema.toml; DO NOT EDIT. - -package thetadatadx - -import ( - "testing" - "unsafe" -) - -// TestFFIStructSizes verifies that every Go C-mirror struct matches -// the Rust FFI layout derived from `tick_schema.toml`. -func TestFFIStructSizes(t *testing.T) { - tests := []struct { - name string - got uintptr - want uintptr - }{ - {"cCalendarDay", unsafe.Sizeof(cCalendarDay{}), CCalendarDayExpectedSize}, - {"cEodTick", unsafe.Sizeof(cEodTick{}), CEodTickExpectedSize}, - {"cGreeksAllTick", unsafe.Sizeof(cGreeksAllTick{}), CGreeksAllTickExpectedSize}, - {"cGreeksFirstOrderTick", unsafe.Sizeof(cGreeksFirstOrderTick{}), CGreeksFirstOrderTickExpectedSize}, - {"cGreeksSecondOrderTick", unsafe.Sizeof(cGreeksSecondOrderTick{}), CGreeksSecondOrderTickExpectedSize}, - {"cGreeksThirdOrderTick", unsafe.Sizeof(cGreeksThirdOrderTick{}), CGreeksThirdOrderTickExpectedSize}, - {"cInterestRateTick", unsafe.Sizeof(cInterestRateTick{}), CInterestRateTickExpectedSize}, - {"cIvTick", unsafe.Sizeof(cIvTick{}), CIvTickExpectedSize}, - {"cMarketValueTick", unsafe.Sizeof(cMarketValueTick{}), CMarketValueTickExpectedSize}, - {"cOhlcTick", unsafe.Sizeof(cOhlcTick{}), COhlcTickExpectedSize}, - {"cOpenInterestTick", unsafe.Sizeof(cOpenInterestTick{}), COpenInterestTickExpectedSize}, - {"cOptionContract", unsafe.Sizeof(cOptionContract{}), COptionContractExpectedSize}, - {"cPriceTick", unsafe.Sizeof(cPriceTick{}), CPriceTickExpectedSize}, - {"cQuoteTick", unsafe.Sizeof(cQuoteTick{}), CQuoteTickExpectedSize}, - {"cTradeQuoteTick", unsafe.Sizeof(cTradeQuoteTick{}), CTradeQuoteTickExpectedSize}, - {"cTradeTick", unsafe.Sizeof(cTradeTick{}), CTradeTickExpectedSize}, - } - for _, tt := range tests { - if tt.got != tt.want { - t.Errorf("%s: sizeof = %d, want %d (Rust FFI layout changed)", tt.name, tt.got, tt.want) - } - } -} - -// TestTickFieldOffsets verifies field offsets for every Go C-mirror -// tick struct against the generator-owned layout derived from TOML. -func TestTickFieldOffsets(t *testing.T) { - tests := []struct { - name string - got uintptr - want uintptr - }{ - {"cCalendarDay.Date", unsafe.Offsetof(cCalendarDay{}.Date), 0}, - {"cCalendarDay.IsOpen", unsafe.Offsetof(cCalendarDay{}.IsOpen), 4}, - {"cCalendarDay.OpenTime", unsafe.Offsetof(cCalendarDay{}.OpenTime), 8}, - {"cCalendarDay.CloseTime", unsafe.Offsetof(cCalendarDay{}.CloseTime), 12}, - {"cCalendarDay.Status", unsafe.Offsetof(cCalendarDay{}.Status), 16}, - - {"cEodTick.MsOfDay", unsafe.Offsetof(cEodTick{}.MsOfDay), 0}, - {"cEodTick.MsOfDay2", unsafe.Offsetof(cEodTick{}.MsOfDay2), 4}, - {"cEodTick.Open", unsafe.Offsetof(cEodTick{}.Open), 8}, - {"cEodTick.High", unsafe.Offsetof(cEodTick{}.High), 16}, - {"cEodTick.Low", unsafe.Offsetof(cEodTick{}.Low), 24}, - {"cEodTick.Close", unsafe.Offsetof(cEodTick{}.Close), 32}, - {"cEodTick.Volume", unsafe.Offsetof(cEodTick{}.Volume), 40}, - {"cEodTick.Count", unsafe.Offsetof(cEodTick{}.Count), 48}, - {"cEodTick.BidSize", unsafe.Offsetof(cEodTick{}.BidSize), 56}, - {"cEodTick.BidExchange", unsafe.Offsetof(cEodTick{}.BidExchange), 60}, - {"cEodTick.Bid", unsafe.Offsetof(cEodTick{}.Bid), 64}, - {"cEodTick.BidCondition", unsafe.Offsetof(cEodTick{}.BidCondition), 72}, - {"cEodTick.AskSize", unsafe.Offsetof(cEodTick{}.AskSize), 76}, - {"cEodTick.AskExchange", unsafe.Offsetof(cEodTick{}.AskExchange), 80}, - {"cEodTick.Ask", unsafe.Offsetof(cEodTick{}.Ask), 88}, - {"cEodTick.AskCondition", unsafe.Offsetof(cEodTick{}.AskCondition), 96}, - {"cEodTick.Date", unsafe.Offsetof(cEodTick{}.Date), 100}, - {"cEodTick.Expiration", unsafe.Offsetof(cEodTick{}.Expiration), 104}, - {"cEodTick.Strike", unsafe.Offsetof(cEodTick{}.Strike), 112}, - {"cEodTick.Right", unsafe.Offsetof(cEodTick{}.Right), 120}, - - {"cGreeksAllTick.MsOfDay", unsafe.Offsetof(cGreeksAllTick{}.MsOfDay), 0}, - {"cGreeksAllTick.Bid", unsafe.Offsetof(cGreeksAllTick{}.Bid), 8}, - {"cGreeksAllTick.Ask", unsafe.Offsetof(cGreeksAllTick{}.Ask), 16}, - {"cGreeksAllTick.ImpliedVolatility", unsafe.Offsetof(cGreeksAllTick{}.ImpliedVolatility), 24}, - {"cGreeksAllTick.Delta", unsafe.Offsetof(cGreeksAllTick{}.Delta), 32}, - {"cGreeksAllTick.Gamma", unsafe.Offsetof(cGreeksAllTick{}.Gamma), 40}, - {"cGreeksAllTick.Theta", unsafe.Offsetof(cGreeksAllTick{}.Theta), 48}, - {"cGreeksAllTick.Vega", unsafe.Offsetof(cGreeksAllTick{}.Vega), 56}, - {"cGreeksAllTick.Rho", unsafe.Offsetof(cGreeksAllTick{}.Rho), 64}, - {"cGreeksAllTick.IvError", unsafe.Offsetof(cGreeksAllTick{}.IvError), 72}, - {"cGreeksAllTick.Vanna", unsafe.Offsetof(cGreeksAllTick{}.Vanna), 80}, - {"cGreeksAllTick.Charm", unsafe.Offsetof(cGreeksAllTick{}.Charm), 88}, - {"cGreeksAllTick.Vomma", unsafe.Offsetof(cGreeksAllTick{}.Vomma), 96}, - {"cGreeksAllTick.Veta", unsafe.Offsetof(cGreeksAllTick{}.Veta), 104}, - {"cGreeksAllTick.Speed", unsafe.Offsetof(cGreeksAllTick{}.Speed), 112}, - {"cGreeksAllTick.Zomma", unsafe.Offsetof(cGreeksAllTick{}.Zomma), 120}, - {"cGreeksAllTick.Color", unsafe.Offsetof(cGreeksAllTick{}.Color), 128}, - {"cGreeksAllTick.Ultima", unsafe.Offsetof(cGreeksAllTick{}.Ultima), 136}, - {"cGreeksAllTick.D1", unsafe.Offsetof(cGreeksAllTick{}.D1), 144}, - {"cGreeksAllTick.D2", unsafe.Offsetof(cGreeksAllTick{}.D2), 152}, - {"cGreeksAllTick.DualDelta", unsafe.Offsetof(cGreeksAllTick{}.DualDelta), 160}, - {"cGreeksAllTick.DualGamma", unsafe.Offsetof(cGreeksAllTick{}.DualGamma), 168}, - {"cGreeksAllTick.Epsilon", unsafe.Offsetof(cGreeksAllTick{}.Epsilon), 176}, - {"cGreeksAllTick.Lambda", unsafe.Offsetof(cGreeksAllTick{}.Lambda), 184}, - {"cGreeksAllTick.Vera", unsafe.Offsetof(cGreeksAllTick{}.Vera), 192}, - {"cGreeksAllTick.UnderlyingMsOfDay", unsafe.Offsetof(cGreeksAllTick{}.UnderlyingMsOfDay), 200}, - {"cGreeksAllTick.UnderlyingPrice", unsafe.Offsetof(cGreeksAllTick{}.UnderlyingPrice), 208}, - {"cGreeksAllTick.Date", unsafe.Offsetof(cGreeksAllTick{}.Date), 216}, - {"cGreeksAllTick.Expiration", unsafe.Offsetof(cGreeksAllTick{}.Expiration), 220}, - {"cGreeksAllTick.Strike", unsafe.Offsetof(cGreeksAllTick{}.Strike), 224}, - {"cGreeksAllTick.Right", unsafe.Offsetof(cGreeksAllTick{}.Right), 232}, - - {"cGreeksFirstOrderTick.MsOfDay", unsafe.Offsetof(cGreeksFirstOrderTick{}.MsOfDay), 0}, - {"cGreeksFirstOrderTick.Bid", unsafe.Offsetof(cGreeksFirstOrderTick{}.Bid), 8}, - {"cGreeksFirstOrderTick.Ask", unsafe.Offsetof(cGreeksFirstOrderTick{}.Ask), 16}, - {"cGreeksFirstOrderTick.Delta", unsafe.Offsetof(cGreeksFirstOrderTick{}.Delta), 24}, - {"cGreeksFirstOrderTick.Theta", unsafe.Offsetof(cGreeksFirstOrderTick{}.Theta), 32}, - {"cGreeksFirstOrderTick.Vega", unsafe.Offsetof(cGreeksFirstOrderTick{}.Vega), 40}, - {"cGreeksFirstOrderTick.Rho", unsafe.Offsetof(cGreeksFirstOrderTick{}.Rho), 48}, - {"cGreeksFirstOrderTick.Epsilon", unsafe.Offsetof(cGreeksFirstOrderTick{}.Epsilon), 56}, - {"cGreeksFirstOrderTick.Lambda", unsafe.Offsetof(cGreeksFirstOrderTick{}.Lambda), 64}, - {"cGreeksFirstOrderTick.ImpliedVolatility", unsafe.Offsetof(cGreeksFirstOrderTick{}.ImpliedVolatility), 72}, - {"cGreeksFirstOrderTick.IvError", unsafe.Offsetof(cGreeksFirstOrderTick{}.IvError), 80}, - {"cGreeksFirstOrderTick.UnderlyingMsOfDay", unsafe.Offsetof(cGreeksFirstOrderTick{}.UnderlyingMsOfDay), 88}, - {"cGreeksFirstOrderTick.UnderlyingPrice", unsafe.Offsetof(cGreeksFirstOrderTick{}.UnderlyingPrice), 96}, - {"cGreeksFirstOrderTick.Date", unsafe.Offsetof(cGreeksFirstOrderTick{}.Date), 104}, - {"cGreeksFirstOrderTick.Expiration", unsafe.Offsetof(cGreeksFirstOrderTick{}.Expiration), 108}, - {"cGreeksFirstOrderTick.Strike", unsafe.Offsetof(cGreeksFirstOrderTick{}.Strike), 112}, - {"cGreeksFirstOrderTick.Right", unsafe.Offsetof(cGreeksFirstOrderTick{}.Right), 120}, - - {"cGreeksSecondOrderTick.MsOfDay", unsafe.Offsetof(cGreeksSecondOrderTick{}.MsOfDay), 0}, - {"cGreeksSecondOrderTick.Bid", unsafe.Offsetof(cGreeksSecondOrderTick{}.Bid), 8}, - {"cGreeksSecondOrderTick.Ask", unsafe.Offsetof(cGreeksSecondOrderTick{}.Ask), 16}, - {"cGreeksSecondOrderTick.Gamma", unsafe.Offsetof(cGreeksSecondOrderTick{}.Gamma), 24}, - {"cGreeksSecondOrderTick.Vanna", unsafe.Offsetof(cGreeksSecondOrderTick{}.Vanna), 32}, - {"cGreeksSecondOrderTick.Charm", unsafe.Offsetof(cGreeksSecondOrderTick{}.Charm), 40}, - {"cGreeksSecondOrderTick.Vomma", unsafe.Offsetof(cGreeksSecondOrderTick{}.Vomma), 48}, - {"cGreeksSecondOrderTick.Veta", unsafe.Offsetof(cGreeksSecondOrderTick{}.Veta), 56}, - {"cGreeksSecondOrderTick.ImpliedVolatility", unsafe.Offsetof(cGreeksSecondOrderTick{}.ImpliedVolatility), 64}, - {"cGreeksSecondOrderTick.IvError", unsafe.Offsetof(cGreeksSecondOrderTick{}.IvError), 72}, - {"cGreeksSecondOrderTick.UnderlyingMsOfDay", unsafe.Offsetof(cGreeksSecondOrderTick{}.UnderlyingMsOfDay), 80}, - {"cGreeksSecondOrderTick.UnderlyingPrice", unsafe.Offsetof(cGreeksSecondOrderTick{}.UnderlyingPrice), 88}, - {"cGreeksSecondOrderTick.Date", unsafe.Offsetof(cGreeksSecondOrderTick{}.Date), 96}, - {"cGreeksSecondOrderTick.Expiration", unsafe.Offsetof(cGreeksSecondOrderTick{}.Expiration), 100}, - {"cGreeksSecondOrderTick.Strike", unsafe.Offsetof(cGreeksSecondOrderTick{}.Strike), 104}, - {"cGreeksSecondOrderTick.Right", unsafe.Offsetof(cGreeksSecondOrderTick{}.Right), 112}, - - {"cGreeksThirdOrderTick.MsOfDay", unsafe.Offsetof(cGreeksThirdOrderTick{}.MsOfDay), 0}, - {"cGreeksThirdOrderTick.Bid", unsafe.Offsetof(cGreeksThirdOrderTick{}.Bid), 8}, - {"cGreeksThirdOrderTick.Ask", unsafe.Offsetof(cGreeksThirdOrderTick{}.Ask), 16}, - {"cGreeksThirdOrderTick.Speed", unsafe.Offsetof(cGreeksThirdOrderTick{}.Speed), 24}, - {"cGreeksThirdOrderTick.Zomma", unsafe.Offsetof(cGreeksThirdOrderTick{}.Zomma), 32}, - {"cGreeksThirdOrderTick.Color", unsafe.Offsetof(cGreeksThirdOrderTick{}.Color), 40}, - {"cGreeksThirdOrderTick.Ultima", unsafe.Offsetof(cGreeksThirdOrderTick{}.Ultima), 48}, - {"cGreeksThirdOrderTick.ImpliedVolatility", unsafe.Offsetof(cGreeksThirdOrderTick{}.ImpliedVolatility), 56}, - {"cGreeksThirdOrderTick.IvError", unsafe.Offsetof(cGreeksThirdOrderTick{}.IvError), 64}, - {"cGreeksThirdOrderTick.UnderlyingMsOfDay", unsafe.Offsetof(cGreeksThirdOrderTick{}.UnderlyingMsOfDay), 72}, - {"cGreeksThirdOrderTick.UnderlyingPrice", unsafe.Offsetof(cGreeksThirdOrderTick{}.UnderlyingPrice), 80}, - {"cGreeksThirdOrderTick.Date", unsafe.Offsetof(cGreeksThirdOrderTick{}.Date), 88}, - {"cGreeksThirdOrderTick.Expiration", unsafe.Offsetof(cGreeksThirdOrderTick{}.Expiration), 92}, - {"cGreeksThirdOrderTick.Strike", unsafe.Offsetof(cGreeksThirdOrderTick{}.Strike), 96}, - {"cGreeksThirdOrderTick.Right", unsafe.Offsetof(cGreeksThirdOrderTick{}.Right), 104}, - - {"cInterestRateTick.MsOfDay", unsafe.Offsetof(cInterestRateTick{}.MsOfDay), 0}, - {"cInterestRateTick.Rate", unsafe.Offsetof(cInterestRateTick{}.Rate), 8}, - {"cInterestRateTick.Date", unsafe.Offsetof(cInterestRateTick{}.Date), 16}, - - {"cIvTick.MsOfDay", unsafe.Offsetof(cIvTick{}.MsOfDay), 0}, - {"cIvTick.ImpliedVolatility", unsafe.Offsetof(cIvTick{}.ImpliedVolatility), 8}, - {"cIvTick.IvError", unsafe.Offsetof(cIvTick{}.IvError), 16}, - {"cIvTick.Date", unsafe.Offsetof(cIvTick{}.Date), 24}, - {"cIvTick.Expiration", unsafe.Offsetof(cIvTick{}.Expiration), 28}, - {"cIvTick.Strike", unsafe.Offsetof(cIvTick{}.Strike), 32}, - {"cIvTick.Right", unsafe.Offsetof(cIvTick{}.Right), 40}, - - {"cMarketValueTick.MsOfDay", unsafe.Offsetof(cMarketValueTick{}.MsOfDay), 0}, - {"cMarketValueTick.MarketBid", unsafe.Offsetof(cMarketValueTick{}.MarketBid), 8}, - {"cMarketValueTick.MarketAsk", unsafe.Offsetof(cMarketValueTick{}.MarketAsk), 16}, - {"cMarketValueTick.MarketPrice", unsafe.Offsetof(cMarketValueTick{}.MarketPrice), 24}, - {"cMarketValueTick.Date", unsafe.Offsetof(cMarketValueTick{}.Date), 32}, - {"cMarketValueTick.Expiration", unsafe.Offsetof(cMarketValueTick{}.Expiration), 36}, - {"cMarketValueTick.Strike", unsafe.Offsetof(cMarketValueTick{}.Strike), 40}, - {"cMarketValueTick.Right", unsafe.Offsetof(cMarketValueTick{}.Right), 48}, - - {"cOhlcTick.MsOfDay", unsafe.Offsetof(cOhlcTick{}.MsOfDay), 0}, - {"cOhlcTick.Open", unsafe.Offsetof(cOhlcTick{}.Open), 8}, - {"cOhlcTick.High", unsafe.Offsetof(cOhlcTick{}.High), 16}, - {"cOhlcTick.Low", unsafe.Offsetof(cOhlcTick{}.Low), 24}, - {"cOhlcTick.Close", unsafe.Offsetof(cOhlcTick{}.Close), 32}, - {"cOhlcTick.Volume", unsafe.Offsetof(cOhlcTick{}.Volume), 40}, - {"cOhlcTick.Count", unsafe.Offsetof(cOhlcTick{}.Count), 48}, - {"cOhlcTick.Date", unsafe.Offsetof(cOhlcTick{}.Date), 56}, - {"cOhlcTick.Expiration", unsafe.Offsetof(cOhlcTick{}.Expiration), 60}, - {"cOhlcTick.Strike", unsafe.Offsetof(cOhlcTick{}.Strike), 64}, - {"cOhlcTick.Right", unsafe.Offsetof(cOhlcTick{}.Right), 72}, - - {"cOpenInterestTick.MsOfDay", unsafe.Offsetof(cOpenInterestTick{}.MsOfDay), 0}, - {"cOpenInterestTick.OpenInterest", unsafe.Offsetof(cOpenInterestTick{}.OpenInterest), 4}, - {"cOpenInterestTick.Date", unsafe.Offsetof(cOpenInterestTick{}.Date), 8}, - {"cOpenInterestTick.Expiration", unsafe.Offsetof(cOpenInterestTick{}.Expiration), 12}, - {"cOpenInterestTick.Strike", unsafe.Offsetof(cOpenInterestTick{}.Strike), 16}, - {"cOpenInterestTick.Right", unsafe.Offsetof(cOpenInterestTick{}.Right), 24}, - - {"cPriceTick.MsOfDay", unsafe.Offsetof(cPriceTick{}.MsOfDay), 0}, - {"cPriceTick.Price", unsafe.Offsetof(cPriceTick{}.Price), 8}, - {"cPriceTick.Date", unsafe.Offsetof(cPriceTick{}.Date), 16}, - - {"cQuoteTick.MsOfDay", unsafe.Offsetof(cQuoteTick{}.MsOfDay), 0}, - {"cQuoteTick.BidSize", unsafe.Offsetof(cQuoteTick{}.BidSize), 4}, - {"cQuoteTick.BidExchange", unsafe.Offsetof(cQuoteTick{}.BidExchange), 8}, - {"cQuoteTick.Bid", unsafe.Offsetof(cQuoteTick{}.Bid), 16}, - {"cQuoteTick.BidCondition", unsafe.Offsetof(cQuoteTick{}.BidCondition), 24}, - {"cQuoteTick.AskSize", unsafe.Offsetof(cQuoteTick{}.AskSize), 28}, - {"cQuoteTick.AskExchange", unsafe.Offsetof(cQuoteTick{}.AskExchange), 32}, - {"cQuoteTick.Ask", unsafe.Offsetof(cQuoteTick{}.Ask), 40}, - {"cQuoteTick.AskCondition", unsafe.Offsetof(cQuoteTick{}.AskCondition), 48}, - {"cQuoteTick.Date", unsafe.Offsetof(cQuoteTick{}.Date), 52}, - {"cQuoteTick.Expiration", unsafe.Offsetof(cQuoteTick{}.Expiration), 56}, - {"cQuoteTick.Strike", unsafe.Offsetof(cQuoteTick{}.Strike), 64}, - {"cQuoteTick.Right", unsafe.Offsetof(cQuoteTick{}.Right), 72}, - {"cQuoteTick.Midpoint", unsafe.Offsetof(cQuoteTick{}.Midpoint), 80}, - - {"cTradeQuoteTick.MsOfDay", unsafe.Offsetof(cTradeQuoteTick{}.MsOfDay), 0}, - {"cTradeQuoteTick.Sequence", unsafe.Offsetof(cTradeQuoteTick{}.Sequence), 4}, - {"cTradeQuoteTick.ExtCondition1", unsafe.Offsetof(cTradeQuoteTick{}.ExtCondition1), 8}, - {"cTradeQuoteTick.ExtCondition2", unsafe.Offsetof(cTradeQuoteTick{}.ExtCondition2), 12}, - {"cTradeQuoteTick.ExtCondition3", unsafe.Offsetof(cTradeQuoteTick{}.ExtCondition3), 16}, - {"cTradeQuoteTick.ExtCondition4", unsafe.Offsetof(cTradeQuoteTick{}.ExtCondition4), 20}, - {"cTradeQuoteTick.Condition", unsafe.Offsetof(cTradeQuoteTick{}.Condition), 24}, - {"cTradeQuoteTick.Size", unsafe.Offsetof(cTradeQuoteTick{}.Size), 28}, - {"cTradeQuoteTick.Exchange", unsafe.Offsetof(cTradeQuoteTick{}.Exchange), 32}, - {"cTradeQuoteTick.Price", unsafe.Offsetof(cTradeQuoteTick{}.Price), 40}, - {"cTradeQuoteTick.ConditionFlags", unsafe.Offsetof(cTradeQuoteTick{}.ConditionFlags), 48}, - {"cTradeQuoteTick.PriceFlags", unsafe.Offsetof(cTradeQuoteTick{}.PriceFlags), 52}, - {"cTradeQuoteTick.VolumeType", unsafe.Offsetof(cTradeQuoteTick{}.VolumeType), 56}, - {"cTradeQuoteTick.RecordsBack", unsafe.Offsetof(cTradeQuoteTick{}.RecordsBack), 60}, - {"cTradeQuoteTick.QuoteMsOfDay", unsafe.Offsetof(cTradeQuoteTick{}.QuoteMsOfDay), 64}, - {"cTradeQuoteTick.BidSize", unsafe.Offsetof(cTradeQuoteTick{}.BidSize), 68}, - {"cTradeQuoteTick.BidExchange", unsafe.Offsetof(cTradeQuoteTick{}.BidExchange), 72}, - {"cTradeQuoteTick.Bid", unsafe.Offsetof(cTradeQuoteTick{}.Bid), 80}, - {"cTradeQuoteTick.BidCondition", unsafe.Offsetof(cTradeQuoteTick{}.BidCondition), 88}, - {"cTradeQuoteTick.AskSize", unsafe.Offsetof(cTradeQuoteTick{}.AskSize), 92}, - {"cTradeQuoteTick.AskExchange", unsafe.Offsetof(cTradeQuoteTick{}.AskExchange), 96}, - {"cTradeQuoteTick.Ask", unsafe.Offsetof(cTradeQuoteTick{}.Ask), 104}, - {"cTradeQuoteTick.AskCondition", unsafe.Offsetof(cTradeQuoteTick{}.AskCondition), 112}, - {"cTradeQuoteTick.Date", unsafe.Offsetof(cTradeQuoteTick{}.Date), 116}, - {"cTradeQuoteTick.Expiration", unsafe.Offsetof(cTradeQuoteTick{}.Expiration), 120}, - {"cTradeQuoteTick.Strike", unsafe.Offsetof(cTradeQuoteTick{}.Strike), 128}, - {"cTradeQuoteTick.Right", unsafe.Offsetof(cTradeQuoteTick{}.Right), 136}, - - {"cTradeTick.MsOfDay", unsafe.Offsetof(cTradeTick{}.MsOfDay), 0}, - {"cTradeTick.Sequence", unsafe.Offsetof(cTradeTick{}.Sequence), 4}, - {"cTradeTick.ExtCondition1", unsafe.Offsetof(cTradeTick{}.ExtCondition1), 8}, - {"cTradeTick.ExtCondition2", unsafe.Offsetof(cTradeTick{}.ExtCondition2), 12}, - {"cTradeTick.ExtCondition3", unsafe.Offsetof(cTradeTick{}.ExtCondition3), 16}, - {"cTradeTick.ExtCondition4", unsafe.Offsetof(cTradeTick{}.ExtCondition4), 20}, - {"cTradeTick.Condition", unsafe.Offsetof(cTradeTick{}.Condition), 24}, - {"cTradeTick.Size", unsafe.Offsetof(cTradeTick{}.Size), 28}, - {"cTradeTick.Exchange", unsafe.Offsetof(cTradeTick{}.Exchange), 32}, - {"cTradeTick.Price", unsafe.Offsetof(cTradeTick{}.Price), 40}, - {"cTradeTick.ConditionFlags", unsafe.Offsetof(cTradeTick{}.ConditionFlags), 48}, - {"cTradeTick.PriceFlags", unsafe.Offsetof(cTradeTick{}.PriceFlags), 52}, - {"cTradeTick.VolumeType", unsafe.Offsetof(cTradeTick{}.VolumeType), 56}, - {"cTradeTick.RecordsBack", unsafe.Offsetof(cTradeTick{}.RecordsBack), 60}, - {"cTradeTick.Date", unsafe.Offsetof(cTradeTick{}.Date), 64}, - {"cTradeTick.Expiration", unsafe.Offsetof(cTradeTick{}.Expiration), 68}, - {"cTradeTick.Strike", unsafe.Offsetof(cTradeTick{}.Strike), 72}, - {"cTradeTick.Right", unsafe.Offsetof(cTradeTick{}.Right), 80}, - - } - for _, tt := range tests { - if tt.got != tt.want { - t.Errorf("%s: offset = %d, want %d (Rust FFI layout changed)", tt.name, tt.got, tt.want) - } - } -} diff --git a/sdks/go/fpss.go b/sdks/go/fpss.go deleted file mode 100644 index db49ece2..00000000 --- a/sdks/go/fpss.go +++ /dev/null @@ -1,83 +0,0 @@ -package thetadatadx - -/* -#include "ffi_bridge.h" -*/ -import "C" - -import ( - "fmt" - "runtime" -) - -// FPSS event types (FpssQuote, FpssTrade, FpssOpenInterest, FpssOhlcvc, -// FpssControl, FpssRawData, FpssEvent) + kind enum + FpssCtrl* constants -// are generated from crates/thetadatadx/fpss_event_schema.toml; see -// fpss_event_structs.go in this package. - -// FpssClient wraps the FPSS real-time streaming handle. -type FpssClient struct { - handle *C.TdxFpssHandle -} - -// NewFpssClient connects to the FPSS streaming servers and returns a client. -// -// Pins the goroutine to one OS thread across the cgo call + TLS error -// read because the FFI's last-error slot is a Rust thread_local. -func NewFpssClient(creds *Credentials, config *Config) (*FpssClient, error) { - if creds == nil || creds.handle == nil { - return nil, fmt.Errorf("thetadatadx: credentials handle is nil") - } - if config == nil || config.handle == nil { - return nil, fmt.Errorf("thetadatadx: config handle is nil") - } - runtime.LockOSThread() - defer runtime.UnlockOSThread() - h := C.tdx_fpss_connect(creds.handle, config.handle) - if h == nil { - return nil, fmt.Errorf("thetadatadx: %s", lastError()) - } - return &FpssClient{handle: h}, nil -} - -// fpssCall is called by the generated subscribe/unsubscribe wrappers in -// fpss_methods.go. Every such wrapper has already pinned its goroutine -// via runtime.LockOSThread() so the in-flight cgo call and this -// lastError() read both run on the same OS thread. -func (f *FpssClient) fpssCall(rc C.int) (int, error) { - if rc < 0 { - return int(rc), fmt.Errorf("thetadatadx: %s", lastError()) - } - return int(rc), nil -} - -// Subscription represents a single active subscription entry. -type Subscription struct { - Kind string // "Quote", "Trade", or "OpenInterest" - Contract string // "SPY" or "SPY 20260417 550 C" -} - -// Close frees the FPSS handle. Call after Shutdown. -func (f *FpssClient) Close() { - if f.handle != nil { - C.tdx_fpss_free(f.handle) - f.handle = nil - } -} - -// DroppedEvents returns the cumulative count of FPSS events dropped -// because the internal receiver was gone (channel disconnected) when -// the callback tried to deliver. Survives Reconnect. -// -// Parity with the Python `tdx.dropped_events()` and TypeScript -// `tdx.droppedEvents()` getters: ops teams diagnosing silent drops on -// production Go consumers get a cheap sample path without scraping -// `RUST_LOG=thetadatadx::ffi::streaming=debug` logs. -// -// Safe to call on a nil / closed handle; returns 0 in either case. -func (f *FpssClient) DroppedEvents() uint64 { - if f == nil || f.handle == nil { - return 0 - } - return uint64(C.tdx_fpss_dropped_events(f.handle)) -} diff --git a/sdks/go/fpss_ffi_offset_checks_generated.go b/sdks/go/fpss_ffi_offset_checks_generated.go deleted file mode 100644 index 5a8bf545..00000000 --- a/sdks/go/fpss_ffi_offset_checks_generated.go +++ /dev/null @@ -1,78 +0,0 @@ -// Code generated by build.rs from fpss_event_schema.toml. DO NOT EDIT. - -package thetadatadx - -/* -#include "ffi_bridge.h" -*/ -import "C" - -import "unsafe" - -var fpssOffsetChecks = []struct { - name string - got uintptr - want uintptr -}{ - {"C.TdxFpssOhlcvc.contract_id", unsafe.Offsetof(C.TdxFpssOhlcvc{}.contract_id), 0}, - {"C.TdxFpssOhlcvc.contract", unsafe.Offsetof(C.TdxFpssOhlcvc{}.contract), 8}, - {"C.TdxFpssOhlcvc.ms_of_day", unsafe.Offsetof(C.TdxFpssOhlcvc{}.ms_of_day), 40}, - {"C.TdxFpssOhlcvc.open", unsafe.Offsetof(C.TdxFpssOhlcvc{}.open), 48}, - {"C.TdxFpssOhlcvc.high", unsafe.Offsetof(C.TdxFpssOhlcvc{}.high), 56}, - {"C.TdxFpssOhlcvc.low", unsafe.Offsetof(C.TdxFpssOhlcvc{}.low), 64}, - {"C.TdxFpssOhlcvc.close", unsafe.Offsetof(C.TdxFpssOhlcvc{}.close), 72}, - {"C.TdxFpssOhlcvc.volume", unsafe.Offsetof(C.TdxFpssOhlcvc{}.volume), 80}, - {"C.TdxFpssOhlcvc.count", unsafe.Offsetof(C.TdxFpssOhlcvc{}.count), 88}, - {"C.TdxFpssOhlcvc.date", unsafe.Offsetof(C.TdxFpssOhlcvc{}.date), 96}, - {"C.TdxFpssOhlcvc.received_at_ns", unsafe.Offsetof(C.TdxFpssOhlcvc{}.received_at_ns), 104}, - {"C.TdxFpssOpenInterest.contract_id", unsafe.Offsetof(C.TdxFpssOpenInterest{}.contract_id), 0}, - {"C.TdxFpssOpenInterest.contract", unsafe.Offsetof(C.TdxFpssOpenInterest{}.contract), 8}, - {"C.TdxFpssOpenInterest.ms_of_day", unsafe.Offsetof(C.TdxFpssOpenInterest{}.ms_of_day), 40}, - {"C.TdxFpssOpenInterest.open_interest", unsafe.Offsetof(C.TdxFpssOpenInterest{}.open_interest), 44}, - {"C.TdxFpssOpenInterest.date", unsafe.Offsetof(C.TdxFpssOpenInterest{}.date), 48}, - {"C.TdxFpssOpenInterest.received_at_ns", unsafe.Offsetof(C.TdxFpssOpenInterest{}.received_at_ns), 56}, - {"C.TdxFpssQuote.contract_id", unsafe.Offsetof(C.TdxFpssQuote{}.contract_id), 0}, - {"C.TdxFpssQuote.contract", unsafe.Offsetof(C.TdxFpssQuote{}.contract), 8}, - {"C.TdxFpssQuote.ms_of_day", unsafe.Offsetof(C.TdxFpssQuote{}.ms_of_day), 40}, - {"C.TdxFpssQuote.bid_size", unsafe.Offsetof(C.TdxFpssQuote{}.bid_size), 44}, - {"C.TdxFpssQuote.bid_exchange", unsafe.Offsetof(C.TdxFpssQuote{}.bid_exchange), 48}, - {"C.TdxFpssQuote.bid", unsafe.Offsetof(C.TdxFpssQuote{}.bid), 56}, - {"C.TdxFpssQuote.bid_condition", unsafe.Offsetof(C.TdxFpssQuote{}.bid_condition), 64}, - {"C.TdxFpssQuote.ask_size", unsafe.Offsetof(C.TdxFpssQuote{}.ask_size), 68}, - {"C.TdxFpssQuote.ask_exchange", unsafe.Offsetof(C.TdxFpssQuote{}.ask_exchange), 72}, - {"C.TdxFpssQuote.ask", unsafe.Offsetof(C.TdxFpssQuote{}.ask), 80}, - {"C.TdxFpssQuote.ask_condition", unsafe.Offsetof(C.TdxFpssQuote{}.ask_condition), 88}, - {"C.TdxFpssQuote.date", unsafe.Offsetof(C.TdxFpssQuote{}.date), 92}, - {"C.TdxFpssQuote.received_at_ns", unsafe.Offsetof(C.TdxFpssQuote{}.received_at_ns), 96}, - {"C.TdxFpssTrade.contract_id", unsafe.Offsetof(C.TdxFpssTrade{}.contract_id), 0}, - {"C.TdxFpssTrade.contract", unsafe.Offsetof(C.TdxFpssTrade{}.contract), 8}, - {"C.TdxFpssTrade.ms_of_day", unsafe.Offsetof(C.TdxFpssTrade{}.ms_of_day), 40}, - {"C.TdxFpssTrade.sequence", unsafe.Offsetof(C.TdxFpssTrade{}.sequence), 44}, - {"C.TdxFpssTrade.ext_condition1", unsafe.Offsetof(C.TdxFpssTrade{}.ext_condition1), 48}, - {"C.TdxFpssTrade.ext_condition2", unsafe.Offsetof(C.TdxFpssTrade{}.ext_condition2), 52}, - {"C.TdxFpssTrade.ext_condition3", unsafe.Offsetof(C.TdxFpssTrade{}.ext_condition3), 56}, - {"C.TdxFpssTrade.ext_condition4", unsafe.Offsetof(C.TdxFpssTrade{}.ext_condition4), 60}, - {"C.TdxFpssTrade.condition", unsafe.Offsetof(C.TdxFpssTrade{}.condition), 64}, - {"C.TdxFpssTrade.size", unsafe.Offsetof(C.TdxFpssTrade{}.size), 68}, - {"C.TdxFpssTrade.exchange", unsafe.Offsetof(C.TdxFpssTrade{}.exchange), 72}, - {"C.TdxFpssTrade.price", unsafe.Offsetof(C.TdxFpssTrade{}.price), 80}, - {"C.TdxFpssTrade.condition_flags", unsafe.Offsetof(C.TdxFpssTrade{}.condition_flags), 88}, - {"C.TdxFpssTrade.price_flags", unsafe.Offsetof(C.TdxFpssTrade{}.price_flags), 92}, - {"C.TdxFpssTrade.volume_type", unsafe.Offsetof(C.TdxFpssTrade{}.volume_type), 96}, - {"C.TdxFpssTrade.records_back", unsafe.Offsetof(C.TdxFpssTrade{}.records_back), 100}, - {"C.TdxFpssTrade.date", unsafe.Offsetof(C.TdxFpssTrade{}.date), 104}, - {"C.TdxFpssTrade.received_at_ns", unsafe.Offsetof(C.TdxFpssTrade{}.received_at_ns), 112}, - {"C.TdxFpssControl.kind", unsafe.Offsetof(C.TdxFpssControl{}.kind), 0}, - {"C.TdxFpssControl.id", unsafe.Offsetof(C.TdxFpssControl{}.id), 4}, - {"C.TdxFpssControl.detail", unsafe.Offsetof(C.TdxFpssControl{}.detail), 8}, - {"C.TdxFpssRawData.code", unsafe.Offsetof(C.TdxFpssRawData{}.code), 0}, - {"C.TdxFpssRawData.payload", unsafe.Offsetof(C.TdxFpssRawData{}.payload), 8}, - {"C.TdxFpssRawData.payload_len", unsafe.Offsetof(C.TdxFpssRawData{}.payload_len), 16}, - {"C.TdxFpssEvent.kind", unsafe.Offsetof(C.TdxFpssEvent{}.kind), 0}, - {"C.TdxFpssEvent.ohlcvc", unsafe.Offsetof(C.TdxFpssEvent{}.ohlcvc), 8}, - {"C.TdxFpssEvent.open_interest", unsafe.Offsetof(C.TdxFpssEvent{}.open_interest), 120}, - {"C.TdxFpssEvent.quote", unsafe.Offsetof(C.TdxFpssEvent{}.quote), 184}, - {"C.TdxFpssEvent.trade", unsafe.Offsetof(C.TdxFpssEvent{}.trade), 288}, - {"C.TdxFpssEvent.control", unsafe.Offsetof(C.TdxFpssEvent{}.control), 408}, - {"C.TdxFpssEvent.raw_data", unsafe.Offsetof(C.TdxFpssEvent{}.raw_data), 424}, -} diff --git a/sdks/go/fpss_ffi_sizes_generated.go b/sdks/go/fpss_ffi_sizes_generated.go deleted file mode 100644 index f0def8b1..00000000 --- a/sdks/go/fpss_ffi_sizes_generated.go +++ /dev/null @@ -1,13 +0,0 @@ -// Code generated by build.rs from fpss_event_schema.toml. DO NOT EDIT. - -package thetadatadx - -const ( - CTdxFpssOhlcvcExpectedSize uintptr = 112 - CTdxFpssOpenInterestExpectedSize uintptr = 64 - CTdxFpssQuoteExpectedSize uintptr = 104 - CTdxFpssTradeExpectedSize uintptr = 120 - CTdxFpssControlExpectedSize uintptr = 16 - CTdxFpssRawDataExpectedSize uintptr = 24 - CTdxFpssEventExpectedSize uintptr = 448 -) diff --git a/sdks/go/go.mod b/sdks/go/go.mod deleted file mode 100644 index 4198e034..00000000 --- a/sdks/go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/userFRM/thetadatadx/sdks/go - -go 1.23 diff --git a/sdks/go/historical.go b/sdks/go/historical.go deleted file mode 100644 index 33f619bf..00000000 --- a/sdks/go/historical.go +++ /dev/null @@ -1,1253 +0,0 @@ -// Code generated by build.rs from endpoint_surface.toml; DO NOT EDIT. - -package thetadatadx - -/* -#include "ffi_bridge.h" -*/ -import "C" - -import ( - "fmt" - "runtime" - "unsafe" -) - -func (c *Client) StockListSymbols(opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_list_symbols_with_options(c.handle, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) StockListDates(requestType string, symbol string, opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cRequestType := C.CString(requestType) - defer C.free(unsafe.Pointer(cRequestType)) - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_list_dates_with_options(c.handle, cRequestType, cSymbol, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) StockSnapshotOHLC(symbol string, opts ...EndpointOption) ([]OhlcTick, error) { - return c.StockSnapshotOHLCBulk([]string{symbol}, opts...) -} - -func (c *Client) StockSnapshotOHLCBulk(symbols []string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_snapshot_ohlc_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockSnapshotTrade(symbol string, opts ...EndpointOption) ([]TradeTick, error) { - return c.StockSnapshotTradeBulk([]string{symbol}, opts...) -} - -func (c *Client) StockSnapshotTradeBulk(symbols []string, opts ...EndpointOption) ([]TradeTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_snapshot_trade_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeTicks(arr) - C.tdx_trade_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockSnapshotQuote(symbol string, opts ...EndpointOption) ([]QuoteTick, error) { - return c.StockSnapshotQuoteBulk([]string{symbol}, opts...) -} - -func (c *Client) StockSnapshotQuoteBulk(symbols []string, opts ...EndpointOption) ([]QuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_snapshot_quote_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertQuoteTicks(arr) - C.tdx_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockSnapshotMarketValue(symbol string, opts ...EndpointOption) ([]MarketValueTick, error) { - return c.StockSnapshotMarketValueBulk([]string{symbol}, opts...) -} - -func (c *Client) StockSnapshotMarketValueBulk(symbols []string, opts ...EndpointOption) ([]MarketValueTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_snapshot_market_value_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_market_value_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertMarketValueTicks(arr) - C.tdx_market_value_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockHistoryEOD(symbol string, startDate string, endDate string, opts ...EndpointOption) ([]EodTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_history_eod_with_options(c.handle, cSymbol, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_eod_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertEodTicks(arr) - C.tdx_eod_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockHistoryOHLC(symbol string, date string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_history_ohlc_with_options(c.handle, cSymbol, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockHistoryTrade(symbol string, date string, opts ...EndpointOption) ([]TradeTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_history_trade_with_options(c.handle, cSymbol, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeTicks(arr) - C.tdx_trade_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockHistoryQuote(symbol string, date string, opts ...EndpointOption) ([]QuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_history_quote_with_options(c.handle, cSymbol, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertQuoteTicks(arr) - C.tdx_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockHistoryTradeQuote(symbol string, date string, opts ...EndpointOption) ([]TradeQuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_history_trade_quote_with_options(c.handle, cSymbol, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeQuoteTicks(arr) - C.tdx_trade_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockAtTimeTrade(symbol string, startDate string, endDate string, timeOfDay string, opts ...EndpointOption) ([]TradeTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cTimeOfDay := C.CString(timeOfDay) - defer C.free(unsafe.Pointer(cTimeOfDay)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_at_time_trade_with_options(c.handle, cSymbol, cStartDate, cEndDate, cTimeOfDay, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeTicks(arr) - C.tdx_trade_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockAtTimeQuote(symbol string, startDate string, endDate string, timeOfDay string, opts ...EndpointOption) ([]QuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cTimeOfDay := C.CString(timeOfDay) - defer C.free(unsafe.Pointer(cTimeOfDay)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_at_time_quote_with_options(c.handle, cSymbol, cStartDate, cEndDate, cTimeOfDay, cOpts) - if e := lastError(); e != "" { - C.tdx_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertQuoteTicks(arr) - C.tdx_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionListSymbols(opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_list_symbols_with_options(c.handle, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) OptionListDates(requestType string, symbol string, expiration string, opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cRequestType := C.CString(requestType) - defer C.free(unsafe.Pointer(cRequestType)) - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_list_dates_with_options(c.handle, cRequestType, cSymbol, cExpiration, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) OptionListExpirations(symbol string, opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_list_expirations_with_options(c.handle, cSymbol, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) OptionListStrikes(symbol string, expiration string, opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_list_strikes_with_options(c.handle, cSymbol, cExpiration, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) OptionListContracts(requestType string, symbol string, date string, opts ...EndpointOption) ([]OptionContract, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cRequestType := C.CString(requestType) - defer C.free(unsafe.Pointer(cRequestType)) - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_list_contracts_with_options(c.handle, cRequestType, cSymbol, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_option_contract_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOptionContracts(arr) - C.tdx_option_contract_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotOHLC(symbol string, expiration string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_ohlc_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotTrade(symbol string, expiration string, opts ...EndpointOption) ([]TradeTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_trade_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeTicks(arr) - C.tdx_trade_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotQuote(symbol string, expiration string, opts ...EndpointOption) ([]QuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_quote_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertQuoteTicks(arr) - C.tdx_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotOpenInterest(symbol string, expiration string, opts ...EndpointOption) ([]OpenInterestTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_open_interest_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_open_interest_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOpenInterestTicks(arr) - C.tdx_open_interest_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotMarketValue(symbol string, expiration string, opts ...EndpointOption) ([]MarketValueTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_market_value_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_market_value_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertMarketValueTicks(arr) - C.tdx_market_value_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotGreeksImpliedVolatility(symbol string, expiration string, opts ...EndpointOption) ([]IVTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_greeks_implied_volatility_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_iv_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertIvTicks(arr) - C.tdx_iv_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotGreeksAll(symbol string, expiration string, opts ...EndpointOption) ([]GreeksAllTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_greeks_all_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_all_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksAllTicks(arr) - C.tdx_greeks_all_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotGreeksFirstOrder(symbol string, expiration string, opts ...EndpointOption) ([]GreeksFirstOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_greeks_first_order_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_first_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksFirstOrderTicks(arr) - C.tdx_greeks_first_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotGreeksSecondOrder(symbol string, expiration string, opts ...EndpointOption) ([]GreeksSecondOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_greeks_second_order_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_second_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksSecondOrderTicks(arr) - C.tdx_greeks_second_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionSnapshotGreeksThirdOrder(symbol string, expiration string, opts ...EndpointOption) ([]GreeksThirdOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_snapshot_greeks_third_order_with_options(c.handle, cSymbol, cExpiration, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_third_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksThirdOrderTicks(arr) - C.tdx_greeks_third_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryEOD(symbol string, expiration string, startDate string, endDate string, opts ...EndpointOption) ([]EodTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_eod_with_options(c.handle, cSymbol, cExpiration, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_eod_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertEodTicks(arr) - C.tdx_eod_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryOHLC(symbol string, expiration string, date string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_ohlc_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTrade(symbol string, expiration string, date string, opts ...EndpointOption) ([]TradeTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeTicks(arr) - C.tdx_trade_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryQuote(symbol string, expiration string, date string, opts ...EndpointOption) ([]QuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_quote_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertQuoteTicks(arr) - C.tdx_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTradeQuote(symbol string, expiration string, date string, opts ...EndpointOption) ([]TradeQuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_quote_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeQuoteTicks(arr) - C.tdx_trade_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryOpenInterest(symbol string, expiration string, date string, opts ...EndpointOption) ([]OpenInterestTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_open_interest_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_open_interest_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOpenInterestTicks(arr) - C.tdx_open_interest_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryGreeksEOD(symbol string, expiration string, startDate string, endDate string, opts ...EndpointOption) ([]GreeksAllTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_greeks_eod_with_options(c.handle, cSymbol, cExpiration, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_all_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksAllTicks(arr) - C.tdx_greeks_all_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryGreeksAll(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksAllTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_greeks_all_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_all_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksAllTicks(arr) - C.tdx_greeks_all_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTradeGreeksAll(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksAllTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_greeks_all_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_all_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksAllTicks(arr) - C.tdx_greeks_all_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryGreeksFirstOrder(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksFirstOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_greeks_first_order_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_first_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksFirstOrderTicks(arr) - C.tdx_greeks_first_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTradeGreeksFirstOrder(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksFirstOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_greeks_first_order_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_first_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksFirstOrderTicks(arr) - C.tdx_greeks_first_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryGreeksSecondOrder(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksSecondOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_greeks_second_order_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_second_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksSecondOrderTicks(arr) - C.tdx_greeks_second_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTradeGreeksSecondOrder(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksSecondOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_greeks_second_order_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_second_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksSecondOrderTicks(arr) - C.tdx_greeks_second_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryGreeksThirdOrder(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksThirdOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_greeks_third_order_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_third_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksThirdOrderTicks(arr) - C.tdx_greeks_third_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTradeGreeksThirdOrder(symbol string, expiration string, date string, opts ...EndpointOption) ([]GreeksThirdOrderTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_greeks_third_order_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_greeks_third_order_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertGreeksThirdOrderTicks(arr) - C.tdx_greeks_third_order_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryGreeksImpliedVolatility(symbol string, expiration string, date string, opts ...EndpointOption) ([]IVTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_greeks_implied_volatility_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_iv_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertIvTicks(arr) - C.tdx_iv_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionHistoryTradeGreeksImpliedVolatility(symbol string, expiration string, date string, opts ...EndpointOption) ([]IVTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_history_trade_greeks_implied_volatility_with_options(c.handle, cSymbol, cExpiration, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_iv_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertIvTicks(arr) - C.tdx_iv_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionAtTimeTrade(symbol string, expiration string, startDate string, endDate string, timeOfDay string, opts ...EndpointOption) ([]TradeTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cTimeOfDay := C.CString(timeOfDay) - defer C.free(unsafe.Pointer(cTimeOfDay)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_at_time_trade_with_options(c.handle, cSymbol, cExpiration, cStartDate, cEndDate, cTimeOfDay, cOpts) - if e := lastError(); e != "" { - C.tdx_trade_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertTradeTicks(arr) - C.tdx_trade_tick_array_free(arr) - return result, nil -} - -func (c *Client) OptionAtTimeQuote(symbol string, expiration string, startDate string, endDate string, timeOfDay string, opts ...EndpointOption) ([]QuoteTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cExpiration := C.CString(expiration) - defer C.free(unsafe.Pointer(cExpiration)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cTimeOfDay := C.CString(timeOfDay) - defer C.free(unsafe.Pointer(cTimeOfDay)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_option_at_time_quote_with_options(c.handle, cSymbol, cExpiration, cStartDate, cEndDate, cTimeOfDay, cOpts) - if e := lastError(); e != "" { - C.tdx_quote_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertQuoteTicks(arr) - C.tdx_quote_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexListSymbols(opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_list_symbols_with_options(c.handle, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) IndexListDates(symbol string, opts ...EndpointOption) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_list_dates_with_options(c.handle, cSymbol, cOpts) - return stringArrayToGo(arr) -} - -func (c *Client) IndexSnapshotOHLC(symbol string, opts ...EndpointOption) ([]OhlcTick, error) { - return c.IndexSnapshotOHLCBulk([]string{symbol}, opts...) -} - -func (c *Client) IndexSnapshotOHLCBulk(symbols []string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_snapshot_ohlc_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexSnapshotPrice(symbol string, opts ...EndpointOption) ([]PriceTick, error) { - return c.IndexSnapshotPriceBulk([]string{symbol}, opts...) -} - -func (c *Client) IndexSnapshotPriceBulk(symbols []string, opts ...EndpointOption) ([]PriceTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_snapshot_price_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_price_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertPriceTicks(arr) - C.tdx_price_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexSnapshotMarketValue(symbol string, opts ...EndpointOption) ([]MarketValueTick, error) { - return c.IndexSnapshotMarketValueBulk([]string{symbol}, opts...) -} - -func (c *Client) IndexSnapshotMarketValueBulk(symbols []string, opts ...EndpointOption) ([]MarketValueTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbols, cSymbolsLen := symbolsToCArray(symbols) - defer freeSymbolArray(cSymbols, cSymbolsLen) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_snapshot_market_value_with_options(c.handle, cSymbols, cSymbolsLen, cOpts) - if e := lastError(); e != "" { - C.tdx_market_value_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertMarketValueTicks(arr) - C.tdx_market_value_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexHistoryEOD(symbol string, startDate string, endDate string, opts ...EndpointOption) ([]EodTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_history_eod_with_options(c.handle, cSymbol, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_eod_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertEodTicks(arr) - C.tdx_eod_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexHistoryOHLC(symbol string, startDate string, endDate string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_history_ohlc_with_options(c.handle, cSymbol, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexHistoryPrice(symbol string, date string, opts ...EndpointOption) ([]PriceTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_history_price_with_options(c.handle, cSymbol, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_price_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertPriceTicks(arr) - C.tdx_price_tick_array_free(arr) - return result, nil -} - -func (c *Client) IndexAtTimePrice(symbol string, startDate string, endDate string, timeOfDay string, opts ...EndpointOption) ([]PriceTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cTimeOfDay := C.CString(timeOfDay) - defer C.free(unsafe.Pointer(cTimeOfDay)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_index_at_time_price_with_options(c.handle, cSymbol, cStartDate, cEndDate, cTimeOfDay, cOpts) - if e := lastError(); e != "" { - C.tdx_price_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertPriceTicks(arr) - C.tdx_price_tick_array_free(arr) - return result, nil -} - -func (c *Client) CalendarOpenToday(opts ...EndpointOption) ([]CalendarDay, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_calendar_open_today_with_options(c.handle, cOpts) - if e := lastError(); e != "" { - C.tdx_calendar_day_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertCalendarDays(arr) - C.tdx_calendar_day_array_free(arr) - return result, nil -} - -func (c *Client) CalendarOnDate(date string, opts ...EndpointOption) ([]CalendarDay, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cDate := C.CString(date) - defer C.free(unsafe.Pointer(cDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_calendar_on_date_with_options(c.handle, cDate, cOpts) - if e := lastError(); e != "" { - C.tdx_calendar_day_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertCalendarDays(arr) - C.tdx_calendar_day_array_free(arr) - return result, nil -} - -func (c *Client) CalendarYear(year string, opts ...EndpointOption) ([]CalendarDay, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cYear := C.CString(year) - defer C.free(unsafe.Pointer(cYear)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_calendar_year_with_options(c.handle, cYear, cOpts) - if e := lastError(); e != "" { - C.tdx_calendar_day_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertCalendarDays(arr) - C.tdx_calendar_day_array_free(arr) - return result, nil -} - -func (c *Client) InterestRateHistoryEOD(symbol string, startDate string, endDate string, opts ...EndpointOption) ([]InterestRateTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_interest_rate_history_eod_with_options(c.handle, cSymbol, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_interest_rate_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertInterestRateTicks(arr) - C.tdx_interest_rate_tick_array_free(arr) - return result, nil -} - -func (c *Client) StockHistoryOHLCRange(symbol string, startDate string, endDate string, opts ...EndpointOption) ([]OhlcTick, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cSymbol := C.CString(symbol) - defer C.free(unsafe.Pointer(cSymbol)) - cStartDate := C.CString(startDate) - defer C.free(unsafe.Pointer(cStartDate)) - cEndDate := C.CString(endDate) - defer C.free(unsafe.Pointer(cEndDate)) - cOpts, freeOpts := endpointRequestOptionsToC(collectEndpointRequestOptions(opts)) - defer freeOpts() - C.tdx_clear_error() - arr := C.tdx_stock_history_ohlc_range_with_options(c.handle, cSymbol, cStartDate, cEndDate, cOpts) - if e := lastError(); e != "" { - C.tdx_ohlc_tick_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - result := convertOhlcTicks(arr) - C.tdx_ohlc_tick_array_free(arr) - return result, nil -} - diff --git a/sdks/go/thetadx.go b/sdks/go/thetadx.go deleted file mode 100644 index 0e53b64b..00000000 --- a/sdks/go/thetadx.go +++ /dev/null @@ -1,219 +0,0 @@ -package thetadatadx - -/* -#cgo linux LDFLAGS: -L${SRCDIR}/../../target/release -lthetadatadx_ffi -lm -ldl -lpthread -#cgo darwin LDFLAGS: -L${SRCDIR}/../../target/release -lthetadatadx_ffi -framework Security -framework SystemConfiguration -#cgo windows LDFLAGS: -L${SRCDIR}/../../target/x86_64-pc-windows-gnu/release -lthetadatadx_ffi -#include "ffi_bridge.h" -*/ -import "C" - -import ( - "fmt" - "runtime" - "unsafe" -) - -// lastError returns the most recent FFI error string, or "" if none is set. -// Callers that need to distinguish "error" from "no error" (e.g. after a -// sentinel-returning call like a list endpoint that returns `{nil, 0}` on -// both success-with-no-rows and failure) MUST rely on `!= ""` rather than -// a non-empty guard against a fallback string. -// -// The slot is a Rust thread_local, so the tdx_clear_error / FFI call / -// lastError read sequence must run on a single OS thread — wrappers pin the -// goroutine via runtime.LockOSThread. -func lastError() string { - p := C.tdx_last_error() - if p == nil { - return "" - } - return C.GoString(p) -} - -// stringArrayToGo converts a TdxStringArray to a Go []string and frees the -// C memory. It consults `tdx_last_error` directly because a successful -// empty result and a timeout/failure both return `{data: null, len: 0}` — -// they can only be distinguished by the error slot. Generated -// `*_with_options` wrappers MUST call `tdx_clear_error` before the FFI -// call so we don't pick up a stale error left by a prior call (W3 -// round-2 fix). -// -// The FFI error slot is a Rust `thread_local!`, so the clear / call / -// check sequence MUST run on a single OS thread. Generated wrappers -// pin the goroutine via `runtime.LockOSThread()` + deferred unlock -// before entering this helper (W3 round-3 fix). Without the pin, Go's -// runtime can migrate the goroutine mid-sequence and the post-call -// `lastError()` below would read a stale/empty slot on the wrong -// OS thread. -// -// We also pin here defensively — Lock/UnlockOSThread nest safely, so -// if a future caller forgets to pin, this helper remains correct. -// The static audit in `timeout_pin_test.go` enforces the same -// invariant statically. -func stringArrayToGo(arr C.TdxStringArray) ([]string, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - if e := lastError(); e != "" { - C.tdx_string_array_free(arr) - return nil, fmt.Errorf("thetadatadx: %s", e) - } - if arr.data == nil || arr.len == 0 { - C.tdx_string_array_free(arr) - return nil, nil - } - n := int(arr.len) - // Create a Go slice backed by the C array of char* pointers. - ptrs := unsafe.Slice((**C.char)(arr.data), n) - result := make([]string, n) - for i := 0; i < n; i++ { - if ptrs[i] != nil { - result[i] = C.GoString(ptrs[i]) - } - } - C.tdx_string_array_free(arr) - return result, nil -} - -// ── Credentials ── -// Lifecycle: intentionally hand-written (language-specific constructor semantics). - -// Credentials holds ThetaData authentication credentials. -type Credentials struct { - handle *C.TdxCredentials -} - -// NewCredentials creates credentials from email and password. -// -// Pins the goroutine to one OS thread across the cgo call + TLS error -// read because the FFI's last-error slot is a Rust thread_local; without -// the pin, Go's runtime could migrate the goroutine and the error read -// would see an empty slot on the wrong thread. -func NewCredentials(email, password string) (*Credentials, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cEmail := C.CString(email) - cPassword := C.CString(password) - defer C.free(unsafe.Pointer(cEmail)) - defer C.free(unsafe.Pointer(cPassword)) - - h := C.tdx_credentials_new(cEmail, cPassword) - if h == nil { - return nil, fmt.Errorf("thetadatadx: %s", lastError()) - } - return &Credentials{handle: h}, nil -} - -// CredentialsFromFile loads credentials from a file (line 1 = email, line 2 = password). -// -// Pins the goroutine to one OS thread across the cgo call + TLS error -// read because the FFI's last-error slot is a Rust thread_local; without -// the pin, Go's runtime could migrate the goroutine and the error read -// would see an empty slot on the wrong thread. -func CredentialsFromFile(path string) (*Credentials, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cPath := C.CString(path) - defer C.free(unsafe.Pointer(cPath)) - - h := C.tdx_credentials_from_file(cPath) - if h == nil { - return nil, fmt.Errorf("thetadatadx: %s", lastError()) - } - return &Credentials{handle: h}, nil -} - -// Close frees the credentials handle. -func (c *Credentials) Close() { - if c.handle != nil { - C.tdx_credentials_free(c.handle) - c.handle = nil - } -} - -// ── Config ── -// Lifecycle: intentionally hand-written (language-specific constructor semantics). - -// Config holds connection configuration. -type Config struct { - handle *C.TdxConfig -} - -// ProductionConfig returns the production server config (ThetaData NJ datacenter). -func ProductionConfig() *Config { - return &Config{handle: C.tdx_config_production()} -} - -// DevConfig returns the dev FPSS config (port 20200, infinite historical replay). -func DevConfig() *Config { - return &Config{handle: C.tdx_config_dev()} -} - -// StageConfig returns the stage FPSS config (port 20100, testing, unstable). -func StageConfig() *Config { - return &Config{handle: C.tdx_config_stage()} -} - -// SetReconnectPolicy sets the FPSS auto-reconnect policy. -// - 0 = Auto (default): auto-reconnect matching Java terminal behavior. -// - 1 = Manual: no auto-reconnect, user calls reconnect explicitly. -func (c *Config) SetReconnectPolicy(policy int) { - C.tdx_config_set_reconnect_policy(c.handle, C.int(policy)) -} - -// SetFlushMode sets the FPSS write flush mode. -// - 0 = Batched (default): flush only on PING every 100ms. -// - 1 = Immediate: flush after every frame write. -func (c *Config) SetFlushMode(mode int) { - C.tdx_config_set_flush_mode(c.handle, C.int(mode)) -} - -// SetDeriveOhlcvc sets whether to derive OHLCVC bars locally from trade events. -// - true (default): derive OHLCVC bars from trades. -// - false: only emit server-sent OHLCVC frames (lower overhead). -func (c *Config) SetDeriveOhlcvc(enabled bool) { - v := 0 - if enabled { - v = 1 - } - C.tdx_config_set_derive_ohlcvc(c.handle, C.int(v)) -} - -// Close frees the config handle. -func (c *Config) Close() { - if c.handle != nil { - C.tdx_config_free(c.handle) - c.handle = nil - } -} - -// symbolsToCArray converts a Go string slice into a C array of C strings. -// The caller must free each element and the array itself with C.free. -func symbolsToCArray(symbols []string) (**C.char, C.size_t) { - n := len(symbols) - if n == 0 { - return nil, 0 - } - // Allocate an array of *C.char pointers. - cArray := C.malloc(C.size_t(n) * C.size_t(unsafe.Sizeof((*C.char)(nil)))) - if cArray == nil { - panic("thetadatadx: C.malloc returned nil") - } - ptrs := unsafe.Slice((**C.char)(cArray), n) - for i, s := range symbols { - ptrs[i] = C.CString(s) - } - return (**C.char)(cArray), C.size_t(n) -} - -// freeSymbolArray frees a C array of C strings allocated by symbolsToCArray. -func freeSymbolArray(arr **C.char, n C.size_t) { - if arr == nil { - return - } - ptrs := unsafe.Slice(arr, int(n)) - for i := range ptrs { - C.free(unsafe.Pointer(ptrs[i])) - } - C.free(unsafe.Pointer(arr)) -} diff --git a/sdks/go/tick_ffi_sizes_generated.go b/sdks/go/tick_ffi_sizes_generated.go deleted file mode 100644 index 838d6274..00000000 --- a/sdks/go/tick_ffi_sizes_generated.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by build.rs from tick_schema.toml; DO NOT EDIT. - -package thetadatadx - -const ( - CCalendarDayExpectedSize uintptr = 64 - CEodTickExpectedSize uintptr = 128 - CGreeksAllTickExpectedSize uintptr = 256 - CGreeksFirstOrderTickExpectedSize uintptr = 128 - CGreeksSecondOrderTickExpectedSize uintptr = 128 - CGreeksThirdOrderTickExpectedSize uintptr = 128 - CInterestRateTickExpectedSize uintptr = 64 - CIvTickExpectedSize uintptr = 64 - CMarketValueTickExpectedSize uintptr = 64 - COhlcTickExpectedSize uintptr = 128 - COpenInterestTickExpectedSize uintptr = 64 - COptionContractExpectedSize uintptr = 32 - CPriceTickExpectedSize uintptr = 64 - CQuoteTickExpectedSize uintptr = 128 - CTradeQuoteTickExpectedSize uintptr = 192 - CTradeTickExpectedSize uintptr = 128 -) diff --git a/sdks/go/timeout_race_test.go b/sdks/go/timeout_race_test.go deleted file mode 100644 index fc74c7a9..00000000 --- a/sdks/go/timeout_race_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package thetadatadx - -import ( - "fmt" - "os" - "runtime" - "strings" - "sync" - "sync/atomic" - "testing" -) - -// TestTimeoutConcurrent is a regression guard for the W3 round-3 fix: -// the FFI error slot is a Rust `thread_local!`, so a goroutine migrated -// to a different OS thread between the cgo call (which sets the error -// on thread A) and the post-call error read (which reads thread B's -// empty slot) would silently mis-classify a real timeout as "no rows". -// The fix pins each endpoint call to one OS thread via -// `runtime.LockOSThread()` + deferred unlock. -// -// What this test asserts directly: -// - Every single call across all goroutines and iterations returns a -// non-nil error containing "request deadline exceeded". -// - The race detector is clean across that concurrent load. -// -// What this test CANNOT directly force: -// - A deterministic cgo-time goroutine migration between the three -// successive cgo calls inside a single wrapper (clear/call/check). -// cgo calls are non-preemptible: while a goroutine is executing on -// the C side, the Go runtime won't migrate it. The migration -// window is only the brief Go-land gap between successive cgo -// calls. That window is microseconds wide and the scheduler has -// no obligation to pick it up. -// -// So this is a regression guard, not a reliable negative test: a -// cleanly-working implementation passes, and a broken implementation -// MAY pass too on lightly-loaded CI because the migration window -// never widens enough. The real deterministic argument is the code -// itself (the pin is on every generated wrapper, a grep on -// `runtime.LockOSThread` shows 61 of 61 non-streaming endpoints) plus -// the `thread_local!`-contract comment in `ffi/src/lib.rs`. -// -// Design choices that still buy us detection in the cases where -// migration DOES happen: -// -// 1. Raise GOMAXPROCS to ensure multiple OS threads are available to -// host goroutines (baseline is runtime.NumCPU(), usually >= 2). -// 2. Synchronize the start: every goroutine waits on `<-start` before -// issuing its first call, so the scheduler is under maximum -// contention at release — any migration is most likely to happen -// in that window. -// 3. Amplify the sample: each goroutine issues N=100 calls in a tight -// loop (10 goroutines × 100 calls = 1000 cgo call sequences). Volume -// alone does not make the detection reliable — on a 1-CPU runner -// or with a scheduler that keeps the goroutines on one OS thread, -// the per-call migration probability p can be effectively zero and -// a broken implementation still passes. This test is a BEHAVIORAL -// LOAD GUARD, not a statistical proof; TestEveryEndpointPinsOSThread -// in timeout_pin_test.go is the deterministic arm. -// 4. Per-call assertion: every call must return an error containing -// "request deadline exceeded". Even one nil-error return from any -// iteration of any goroutine fails the test with the goroutine id -// and iteration number in the failure message. -// -// The `-race` detector is orthogonal to the TLS-migration bug (it -// catches Go-managed memory races, not wrong-thread reads of Rust -// TLS), so running under `go test -race` gives both guarantees: -// data-race-free AND no regressions from the pin itself. -// -// Gated on `THETADX_TEST_CREDS=path/to/creds.txt`; CI runs it in the -// surfaces job with creds, local `go test` without creds skips. -func TestTimeoutConcurrent(t *testing.T) { - credsPath := os.Getenv("THETADX_TEST_CREDS") - if credsPath == "" { - t.Skip("set THETADX_TEST_CREDS to a creds.txt path to enable this live test") - } - - // Force multi-threaded scheduling so migration is possible. On a - // single-core GOMAXPROCS=1 host the goroutines would share one OS - // thread and never trigger the bug. - prevMax := runtime.GOMAXPROCS(0) - if prevMax < 2 { - runtime.GOMAXPROCS(2) - defer runtime.GOMAXPROCS(prevMax) - } - - creds, err := CredentialsFromFile(credsPath) - if err != nil { - t.Fatalf("creds: %v", err) - } - defer creds.Close() - cfg := ProductionConfig() - defer cfg.Close() - client, err := Connect(creds, cfg) - if err != nil { - t.Fatalf("connect: %v", err) - } - defer client.Close() - - const ( - goroutines = 10 - iterations = 100 - ) - - start := make(chan struct{}) - var wg sync.WaitGroup - var successCount int64 - // Buffered so goroutines don't block on send; capacity is the - // worst case (every call fails). Drained after wg.Wait. - failures := make(chan string, goroutines*iterations) - - for g := 0; g < goroutines; g++ { - wg.Add(1) - go func(gid int) { - defer wg.Done() - <-start // maximum contention on release - for i := 0; i < iterations; i++ { - _, err := client.StockListSymbols(WithTimeoutMs(1)) - if err == nil { - // The bug signature: SDK returned success (nil - // error, empty slice) for what should have been - // a timeout. If we see this even once, the - // OS-thread pin is not effective. - failures <- fmt.Sprintf("goroutine=%d iter=%d: expected timeout error, got nil (possible cgo TLS race)", gid, i) - continue - } - msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "request deadline exceeded") { - failures <- fmt.Sprintf("goroutine=%d iter=%d: unexpected error text: %v", gid, i, err) - continue - } - atomic.AddInt64(&successCount, 1) - } - }(g) - } - - close(start) // release all goroutines simultaneously - wg.Wait() - close(failures) - - for f := range failures { - t.Error(f) - } - - want := int64(goroutines * iterations) - if got := atomic.LoadInt64(&successCount); got != want { - t.Fatalf("expected %d timeout errors across all goroutines/iterations, got %d", want, got) - } -} diff --git a/sdks/go/timeout_test.go b/sdks/go/timeout_test.go deleted file mode 100644 index aacc1d4d..00000000 --- a/sdks/go/timeout_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package thetadatadx - -import ( - "os" - "strings" - "testing" - "time" -) - -// TestStringListEndpointTimeoutReturnsError covers Finding 2 end-to-end: -// `stock_list_symbols` returns `{nullptr, 0}` for both success-empty -// (rare) AND failure-empty (e.g. timeout). With the W3 round-2 fix the -// Go wrapper must consult `tdx_last_error` via `lastError()` and -// surface a real error, not silently return `(nil, nil)`. -// -// Gated on `THETADX_TEST_CREDS=path/to/creds.txt` because it needs -// network access; CI runs it in the surfaces job which has creds, local -// `cargo test` skips silently. -func TestStringListEndpointTimeoutReturnsError(t *testing.T) { - credsPath := os.Getenv("THETADX_TEST_CREDS") - if credsPath == "" { - t.Skip("set THETADX_TEST_CREDS to a creds.txt path to enable this live test") - } - creds, err := CredentialsFromFile(credsPath) - if err != nil { - t.Fatalf("creds: %v", err) - } - defer creds.Close() - cfg := ProductionConfig() - defer cfg.Close() - client, err := Connect(creds, cfg) - if err != nil { - t.Fatalf("connect: %v", err) - } - defer client.Close() - - // 1ms deadline guarantees the gRPC call doesn't complete in time. - got, err := client.StockListSymbols(WithTimeoutMs(1)) - if err == nil { - t.Fatalf("StockListSymbols(WithTimeoutMs(1)) returned (%d symbols, nil), want error", len(got)) - } - if !strings.Contains(strings.ToLower(err.Error()), "request deadline exceeded") { - t.Errorf("expected timeout error containing \"Request deadline exceeded\", got: %v", err) - } - - // Subsequent call on the same handle must succeed (W3 contract). - got, err = client.StockListSymbols(WithTimeoutMs(60_000)) - if err != nil { - t.Fatalf("subsequent StockListSymbols after timeout returned %v, want success", err) - } - if len(got) == 0 { - t.Error("expected non-empty symbol list, got 0") - } -} - -// TestWithDeadlineNegativeClampsToImmediate (Finding 4) — a negative -// time.Duration would silently wrap to a multi-century unsigned timeout -// after the uint64 cast. WithDeadline must clamp so the deadline is -// effectively expired (immediate timeout) instead of effectively -// infinite. We pick clamp-to-1ms: "deadline already in the past" fires -// immediately, matches the user's intent. -func TestWithDeadlineNegativeClampsToImmediate(t *testing.T) { - opts := &EndpointRequestOptions{} - WithDeadline(-1 * time.Second)(opts) - if opts.TimeoutMs == nil { - t.Fatal("WithDeadline(negative) returned nil TimeoutMs, want clamped value") - } - if *opts.TimeoutMs != 1 { - t.Errorf("WithDeadline(-1s) set TimeoutMs=%d, want 1 (clamp-to-immediate)", *opts.TimeoutMs) - } -} - -// TestWithDeadlinePositiveValuePassesThrough — sanity check that the -// clamp doesn't intercept legitimate positive durations. -func TestWithDeadlinePositiveValuePassesThrough(t *testing.T) { - opts := &EndpointRequestOptions{} - WithDeadline(60 * time.Second)(opts) - if opts.TimeoutMs == nil { - t.Fatal("WithDeadline(60s) returned nil TimeoutMs") - } - if *opts.TimeoutMs != 60_000 { - t.Errorf("WithDeadline(60s) set TimeoutMs=%d, want 60000", *opts.TimeoutMs) - } -} - -// TestWithTimeoutMsZeroPassesThroughAsZero — `0` becomes `Some(0)` at -// the EndpointOption layer; the Rust-side `EndpointArgs::set_timeout_ms` -// then normalizes Some(0) to None. We verify the Go layer doesn't -// intercept the 0 sentinel itself. -func TestWithTimeoutMsZeroPassesThroughAsZero(t *testing.T) { - opts := &EndpointRequestOptions{} - WithTimeoutMs(0)(opts) - if opts.TimeoutMs == nil { - t.Fatal("WithTimeoutMs(0) returned nil TimeoutMs") - } - if *opts.TimeoutMs != 0 { - t.Errorf("WithTimeoutMs(0) set TimeoutMs=%d, want 0", *opts.TimeoutMs) - } -} diff --git a/sdks/go/utilities.go b/sdks/go/utilities.go deleted file mode 100644 index 43c2e3ea..00000000 --- a/sdks/go/utilities.go +++ /dev/null @@ -1,68 +0,0 @@ -// Code generated by build.rs from sdk_surface.toml; DO NOT EDIT. - -package thetadatadx - -/* -#include -#include "ffi_bridge.h" -*/ -import "C" - -import ( - "fmt" - "runtime" - "unsafe" -) - -// AllGreeks Compute all 23 Black-Scholes Greeks + IV in one call. -func AllGreeks(spot float64, strike float64, rate float64, divYield float64, tte float64, optionPrice float64, right string) (*Greeks, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cRight := C.CString(right) - defer C.free(unsafe.Pointer(cRight)) - ptr := C.tdx_all_greeks(C.double(spot), C.double(strike), C.double(rate), C.double(divYield), C.double(tte), C.double(optionPrice), cRight) - if ptr == nil { - return nil, fmt.Errorf("thetadatadx: %s", lastError()) - } - defer C.tdx_greeks_result_free(ptr) - return &Greeks{ - Value: float64(ptr.value), - IV: float64(ptr.iv), - IVError: float64(ptr.iv_error), - Delta: float64(ptr.delta), - Gamma: float64(ptr.gamma), - Theta: float64(ptr.theta), - Vega: float64(ptr.vega), - Rho: float64(ptr.rho), - Vanna: float64(ptr.vanna), - Charm: float64(ptr.charm), - Vomma: float64(ptr.vomma), - Veta: float64(ptr.veta), - Vera: float64(ptr.vera), - Speed: float64(ptr.speed), - Zomma: float64(ptr.zomma), - Color: float64(ptr.color), - Ultima: float64(ptr.ultima), - D1: float64(ptr.d1), - D2: float64(ptr.d2), - DualDelta: float64(ptr.dual_delta), - DualGamma: float64(ptr.dual_gamma), - Epsilon: float64(ptr.epsilon), - Lambda: float64(ptr.lambda), - }, nil -} - -// ImpliedVolatility Compute implied volatility via bisection. -func ImpliedVolatility(spot float64, strike float64, rate float64, divYield float64, tte float64, optionPrice float64, right string) (float64, float64, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - cRight := C.CString(right) - defer C.free(unsafe.Pointer(cRight)) - var iv, ivErr C.double - rc := C.tdx_implied_volatility(C.double(spot), C.double(strike), C.double(rate), C.double(divYield), C.double(tte), C.double(optionPrice), cRight, &iv, &ivErr) - if rc != 0 { - return 0, 0, fmt.Errorf("thetadatadx: %s", lastError()) - } - return float64(iv), float64(ivErr), nil -} - diff --git a/sdks/go/validate.go b/sdks/go/validate.go deleted file mode 100644 index 335fa494..00000000 --- a/sdks/go/validate.go +++ /dev/null @@ -1,2710 +0,0 @@ -// Code generated by generate_sdk_surfaces from endpoint_surface.toml; DO NOT EDIT. - -package thetadatadx - -import ( - "fmt" - "reflect" - "strings" - "time" -) - -// perCellTimeoutMs caps each live cell at 60 seconds; slowModeTimeoutMs -// applies to bulk-chain / all-strike cells whose full option chain -// payload legitimately takes longer than a minute. The Rust SDK -// enforces the deadline via tokio::time::timeout and cancels the -// in-flight gRPC stream on expiry; the *Client handle stays usable. -// -const ( - perCellTimeoutMs uint64 = 60_000 - slowModeTimeoutMs uint64 = 180_000 -) - -// CellRecord is one row of the validator's per-cell JSON artifact used -// by the cross-language agreement check. `Status` is PASS|SKIP|FAIL. -// `Rationale` is the one-sentence description from the generator -// (rationale_for_mode in build_support/endpoints/modes.rs); it surfaces -// in scripts/validate_agreement.py disagreement output so a FAIL line -// carries the feature description, not just the mode name. -type CellRecord struct { - Endpoint string `json:"endpoint"` - Mode string `json:"mode"` - Rationale string `json:"rationale"` - Status string `json:"status"` - RowCount int `json:"row_count"` - DurationMS int64 `json:"duration_ms"` - Detail string `json:"detail"` -} - -// goRowCount returns len() for slices, 1 for non-nil non-slice, 0 for -// nil. Uses reflect so it works across all SDK tick types uniformly -// without a huge switch. -func goRowCount(v interface{}) int { - if v == nil { - return 0 - } - rv := reflect.ValueOf(v) - if rv.Kind() == reflect.Slice { - return rv.Len() - } - return 1 -} - -// ValidateAllEndpoints runs the live parameter-mode matrix against `c`. -// Every cell is attempted against production; the server is the ground -// truth for what the account can access. Cells whose documented min_tier -// exceeds the live account tier come back as a permission error and are -// classified as SKIP: tier-permission. Real configuration bugs surface -// as FAIL. Cells that don't finish within 60 seconds are cancelled by -// the SDK and classified as FAIL with "timeout after 60s". Returns -// (pass, skip, fail, records) — there is no `hadTimeout` flag any more -// because the SDK cleans up after itself; subsequent cells run normally -// on the same `*Client`. See issues #287, #290 and W3. -func ValidateAllEndpoints(c *Client) (int, int, int, []CellRecord) { - pass, skip, fail := 0, 0, 0 - records := make([]CellRecord, 0) - - // stock_list_symbols::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.StockListSymbols(WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_list_symbols", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_list_dates::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.StockListDates("TRADE", "AAPL", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_list_dates", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_snapshot_ohlc::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockSnapshotOHLC("AAPL", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_ohlc", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_snapshot_ohlc::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.StockSnapshotOHLC("AAPL", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_ohlc", "with_min_time", "value", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_snapshot_trade::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockSnapshotTrade("AAPL", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_trade", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_snapshot_trade::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.StockSnapshotTrade("AAPL", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_trade", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_snapshot_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockSnapshotQuote("AAPL", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_quote", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_snapshot_quote::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.StockSnapshotQuote("AAPL", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_quote", "with_min_time", "value", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_snapshot_market_value::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockSnapshotMarketValue("AAPL", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_market_value", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_snapshot_market_value::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.StockSnapshotMarketValue("AAPL", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_snapshot_market_value", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_history_eod::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockHistoryEOD("AAPL", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_eod", "concrete", "free", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_history_ohlc::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockHistoryOHLC("AAPL", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLC("AAPL", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc", "with_intraday_window", "value", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLC("AAPL", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc", "with_date_range", "value", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLC("AAPL", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc", "with_interval", "value", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLC("AAPL", "20250303", WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithVenue("nqb"), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_history_trade::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockHistoryTrade("AAPL", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_trade::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryTrade("AAPL", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_trade::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryTrade("AAPL", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade", "with_date_range", "standard", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_trade::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryTrade("AAPL", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithVenue("nqb"), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_history_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockHistoryQuote("AAPL", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_quote", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_quote::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryQuote("AAPL", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_quote", "with_intraday_window", "value", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_quote::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryQuote("AAPL", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_quote", "with_date_range", "value", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_quote::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.StockHistoryQuote("AAPL", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_quote", "with_interval", "value", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_quote::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryQuote("AAPL", "20250303", WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithVenue("nqb"), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_quote", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_history_trade_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockHistoryTradeQuote("AAPL", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade_quote", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_trade_quote::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryTradeQuote("AAPL", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade_quote", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_trade_quote::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryTradeQuote("AAPL", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade_quote", "with_date_range", "standard", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_trade_quote::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryTradeQuote("AAPL", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithExclusive(true), WithVenue("nqb"), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_trade_quote", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_at_time_trade::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockAtTimeTrade("AAPL", "20250303", "20250303", "12:00:00.000", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_at_time_trade", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_at_time_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockAtTimeQuote("AAPL", "20250303", "20250303", "12:00:00.000", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_at_time_quote", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_list_symbols::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.OptionListSymbols(WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_symbols", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_list_dates::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.OptionListDates("TRADE", "SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_dates", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_list_dates::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionListDates("TRADE", "SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_dates", "with_strike", "free", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_list_dates::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionListDates("TRADE", "SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_dates", "with_right", "free", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_list_dates::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionListDates("TRADE", "SPY", "20250321", WithStrike("570"), WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_dates", "all_optionals", "free", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_list_expirations::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.OptionListExpirations("SPY", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_expirations", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_list_strikes::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.OptionListStrikes("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_strikes", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_list_contracts::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionListContracts("TRADE", "SPY", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_contracts", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_list_contracts::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionListContracts("TRADE", "SPY", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_list_contracts", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_ohlc::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "all_exps_one_strike", "value", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "with_min_time", "value", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_ohlc::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOHLC("SPY", "20250321", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_ohlc", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_trade::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotTrade("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_trade", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_trade::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotTrade("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_trade", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_trade::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotTrade("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_trade", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_trade::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotTrade("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_trade", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_trade::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotTrade("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_trade", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_trade::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotTrade("SPY", "20250321", WithStrike("570"), WithRight("call"), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_trade", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "all_exps_one_strike", "value", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "with_min_time", "value", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_quote::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotQuote("SPY", "20250321", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_quote", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_open_interest::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "all_exps_one_strike", "value", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "with_min_time", "value", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_open_interest::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotOpenInterest("SPY", "20250321", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_open_interest", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_market_value::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_market_value::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotMarketValue("SPY", "20250321", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_market_value", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_greeks_implied_volatility::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_annual_dividend", "standard", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_rate_value", "standard", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_stock_price - // rationale: stock_price=150.0 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithStockPrice(150.0), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_stock_price", "standard", "stock_price=150.0 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_version", "standard", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::with_use_market_value - // rationale: use_market_value=true optional flag wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "with_use_market_value", "standard", "use_market_value=true optional flag wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_implied_volatility::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksImpliedVolatility("SPY", "20250321", WithStrike("570"), WithRight("call"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithStockPrice(150.0), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_implied_volatility", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_greeks_all::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_stock_price - // rationale: stock_price=150.0 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithStockPrice(150.0), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_stock_price", "professional", "stock_price=150.0 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_min_time", "professional", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::with_use_market_value - // rationale: use_market_value=true optional flag wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "with_use_market_value", "professional", "use_market_value=true optional flag wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_all::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksAll("SPY", "20250321", WithStrike("570"), WithRight("call"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithStockPrice(150.0), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_all", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_greeks_first_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_annual_dividend", "standard", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_rate_value", "standard", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_stock_price - // rationale: stock_price=150.0 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithStockPrice(150.0), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_stock_price", "standard", "stock_price=150.0 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_version", "standard", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::with_use_market_value - // rationale: use_market_value=true optional flag wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "with_use_market_value", "standard", "use_market_value=true optional flag wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_first_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksFirstOrder("SPY", "20250321", WithStrike("570"), WithRight("call"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithStockPrice(150.0), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_first_order", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_greeks_second_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_stock_price - // rationale: stock_price=150.0 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithStockPrice(150.0), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_stock_price", "professional", "stock_price=150.0 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_min_time", "professional", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::with_use_market_value - // rationale: use_market_value=true optional flag wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "with_use_market_value", "professional", "use_market_value=true optional flag wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_second_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksSecondOrder("SPY", "20250321", WithStrike("570"), WithRight("call"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithStockPrice(150.0), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_second_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_snapshot_greeks_third_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "*", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_stock_price - // rationale: stock_price=150.0 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithStockPrice(150.0), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_stock_price", "professional", "stock_price=150.0 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_min_time", "professional", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::with_use_market_value - // rationale: use_market_value=true optional flag wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "with_use_market_value", "professional", "use_market_value=true optional flag wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_snapshot_greeks_third_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionSnapshotGreeksThirdOrder("SPY", "20250321", WithStrike("570"), WithRight("call"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithStockPrice(150.0), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithMinTime("09:45:00"), WithUseMarketValue(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_snapshot_greeks_third_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_eod::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "20250321", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "concrete", "free", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_eod::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "*", "20250303", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "all_exps_one_strike", "free", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_eod::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "20250321", "20250303", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "with_strike", "free", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_eod::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "20250321", "20250303", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "with_right", "free", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_eod::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "20250321", "20250303", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "with_max_dte", "free", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_eod::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "20250321", "20250303", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "with_strike_range", "free", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_eod::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryEOD("SPY", "20250321", "20250303", "20250303", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_eod", "all_optionals", "free", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_ohlc::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "with_intraday_window", "value", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "with_date_range", "value", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "with_interval", "value", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_ohlc::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOHLC("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_ohlc", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "with_date_range", "standard", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTrade("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "all_exps_one_strike", "value", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_intraday_window", "value", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_date_range", "value", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_interval", "value", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_quote::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryQuote("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_quote", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "with_date_range", "standard", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_quote::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeQuote("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithExclusive(true), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_quote", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_open_interest::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "all_exps_one_strike", "value", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "with_date_range", "value", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_open_interest::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryOpenInterest("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_open_interest", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_greeks_eod::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "*", "20250303", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_annual_dividend", "standard", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_rate_value", "standard", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_version", "standard", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_underlyer_use_nbbo - // rationale: underlyer_use_nbbo=true optional flag wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithUnderlyerUseNBBO(true), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_underlyer_use_nbbo", "standard", "underlyer_use_nbbo=true optional flag wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_eod::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksEOD("SPY", "20250321", "20250303", "20250303", WithStrike("570"), WithRight("call"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithUnderlyerUseNBBO(true), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_eod", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_greeks_all::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_interval", "professional", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_all::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksAll("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_all", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade_greeks_all::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_all::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksAll("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_all", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_greeks_first_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_date_range", "standard", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_interval", "standard", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_annual_dividend", "standard", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_rate_value", "standard", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_version", "standard", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_first_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksFirstOrder("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_first_order", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade_greeks_first_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_first_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksFirstOrder("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_first_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_greeks_second_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_interval", "professional", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_second_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksSecondOrder("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_second_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade_greeks_second_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_second_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksSecondOrder("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_second_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_greeks_third_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_interval", "professional", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_third_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksThirdOrder("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_third_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade_greeks_third_order::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_third_order::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksThirdOrder("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_third_order", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_greeks_implied_volatility::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_date_range", "standard", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_interval", "standard", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_annual_dividend", "standard", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_rate_value", "standard", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_version", "standard", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_greeks_implied_volatility::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_greeks_implied_volatility", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_history_trade_greeks_implied_volatility::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "concrete", "professional", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "*", "20250303", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "all_exps_one_strike", "professional", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_intraday_window", "professional", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_date_range", "professional", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_strike", "professional", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_right", "professional", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_annual_dividend - // rationale: annual_dividend=0.015 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithAnnualDividend(0.015), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_annual_dividend", "professional", "annual_dividend=0.015 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_rate_value - // rationale: rate_value=0.05 optional Greeks-input wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithRateValue(0.05), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_rate_value", "professional", "rate_value=0.05 optional Greeks-input wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_version - // rationale: version=dg3 optional Greeks-version selector wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithVersion("dg3"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_version", "professional", "version=dg3 optional Greeks-version selector wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_max_dte", "professional", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "with_strike_range", "professional", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_history_trade_greeks_implied_volatility::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionHistoryTradeGreeksImpliedVolatility("SPY", "20250321", "20250303", WithStrike("570"), WithRight("call"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithAnnualDividend(0.015), WithRateType("sofr"), WithRateValue(0.05), WithVersion("dg3"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_history_trade_greeks_implied_volatility", "all_optionals", "professional", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_at_time_trade::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_trade::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "*", "20250303", "20250303", "12:00:00.000", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "all_exps_one_strike", "standard", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_trade::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "with_strike", "standard", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_trade::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "with_right", "standard", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_trade::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "with_max_dte", "standard", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_trade::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "with_strike_range", "standard", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_trade::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeTrade("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_trade", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // option_at_time_quote::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_quote::all_exps_one_strike - // rationale: expiration=* — sent as literal `*` on the wire (server fan-out) - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "*", "20250303", "20250303", "12:00:00.000", WithRight("both"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "all_exps_one_strike", "value", "expiration=* — sent as literal `*` on the wire (server fan-out)", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_quote::with_strike - // rationale: strike=570 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithStrike("570"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "with_strike", "value", "strike=570 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_quote::with_right - // rationale: right=call optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithRight("call"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "with_right", "value", "right=call optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_quote::with_max_dte - // rationale: max_dte=30 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithMaxDTE(int32(30)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "with_max_dte", "value", "max_dte=30 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_quote::with_strike_range - // rationale: strike_range=10 optional filter wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "with_strike_range", "value", "strike_range=10 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // option_at_time_quote::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.OptionAtTimeQuote("SPY", "20250321", "20250303", "20250303", "12:00:00.000", WithStrike("570"), WithRight("call"), WithMaxDTE(int32(30)), WithStrikeRange(int32(10)), WithTimeoutMs(perCellTimeoutMs)) - records = classify("option_at_time_quote", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_list_symbols::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.IndexListSymbols(WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_list_symbols", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_list_dates::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.IndexListDates("SPX", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_list_dates", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_snapshot_ohlc::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexSnapshotOHLC("SPX", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_snapshot_ohlc", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_snapshot_ohlc::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.IndexSnapshotOHLC("SPX", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_snapshot_ohlc", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_snapshot_price::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexSnapshotPrice("SPX", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_snapshot_price", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_snapshot_price::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.IndexSnapshotPrice("SPX", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_snapshot_price", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_snapshot_market_value::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexSnapshotMarketValue("SPX", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_snapshot_market_value", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_snapshot_market_value::with_min_time - // rationale: min_time=09:45:00 optional filter wiring - { - t0 := time.Now() - v, e := c.IndexSnapshotMarketValue("SPX", WithMinTime("09:45:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_snapshot_market_value", "with_min_time", "standard", "min_time=09:45:00 optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_history_eod::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexHistoryEOD("SPX", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_eod", "concrete", "free", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_history_ohlc::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexHistoryOHLC("SPX", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_ohlc", "concrete", "standard", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_ohlc::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.IndexHistoryOHLC("SPX", "20250303", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_ohlc", "with_intraday_window", "standard", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_ohlc::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.IndexHistoryOHLC("SPX", "20250303", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_ohlc", "with_interval", "standard", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_ohlc::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.IndexHistoryOHLC("SPX", "20250303", "20250303", WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_ohlc", "all_optionals", "standard", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_history_price::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexHistoryPrice("SPX", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_price", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_price::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.IndexHistoryPrice("SPX", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_price", "with_intraday_window", "value", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_price::with_date_range - // rationale: start_date + end_date pair — date range optional wiring - { - t0 := time.Now() - v, e := c.IndexHistoryPrice("SPX", "20250303", WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_price", "with_date_range", "value", "start_date + end_date pair — date range optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_price::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.IndexHistoryPrice("SPX", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_price", "with_interval", "value", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // index_history_price::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.IndexHistoryPrice("SPX", "20250303", WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithStartDate("20250303"), WithEndDate("20250303"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_history_price", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // index_at_time_price::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.IndexAtTimePrice("SPX", "20250303", "20250303", "12:00:00.000", WithTimeoutMs(perCellTimeoutMs)) - records = classify("index_at_time_price", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // calendar_open_today::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.CalendarOpenToday(WithTimeoutMs(perCellTimeoutMs)) - records = classify("calendar_open_today", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // calendar_on_date::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.CalendarOnDate("20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("calendar_on_date", "basic", "value", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // calendar_year::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.CalendarYear("2025", WithTimeoutMs(perCellTimeoutMs)) - records = classify("calendar_year", "basic", "value", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // interest_rate_history_eod::basic - // rationale: list/calendar/rate baseline call — no parameter variation - { - t0 := time.Now() - v, e := c.InterestRateHistoryEOD("SOFR", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("interest_rate_history_eod", "basic", "free", "list/calendar/rate baseline call — no parameter variation", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - // stock_history_ohlc_range::concrete - // rationale: required params set, no optionals — baseline wire path - { - t0 := time.Now() - v, e := c.StockHistoryOHLCRange("AAPL", "20250303", "20250303", WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc_range", "concrete", "value", "required params set, no optionals — baseline wire path", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc_range::with_intraday_window - // rationale: start_time + end_time pair — intraday window optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLCRange("AAPL", "20250303", "20250303", WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc_range", "with_intraday_window", "value", "start_time + end_time pair — intraday window optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc_range::with_interval - // rationale: interval=1m optional filter wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLCRange("AAPL", "20250303", "20250303", WithInterval("1m"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc_range", "with_interval", "value", "interval=1m optional filter wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - // stock_history_ohlc_range::all_optionals - // rationale: every applicable optional set at once — proves multi-optional wiring - { - t0 := time.Now() - v, e := c.StockHistoryOHLCRange("AAPL", "20250303", "20250303", WithInterval("1m"), WithStartTime("09:30:00"), WithEndTime("10:00:00"), WithVenue("nqb"), WithTimeoutMs(perCellTimeoutMs)) - records = classify("stock_history_ohlc_range", "all_optionals", "value", "every applicable optional set at once — proves multi-optional wiring", v, e, time.Since(t0), &pass, &skip, &fail, records) - } - - return pass, skip, fail, records -} - -// classify maps a live call outcome into PASS / SKIP / FAIL buckets and -// appends a CellRecord for the agreement-check JSON artifact. -func classify(endpoint, mode, declaredMinTier, rationale string, v interface{}, err error, dur time.Duration, pass, skip, fail *int, records []CellRecord) []CellRecord { - label := endpoint + "::" + mode - rec := CellRecord{Endpoint: endpoint, Mode: mode, Rationale: rationale, RowCount: goRowCount(v), DurationMS: dur.Milliseconds()} - if err == nil { - fmt.Printf(" %-60s PASS\n", label) - *pass++ - rec.Status = "PASS" - return append(records, rec) - } - lowered := strings.ToLower(err.Error()) - // Per-call deadline elapsed: SDK cancelled the in-flight gRPC stream - // (the next cell runs normally on the same *Client). - if strings.Contains(lowered, "request deadline exceeded") { - secs := int(dur / time.Second) - fmt.Printf(" %-60s FAIL timeout after %ds\n", label, secs) - *fail++ - rec.Status = "FAIL" - rec.Detail = fmt.Sprintf("timeout after %ds", secs) - rec.RowCount = 0 - return append(records, rec) - } - if strings.Contains(lowered, "permission") || strings.Contains(lowered, "subscription") { - fmt.Printf(" %-60s SKIP: tier-permission (declared min_tier=%s)\n", label, declaredMinTier) - *skip++ - rec.Status = "SKIP" - rec.Detail = "tier-permission" - rec.RowCount = 0 - return append(records, rec) - } - if strings.Contains(lowered, "no data found") { - fmt.Printf(" %-60s PASS (no data)\n", label) - *pass++ - rec.Status = "PASS" - rec.Detail = "no data" - rec.RowCount = 0 - return append(records, rec) - } - fmt.Printf(" %-60s FAIL %v\n", label, err) - *fail++ - rec.Status = "FAIL" - // gRPC / transport errors can contain embedded newlines; escape them - // so the agreement-table row stays single-line. - detail := strings.ReplaceAll(err.Error(), "\n", "\\n") - detail = strings.ReplaceAll(detail, "\r", "\\r") - if len(detail) > 200 { - detail = detail[:200] - } - rec.Detail = detail - rec.RowCount = 0 - return append(records, rec) -} diff --git a/sdks/python/Cargo.lock b/sdks/python/Cargo.lock index 51a34c4a..7b96c13d 100644 --- a/sdks/python/Cargo.lock +++ b/sdks/python/Cargo.lock @@ -2330,7 +2330,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.28" +version = "8.0.29" dependencies = [ "disruptor", "metrics", @@ -2364,7 +2364,7 @@ dependencies = [ [[package]] name = "thetadatadx-py" -version = "8.0.28" +version = "8.0.29" dependencies = [ "arrow", "arrow-array", diff --git a/sdks/python/Cargo.toml b/sdks/python/Cargo.toml index 4df19174..2b2f78b2 100644 --- a/sdks/python/Cargo.toml +++ b/sdks/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-py" -version = "8.0.28" +version = "8.0.29" edition = "2021" description = "Python bindings for thetadatadx — native ThetaData SDK powered by Rust" license = "Apache-2.0" diff --git a/sdks/python/tests/test_dropped_events.py b/sdks/python/tests/test_dropped_events.py index 8cd6f6b5..43f5e12e 100644 --- a/sdks/python/tests/test_dropped_events.py +++ b/sdks/python/tests/test_dropped_events.py @@ -13,8 +13,7 @@ Gated on `THETADX_TEST_CREDS=path/to/creds.txt` because `ThetaDataDx` needs a live FPSS handshake. Tests skip silently on developer -machines that haven't wired creds. CI runs this in the surfaces job -(same pattern as `sdks/go/timeout_test.go`). +machines that haven't wired creds. CI runs this in the surfaces job. What this test does NOT assert: diff --git a/sdks/typescript/Cargo.lock b/sdks/typescript/Cargo.lock index 40093b73..b96ed0a0 100644 --- a/sdks/typescript/Cargo.lock +++ b/sdks/typescript/Cargo.lock @@ -1954,7 +1954,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.28" +version = "8.0.29" dependencies = [ "disruptor", "metrics", @@ -1988,7 +1988,7 @@ dependencies = [ [[package]] name = "thetadatadx-napi" -version = "8.0.28" +version = "8.0.29" dependencies = [ "chrono", "napi", diff --git a/sdks/typescript/Cargo.toml b/sdks/typescript/Cargo.toml index d4990a66..8e94e2e8 100644 --- a/sdks/typescript/Cargo.toml +++ b/sdks/typescript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-napi" -version = "8.0.28" +version = "8.0.29" edition = "2021" description = "TypeScript/Node.js bindings for thetadatadx — native ThetaData SDK powered by Rust" license = "Apache-2.0" diff --git a/sdks/typescript/__tests__/dropped_events.test.mjs b/sdks/typescript/__tests__/dropped_events.test.mjs index 18552184..a11fc598 100644 --- a/sdks/typescript/__tests__/dropped_events.test.mjs +++ b/sdks/typescript/__tests__/dropped_events.test.mjs @@ -14,7 +14,7 @@ // Gated on THETADX_TEST_CREDS=/path/to/creds.txt — the underlying // `ThetaDataDx.connectFromFile(...)` needs a live FPSS handshake. // Skips silently on dev machines without creds; CI runs this in the -// surfaces job. Same pattern as sdks/go/timeout_test.go. +// surfaces job. import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml index a4014540..15f1986c 100644 --- a/tools/cli/Cargo.toml +++ b/tools/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-cli" -version = "8.0.28" +version = "8.0.29" edition.workspace = true rust-version.workspace = true authors.workspace = true diff --git a/tools/cli/src/main.rs b/tools/cli/src/main.rs index bb9af7ac..c211d43e 100644 --- a/tools/cli/src/main.rs +++ b/tools/cli/src/main.rs @@ -448,12 +448,12 @@ async fn connect( // // These build `sonic_rs::Value` directly from the raw tick struct fields so // the cross-language agreement check can compare apples-to-apples with the -// Python / Go / C++ SDKs, which expose raw ints for dates and ms-of-day. See +// Python / C++ SDKs, which expose raw ints for dates and ms-of-day. See // scripts/validate_agreement.py for the canonical contract. // // Sentinel semantics (`date == 0`, `ms_of_day < 0`) are preserved verbatim -// here -- Python (sdks/python/src/tick_columnar.rs) and Go (sdks/go/tick_structs.go) -// emit those same sentinels as raw ints, and the server emitter +// here -- Python (sdks/python/src/tick_columnar.rs) emits those same +// sentinels as raw ints, and the server emitter // (tools/server/src/format.rs:346) does too. Normalization to `null` lives // entirely on the consumer side in scripts/validate_agreement.py so all // producers can stay stupid-simple passthroughs and the agreement check has @@ -501,9 +501,8 @@ fn raw_str(value: &str) -> sonic_rs::Value { } /// Canonical `right` representation for tick types (NOT `OptionContract`). -/// Matches `sdks/python/src/tick_columnar.rs` (`"C"` / `"P"` / `""`) and -/// `sdks/go/tick_structs.go` `RightStr` (same mapping). Server uses the -/// same mapping for the option-tick contract-id helper. +/// Matches `sdks/python/src/tick_columnar.rs` (`"C"` / `"P"` / `""`). Server +/// uses the same mapping for the option-tick contract-id helper. fn raw_right_label(is_call: bool, is_put: bool) -> sonic_rs::Value { if is_call { sonic_rs::Value::from("C") @@ -1478,8 +1477,7 @@ mod tests { #[test] fn raw_right_label_matches_python_string_mapping() { - // Mirrors sdks/python/src/tick_columnar.rs:41 ("C" / "P" / "") - // and Go RightStr at sdks/go/tick_structs.go:215. + // Mirrors sdks/python/src/tick_columnar.rs:41 ("C" / "P" / ""). assert_eq!(raw_right_label(true, false).as_str(), Some("C")); assert_eq!(raw_right_label(false, true).as_str(), Some("P")); assert_eq!(raw_right_label(false, false).as_str(), Some("")); diff --git a/tools/mcp/Cargo.lock b/tools/mcp/Cargo.lock index c85c41c0..5664a2e8 100644 --- a/tools/mcp/Cargo.lock +++ b/tools/mcp/Cargo.lock @@ -1956,7 +1956,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.28" +version = "8.0.29" dependencies = [ "disruptor", "metrics", @@ -1990,7 +1990,7 @@ dependencies = [ [[package]] name = "thetadatadx-mcp" -version = "8.0.28" +version = "8.0.29" dependencies = [ "json_canon", "serde", diff --git a/tools/mcp/Cargo.toml b/tools/mcp/Cargo.toml index dde1bdf3..0ad78e9c 100644 --- a/tools/mcp/Cargo.toml +++ b/tools/mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-mcp" -version = "8.0.28" +version = "8.0.29" edition = "2021" description = "MCP server for ThetaDataDx — gives LLMs instant access to ThetaData market data" license = "Apache-2.0" diff --git a/tools/mcp/src/main.rs b/tools/mcp/src/main.rs index 6dbec5f4..82889948 100644 --- a/tools/mcp/src/main.rs +++ b/tools/mcp/src/main.rs @@ -615,9 +615,7 @@ fn serialize_greeks_all_ticks(ticks: &[tdbe::types::tick::GreeksAllTick]) -> Val json!({ "ticks": rows, "count": rows.len() }) } -fn serialize_greeks_first_order_ticks( - ticks: &[tdbe::types::tick::GreeksFirstOrderTick], -) -> Value { +fn serialize_greeks_first_order_ticks(ticks: &[tdbe::types::tick::GreeksFirstOrderTick]) -> Value { let rows: Vec = ticks .iter() .map(|t| { @@ -661,9 +659,7 @@ fn serialize_greeks_second_order_ticks( json!({ "ticks": rows, "count": rows.len() }) } -fn serialize_greeks_third_order_ticks( - ticks: &[tdbe::types::tick::GreeksThirdOrderTick], -) -> Value { +fn serialize_greeks_third_order_ticks(ticks: &[tdbe::types::tick::GreeksThirdOrderTick]) -> Value { let rows: Vec = ticks .iter() .map(|t| { diff --git a/tools/server/Cargo.lock b/tools/server/Cargo.lock index 9f3b2602..e94c7634 100644 --- a/tools/server/Cargo.lock +++ b/tools/server/Cargo.lock @@ -2349,7 +2349,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.28" +version = "8.0.29" dependencies = [ "disruptor", "metrics", @@ -2383,7 +2383,7 @@ dependencies = [ [[package]] name = "thetadatadx-server" -version = "8.0.28" +version = "8.0.29" dependencies = [ "axum", "clap", diff --git a/tools/server/Cargo.toml b/tools/server/Cargo.toml index 0924e1b8..9448a7b8 100644 --- a/tools/server/Cargo.toml +++ b/tools/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-server" -version = "8.0.28" +version = "8.0.29" edition = "2021" rust-version = "1.85" authors = ["userFRM"] diff --git a/tools/server/src/format.rs b/tools/server/src/format.rs index 9523b223..d0ab80b6 100644 --- a/tools/server/src/format.rs +++ b/tools/server/src/format.rs @@ -321,9 +321,7 @@ pub fn greeks_all_ticks_to_json(ticks: &[GreeksAllTick]) -> Vec /// Convert first-order Greeks subset ticks /// (`option_*_greeks_first_order`) to JSON array. -pub fn greeks_first_order_ticks_to_json( - ticks: &[GreeksFirstOrderTick], -) -> Vec { +pub fn greeks_first_order_ticks_to_json(ticks: &[GreeksFirstOrderTick]) -> Vec { ticks .iter() .map(|t| { @@ -351,9 +349,7 @@ pub fn greeks_first_order_ticks_to_json( /// Convert second-order Greeks subset ticks /// (`option_*_greeks_second_order`) to JSON array. -pub fn greeks_second_order_ticks_to_json( - ticks: &[GreeksSecondOrderTick], -) -> Vec { +pub fn greeks_second_order_ticks_to_json(ticks: &[GreeksSecondOrderTick]) -> Vec { ticks .iter() .map(|t| { @@ -381,9 +377,7 @@ pub fn greeks_second_order_ticks_to_json( /// Convert third-order Greeks subset ticks /// (`option_*_greeks_third_order`) to JSON array. The vendor's /// third-order schema does not publish `vera`, hence its absence here. -pub fn greeks_third_order_ticks_to_json( - ticks: &[GreeksThirdOrderTick], -) -> Vec { +pub fn greeks_third_order_ticks_to_json(ticks: &[GreeksThirdOrderTick]) -> Vec { ticks .iter() .map(|t| { From 225c99070eca423b43b99399519def147ccb15f6 Mon Sep 17 00:00:00 2001 From: userFRM Date: Wed, 6 May 2026 11:53:22 +0200 Subject: [PATCH 2/3] fix(docs): drop Go SDK assertion from check_docs_consistency.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The script asserted `sdks/README.md` contains a sentence about Windows being validated with the GNU Rust target — that sentence lived inside a Go SDK bullet (cgo links through MinGW). Go SDK removed in this PR; sentence removed from the README; the assertion is now stale and the \`Extended Surfaces\` CI gate fails on it. Drop the assertion. C++ and Python/TS Windows validation is already documented in the surviving bullets. --- scripts/check_docs_consistency.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/check_docs_consistency.py b/scripts/check_docs_consistency.py index 5611f0ac..1ca98c00 100755 --- a/scripts/check_docs_consistency.py +++ b/scripts/check_docs_consistency.py @@ -162,7 +162,6 @@ def check_static_docs() -> None: expect_contains(sdk_overview, "`TdxUnified` / `TdxFpssHandle`") expect_contains(sdk_overview, "| **Unified** | `tdx_unified_connect`, `tdx_unified_historical`, `tdx_unified_*`, `tdx_unified_free` |") expect_contains(sdk_overview, "26 functions: `tdx_fpss_connect`") - expect_contains(sdk_overview, "Windows is validated with the GNU Rust target (`x86_64-pc-windows-gnu`)") option_docs = list((ROOT / "docs-site/docs/historical/option").rglob("*.md")) strike_docs = option_docs + [ From f8c50e2b88fd52632179fc8024ce57a7e36a4c2d Mon Sep 17 00:00:00 2001 From: userFRM Date: Wed, 6 May 2026 12:05:42 +0200 Subject: [PATCH 3/3] fix(tests): drop Go producer from validate_agreement_test.py The agreement-test fixtures wrote artifacts under producer key "go", but `scripts/validate_agreement.py::LANGS` only reads python/cli/cpp after the Go SDK removal. The disagreement-detection tests therefore silently passed (the diff engine ignored the "go" artifact carrying the disagreement). Bulk-replace "go" -> "cli" for inert fixture rows; rewrite the two disagreement-anchor tests so the disagreeing artifact is written by a producer the loop does not subsequently overwrite. 29/29 tests pass. --- scripts/validate_agreement_test.py | 76 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/scripts/validate_agreement_test.py b/scripts/validate_agreement_test.py index 4c5837c4..4d4048f6 100644 --- a/scripts/validate_agreement_test.py +++ b/scripts/validate_agreement_test.py @@ -109,7 +109,7 @@ def test_field_level_diff_pinpoints_bid(self) -> None: go_row["bid"] = 685.87 _write_artifact( self.artifacts, - "go", + "cli", [_base_record("stock_snapshot_quote", "concrete", first_row=go_row)], ) @@ -127,7 +127,7 @@ def test_field_level_diff_pinpoints_bid(self) -> None: def test_row_count_disagreement_without_first_row(self) -> None: # Legacy artifacts: no first_row field. Diff engine must fall # back to row_count comparison and still report the mismatch. - for lang, rc in (("python", 10), ("cli", 10), ("go", 9), ("cpp", 10)): + for lang, rc in (("python", 10), ("cli", 10), ("cli", 9), ("cpp", 10)): _write_artifact( self.artifacts, lang, @@ -138,7 +138,7 @@ def test_row_count_disagreement_without_first_row(self) -> None: self.assertIn("stock_history_ohlc::concrete", err) self.assertIn("row_count disagreement", err) # Per-SDK status table still names each SDK and its row count. - self.assertIn("go", err) + self.assertIn("cli", err) self.assertIn("9", err) self.assertIn("10", err) @@ -150,7 +150,7 @@ def test_status_disagreement(self) -> None: "python", [_base_record("option_snapshot_trade", "concrete")], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -178,7 +178,7 @@ def test_nested_first_row_diff(self) -> None: "symbol": "SPY", "greeks": {"delta": [0.5, 0.6], "gamma": 0.01}, } - for lang in ("python", "cli", "go"): + for lang in ("python", "cli", "cli"): _write_artifact( self.artifacts, lang, @@ -206,7 +206,7 @@ def test_missing_field_in_one_sdk(self) -> None: # surface it). Diff must mark volume as for cpp and # show the real value for the others. full_row = {"price": 100.0, "volume": 5000} - for lang in ("python", "cli", "go"): + for lang in ("python", "cli", "cli"): _write_artifact( self.artifacts, lang, @@ -227,7 +227,7 @@ def test_missing_field_in_one_sdk(self) -> None: def test_soft_skip_missing_sdk_without_require(self) -> None: # Only 3 SDKs reported; without --require-all-sdks this is a # warning, not a failure. - for lang in ("python", "cli", "go"): + for lang in ("python", "cli", "cli"): _write_artifact( self.artifacts, lang, @@ -239,7 +239,7 @@ def test_soft_skip_missing_sdk_without_require(self) -> None: self.assertIn("1 cells agree across", out) def test_require_all_sdks_fails_on_missing(self) -> None: - for lang in ("python", "cli", "go"): + for lang in ("python", "cli", "cli"): _write_artifact( self.artifacts, lang, @@ -253,7 +253,7 @@ def test_float_precision_tolerance(self) -> None: # 685.860000 == 685.8600004 after 6-decimal rounding. These # must compare equal; the diff engine must not flag a false # positive on 1-ULP float noise. - for lang, bid in (("python", 685.86), ("cli", 685.8600004), ("go", 685.86), ("cpp", 685.8599996)): + for lang, bid in (("python", 685.86), ("cli", 685.8600004), ("cli", 685.86), ("cpp", 685.8599996)): _write_artifact( self.artifacts, lang, @@ -266,7 +266,7 @@ def test_float_precision_tolerance(self) -> None: def test_partial_cells_note(self) -> None: # CLI skips per-optional-param mode; the other three SDKs run # it. Not a disagreement, but the summary must mention partial. - for lang in ("python", "go", "cpp"): + for lang in ("python", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -295,7 +295,7 @@ def test_mixed_case_keys_canonicalize(self) -> None: "python", [_base_record("stock_snapshot_quote", "concrete", first_row={"Bid": 685.86, "Ask": 685.88})], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -320,7 +320,7 @@ def test_mixed_case_key_nested_canonicalize(self) -> None: ) ], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -348,7 +348,7 @@ def test_nan_normalizes_to_null(self) -> None: "python", [_base_record("option_snapshot_greeks_all", "concrete", first_row={"delta": float("nan")})], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -363,7 +363,7 @@ def test_infinity_normalizes_to_null(self) -> None: for lang, val in ( ("python", float("inf")), ("cli", float("-inf")), - ("go", None), + ("cli", None), ("cpp", None), ): _write_artifact( @@ -387,15 +387,14 @@ def test_real_disagreement_still_detected_after_canonicalization(self) -> None: ) _write_artifact( self.artifacts, - "go", + "cli", [_base_record("stock_snapshot_quote", "concrete", first_row={"bid": 685.87})], ) - for lang in ("cli", "cpp"): - _write_artifact( - self.artifacts, - lang, - [_base_record("stock_snapshot_quote", "concrete", first_row={"bid": 685.86})], - ) + _write_artifact( + self.artifacts, + "cpp", + [_base_record("stock_snapshot_quote", "concrete", first_row={"bid": 685.86})], + ) code, _, err = self._run() self.assertEqual(code, 1) # Diff table names the LOWERCASED field even though python sent @@ -413,7 +412,7 @@ def test_date_zero_sentinel_normalizes_to_null(self) -> None: for lang, val in ( ("python", 0), ("cli", 0), - ("go", None), + ("cli", None), ("cpp", None), ): _write_artifact( @@ -433,7 +432,7 @@ def test_ms_of_day_negative_sentinel_normalizes_to_null(self) -> None: for lang, val in ( ("python", -1), ("cli", -1), - ("go", None), + ("cli", None), ("cpp", None), ): _write_artifact( @@ -455,7 +454,7 @@ def test_sentinel_vs_real_value_still_disagrees(self) -> None: "python", [_base_record("stock_history_ohlc", "concrete", first_row={"date": 20260417})], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -478,7 +477,7 @@ def test_expiration_zero_sentinel_and_time_alias(self) -> None: for lang, row in ( ("python", {"expiration": 0, "time": -1}), ("cli", {"expiration": 0, "time": -1}), - ("go", {"expiration": None, "time": None}), + ("cli", {"expiration": None, "time": None}), ("cpp", {"expiration": None, "time": None}), ): _write_artifact( @@ -529,7 +528,7 @@ def test_omit_vs_null_for_expiration_agrees(self) -> None: "python", [_base_record("stock_history_ohlc", "concrete", first_row={"expiration": None})], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -554,7 +553,7 @@ def test_omit_vs_zero_for_expiration_agrees(self) -> None: ) _write_artifact( self.artifacts, - "go", + "cli", [_base_record("stock_history_ohlc", "concrete", first_row={})], ) code, out, _ = self._run() @@ -600,7 +599,7 @@ def test_strike_zero_sentinel_canonicalizes(self) -> None: "cli", [_base_record("stock_history_ohlc", "concrete", first_row={"strike": 0.0})], ) - for lang in ("go", "cpp"): + for lang in ("cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -629,7 +628,7 @@ def test_right_empty_string_and_zero_canonicalize(self) -> None: ) _write_artifact( self.artifacts, - "go", + "cli", [_base_record("stock_history_ohlc", "concrete", first_row={})], ) _write_artifact( @@ -652,15 +651,14 @@ def test_real_right_value_still_disagrees(self) -> None: ) _write_artifact( self.artifacts, - "go", + "cli", [_base_record("option_history_ohlc", "concrete", first_row={"right": "P"})], ) - for lang in ("cli", "cpp"): - _write_artifact( - self.artifacts, - lang, - [_base_record("option_history_ohlc", "concrete", first_row={"right": "C"})], - ) + _write_artifact( + self.artifacts, + "cpp", + [_base_record("option_history_ohlc", "concrete", first_row={"right": "C"})], + ) code, _, err = self._run() self.assertEqual(code, 1, "right C vs P is a real disagreement") self.assertIn("right", _diff_section(err)) @@ -686,7 +684,7 @@ def test_originally_empty_container_does_not_false_pass(self) -> None: ) _write_artifact( self.artifacts, - "go", + "cli", [_base_record("option_history_ohlc", "concrete", first_row={})], ) for lang in ("cli", "cpp"): @@ -717,7 +715,7 @@ def test_stripped_empty_container_via_sentinel_agrees(self) -> None: first_row={"contract": {"expiration": None}}, )], ) - for lang in ("cli", "go", "cpp"): + for lang in ("cli", "cli", "cpp"): _write_artifact( self.artifacts, lang, @@ -754,7 +752,7 @@ def test_stripped_empty_container_vs_absent_parent_disagrees(self) -> None: ) _write_artifact( self.artifacts, - "go", + "cli", [_base_record("option_history_ohlc", "concrete", first_row={})], ) for lang in ("cli", "cpp"):