Skip to content

Commit b28cea2

Browse files
committed
refactor: normalize endpoint request option surface
1 parent 402cfa4 commit b28cea2

11 files changed

Lines changed: 259 additions & 40 deletions

File tree

docs-site/docs/getting-started/migration-from-rest-ws.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ rows, err := client.OptionHistoryGreeksEODWithOptions(
7979
"C",
8080
"20221219",
8181
"20221220",
82-
&thetadatadx.OptionRequestOptions{StrikeRange: thetadatadx.Int32(5)},
82+
&thetadatadx.EndpointRequestOptions{StrikeRange: thetadatadx.Int32(5)},
8383
)
8484
```
8585
```cpp [C++]
86-
tdx::OptionRequestOptions options;
86+
tdx::EndpointRequestOptions options;
8787
options.strike_range = 5;
8888
auto rows = client.option_history_greeks_eod("SPY", "20230120", "0", "C", "20221219", "20221220", options);
8989
```
@@ -218,7 +218,13 @@ The official streaming API exposes `STREAM` requests with `sec_type: "INDEX"` an
218218

219219
## Rust Builder vs Fixed-Arity Bindings
220220

221-
Rust is the closest direct projection of the merged current REST routes because optional query parameters stay on builder methods.
221+
Rust is the closest direct projection of the merged current REST routes because required parameters stay in the constructor call and optional query parameters stay on builder methods. That chained style is intentional, not a special case for `strike_range`.
222+
223+
Other SDKs project the same optional surface idiomatically for their language:
224+
225+
- Python uses keyword-only optional parameters
226+
- Go uses `WithOptions` helpers plus `EndpointRequestOptions`
227+
- C++ uses `EndpointRequestOptions` overloads
222228

223229
Example:
224230

@@ -237,7 +243,7 @@ Python, Go, and C++ still expose fixed-arity convenience methods for the common
237243

238244
- exact single-date helper when the binding exposes it
239245
- dedicated range helper when the binding exposes it, such as `stock_history_ohlc_range`
240-
- Python keyword-only optional parameters, Go `WithOptions` helpers, or C++ `OptionRequestOptions` overloads when the binding exposes them
246+
- Python keyword-only optional parameters, Go `WithOptions` helpers, or C++ `EndpointRequestOptions` overloads when the binding exposes them
241247
- Rust for the most faithful 1:1 projection of the official optional query surface
242248

243249
## Worked Example

docs-site/docs/historical/option/history/greeks-eod.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ data, _ := client.OptionHistoryGreeksEODWithOptions(
4444
"C",
4545
"20260101",
4646
"20260301",
47-
&thetadatadx.OptionRequestOptions{StrikeRange: thetadatadx.Int32(5)},
47+
&thetadatadx.EndpointRequestOptions{StrikeRange: thetadatadx.Int32(5)},
4848
)
4949
for _, t := range data {
5050
fmt.Printf("date=%d implied_volatility=%.4f delta=%.4f gamma=%.4f theta=%.4f vega=%.4f rho=%.4f\n",
5151
t.Date, t.ImpliedVolatility, t.Delta, t.Gamma, t.Theta, t.Vega, t.Rho)
5252
}
5353
```
5454
```cpp [C++]
55-
tdx::OptionRequestOptions options;
55+
tdx::EndpointRequestOptions options;
5656
options.strike_range = 5;
5757
auto data = client.option_history_greeks_eod("SPY", "20260417", "0", "C", "20260101", "20260301", options);
5858
for (const auto& t : data) {

ffi/src/lib.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub struct TdxFpssHandle {
118118
rx: Arc<Mutex<std::sync::mpsc::Receiver<FfiBufferedEvent>>>,
119119
}
120120

121-
/// Optional builder parameters for option historical/snapshot requests over FFI.
121+
/// Optional builder parameters for registry-driven endpoint requests over FFI.
122122
///
123123
/// Fields use simple C-friendly sentinels:
124124
///
@@ -127,9 +127,10 @@ pub struct TdxFpssHandle {
127127
/// - floating-point values: `NaN` means unset
128128
/// - string pointers: null means unset
129129
#[repr(C)]
130-
pub struct TdxOptionRequestOptions {
130+
pub struct TdxEndpointRequestOptions {
131131
pub max_dte: i32,
132132
pub strike_range: i32,
133+
pub venue: *const c_char,
133134
pub min_time: *const c_char,
134135
pub start_time: *const c_char,
135136
pub end_time: *const c_char,
@@ -679,9 +680,9 @@ fn insert_optional_float_arg(args: &mut thetadatadx::EndpointArgs, key: &str, va
679680
}
680681
}
681682

682-
fn apply_option_request_options(
683+
fn apply_endpoint_request_options(
683684
args: &mut thetadatadx::EndpointArgs,
684-
options: *const TdxOptionRequestOptions,
685+
options: *const TdxEndpointRequestOptions,
685686
) -> Result<(), String> {
686687
if options.is_null() {
687688
return Ok(());
@@ -690,6 +691,7 @@ fn apply_option_request_options(
690691
let options = unsafe { &*options };
691692
insert_optional_int_arg(args, "max_dte", options.max_dte);
692693
insert_optional_int_arg(args, "strike_range", options.strike_range);
694+
insert_optional_str_arg(args, "venue", options.venue)?;
693695
insert_optional_str_arg(args, "min_time", options.min_time)?;
694696
insert_optional_str_arg(args, "start_time", options.start_time)?;
695697
insert_optional_str_arg(args, "end_time", options.end_time)?;
@@ -1580,8 +1582,8 @@ ffi_typed_endpoint! {
15801582

15811583
/// Fetch EOD Greeks history with optional builder parameters.
15821584
///
1583-
/// This currently enables non-Rust bindings to surface parameters such as
1584-
/// `strike_range` without hand-coding Rust builder logic in each language.
1585+
/// This currently enables non-Rust bindings to surface endpoint builder
1586+
/// parameters without hand-coding Rust builder logic in each language.
15851587
#[no_mangle]
15861588
pub unsafe extern "C" fn tdx_option_history_greeks_eod_with_options(
15871589
client: *const TdxClient,
@@ -1591,7 +1593,7 @@ pub unsafe extern "C" fn tdx_option_history_greeks_eod_with_options(
15911593
right: *const c_char,
15921594
start_date: *const c_char,
15931595
end_date: *const c_char,
1594-
options: *const TdxOptionRequestOptions,
1596+
options: *const TdxEndpointRequestOptions,
15951597
) -> TdxGreeksTickArray {
15961598
let empty = TdxGreeksTickArray {
15971599
data: ptr::null(),
@@ -1671,7 +1673,7 @@ pub unsafe extern "C" fn tdx_option_history_greeks_eod_with_options(
16711673
thetadatadx::EndpointArgValue::Str(end_date.to_string()),
16721674
);
16731675

1674-
if let Err(message) = apply_option_request_options(&mut args, options) {
1676+
if let Err(message) = apply_endpoint_request_options(&mut args, options) {
16751677
set_error(&message);
16761678
return empty;
16771679
}

scripts/check_docs_consistency.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
ENDPOINT_NAMES = {ep["name"] for ep in ENDPOINTS}
2525
REST_PATHS = {ep["rest_path"].removeprefix("/v3") for ep in ENDPOINTS}
2626
EXPECTED_TOOL_COUNT = len(ENDPOINTS) + 3
27+
BUILDER_PARAMS = {
28+
param["name"]
29+
for group in SURFACE["param_groups"].values()
30+
for param in group.get("params", [])
31+
if param.get("binding") == "builder"
32+
}
2733

2834

2935
def lower_camel(snake: str) -> str:
@@ -36,6 +42,14 @@ def fail(message: str) -> None:
3642
raise SystemExit(1)
3743

3844

45+
def snake_to_go(name: str) -> str:
46+
acronyms = {
47+
"dte": "DTE",
48+
"nbbo": "NBBO",
49+
}
50+
return "".join(acronyms.get(part, part.capitalize()) for part in name.split("_"))
51+
52+
3953
def expect_contains(path: Path, snippet: str) -> None:
4054
text = path.read_text()
4155
if snippet not in text:
@@ -160,10 +174,93 @@ def check_openapi() -> None:
160174
)
161175

162176

177+
def extract_struct_fields(path: Path, struct_pattern: str, field_pattern: str) -> set[str]:
178+
text = path.read_text()
179+
match = re.search(struct_pattern, text, re.DOTALL)
180+
if not match:
181+
fail(f"{path.relative_to(ROOT)} missing expected struct pattern: {struct_pattern!r}")
182+
return set(re.findall(field_pattern, match.group(1), re.MULTILINE))
183+
184+
185+
def check_endpoint_option_surface() -> None:
186+
rust_fields = extract_struct_fields(
187+
ROOT / "ffi/src/lib.rs",
188+
r"pub struct TdxEndpointRequestOptions \{(.*?)\n\}",
189+
r"^\s*pub\s+([a-z_]+)\s*:",
190+
)
191+
if rust_fields != BUILDER_PARAMS:
192+
missing = sorted(BUILDER_PARAMS - rust_fields)
193+
extra = sorted(rust_fields - BUILDER_PARAMS)
194+
fail(
195+
"ffi/src/lib.rs endpoint option fields drifted from endpoint_surface.toml. "
196+
f"missing={missing or '[]'} extra={extra or '[]'}"
197+
)
198+
199+
for path in [
200+
ROOT / "sdks/go/ffi_bridge.h",
201+
ROOT / "sdks/cpp/include/thetadx.h",
202+
]:
203+
c_fields = extract_struct_fields(
204+
path,
205+
r"typedef struct \{(.*?)\n\}\s*TdxEndpointRequestOptions;",
206+
r"^\s*(?:const char\*|int32_t|double)\s+([a-z_]+);",
207+
)
208+
if c_fields != BUILDER_PARAMS:
209+
missing = sorted(BUILDER_PARAMS - c_fields)
210+
extra = sorted(c_fields - BUILDER_PARAMS)
211+
fail(
212+
f"{path.relative_to(ROOT)} endpoint option fields drifted from endpoint_surface.toml. "
213+
f"missing={missing or '[]'} extra={extra or '[]'}"
214+
)
215+
216+
go_fields = extract_struct_fields(
217+
ROOT / "sdks/go/client.go",
218+
r"type EndpointRequestOptions struct \{(.*?)\n\}",
219+
r"^\s*([A-Z][A-Za-z0-9]+)\s+\*",
220+
)
221+
expected_go_fields = {snake_to_go(name) for name in BUILDER_PARAMS}
222+
if go_fields != expected_go_fields:
223+
missing = sorted(expected_go_fields - go_fields)
224+
extra = sorted(go_fields - expected_go_fields)
225+
fail(
226+
"sdks/go/client.go EndpointRequestOptions fields drifted from endpoint_surface.toml. "
227+
f"missing={missing or '[]'} extra={extra or '[]'}"
228+
)
229+
230+
cpp_fields = extract_struct_fields(
231+
ROOT / "sdks/cpp/include/thetadx.hpp",
232+
r"struct EndpointRequestOptions \{(.*?)\n\};",
233+
r"^\s*std::optional<[^>]+>\s+([a-z_]+);",
234+
)
235+
if cpp_fields != BUILDER_PARAMS:
236+
missing = sorted(BUILDER_PARAMS - cpp_fields)
237+
extra = sorted(cpp_fields - BUILDER_PARAMS)
238+
fail(
239+
"sdks/cpp/include/thetadx.hpp EndpointRequestOptions fields drifted from endpoint_surface.toml. "
240+
f"missing={missing or '[]'} extra={extra or '[]'}"
241+
)
242+
243+
for path in [
244+
ROOT / "ffi/src/lib.rs",
245+
ROOT / "sdks/go/ffi_bridge.h",
246+
ROOT / "sdks/cpp/include/thetadx.h",
247+
ROOT / "sdks/go/client.go",
248+
ROOT / "sdks/cpp/include/thetadx.hpp",
249+
ROOT / "sdks/cpp/src/thetadx.cpp",
250+
ROOT / "sdks/go/README.md",
251+
ROOT / "sdks/cpp/README.md",
252+
ROOT / "docs-site/docs/getting-started/migration-from-rest-ws.md",
253+
ROOT / "docs-site/docs/historical/option/history/greeks-eod.md",
254+
]:
255+
expect_not_contains(path, "OptionRequestOptions")
256+
expect_not_contains(path, "TdxOptionRequestOptions")
257+
258+
163259
def main() -> None:
164260
check_static_docs()
165261
check_api_reference()
166262
check_openapi()
263+
check_endpoint_option_surface()
167264
print("docs consistency: ok")
168265

169266

sdks/cpp/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ auto client = tdx::Client::connect(creds, tdx::Config::production());
165165

166166
| Method | Returns | Description |
167167
|--------|---------|-------------|
168-
| `option_history_greeks_eod(sym, exp, strike, right, start, end[, options])` | `vector<GreeksTick>` | EOD Greeks history. Optional `OptionRequestOptions` exposes filters such as `strike_range`. |
168+
| `option_history_greeks_eod(sym, exp, strike, right, start, end[, options])` | `vector<GreeksTick>` | EOD Greeks history. Optional `EndpointRequestOptions` exposes filters such as `strike_range`. |
169169
| `option_history_greeks_all(sym, exp, strike, right, date, interval)` | `vector<GreeksTick>` | All Greeks history (intraday) |
170170
| `option_history_trade_greeks_all(sym, exp, strike, right, date)` | `vector<GreeksTick>` | All Greeks on each trade |
171171
| `option_history_greeks_first_order(sym, exp, strike, right, date, interval)` | `vector<GreeksTick>` | First-order Greeks history |

sdks/cpp/include/thetadx.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ typedef struct TdxConfig TdxConfig;
3939
typedef struct TdxFpssHandle TdxFpssHandle;
4040
typedef struct TdxUnified TdxUnified;
4141

42-
/* Optional builder parameters for option historical/snapshot requests.
42+
/* Optional builder parameters for registry-driven endpoint requests.
4343
* Sentinels:
4444
* - integers: -1 means unset
4545
* - booleans: -1 unset, 0 false, 1 true
@@ -49,6 +49,7 @@ typedef struct TdxUnified TdxUnified;
4949
typedef struct {
5050
int32_t max_dte;
5151
int32_t strike_range;
52+
const char* venue;
5253
const char* min_time;
5354
const char* start_time;
5455
const char* end_time;
@@ -62,7 +63,7 @@ typedef struct {
6263
const char* version;
6364
int32_t underlyer_use_nbbo;
6465
int32_t use_market_value;
65-
} TdxOptionRequestOptions;
66+
} TdxEndpointRequestOptions;
6667

6768
/* ═══════════════════════════════════════════════════════════════════════ */
6869
/* #[repr(C)] tick types — layout-compatible with Rust tdbe structs */
@@ -625,7 +626,7 @@ TdxGreeksTickArray tdx_option_history_greeks_eod(const TdxClient* client, const
625626
TdxGreeksTickArray tdx_option_history_greeks_eod_with_options(const TdxClient* client, const char* symbol, const char* expiration,
626627
const char* strike, const char* right,
627628
const char* start_date, const char* end_date,
628-
const TdxOptionRequestOptions* options);
629+
const TdxEndpointRequestOptions* options);
629630

630631
TdxGreeksTickArray tdx_option_history_greeks_all(const TdxClient* client, const char* symbol, const char* expiration,
631632
const char* strike, const char* right,

sdks/cpp/include/thetadx.hpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,25 @@ struct Greeks {
8383
double lambda;
8484
};
8585

86-
/// Optional builder parameters for option historical/snapshot requests.
86+
/// Optional builder parameters for registry-driven endpoint wrappers.
8787
///
88-
/// The C++ wrapper uses `std::optional` so callers can opt into the same
89-
/// metadata-driven filters exposed by Rust without sentinel values.
90-
struct OptionRequestOptions {
88+
/// Rust models these as builder setters. The C++ wrapper projects the same
89+
/// surface as a value object with `std::optional` fields.
90+
struct EndpointRequestOptions {
91+
std::optional<std::string> venue;
92+
std::optional<std::string> min_time;
93+
std::optional<std::string> start_time;
94+
std::optional<std::string> end_time;
95+
std::optional<std::string> start_date;
96+
std::optional<std::string> end_date;
97+
std::optional<bool> exclusive;
9198
std::optional<double> annual_dividend;
9299
std::optional<std::string> rate_type;
93100
std::optional<double> rate_value;
101+
std::optional<double> stock_price;
94102
std::optional<std::string> version;
95103
std::optional<bool> underlyer_use_nbbo;
104+
std::optional<bool> use_market_value;
96105
std::optional<int32_t> max_dte;
97106
std::optional<int32_t> strike_range;
98107
};
@@ -391,7 +400,7 @@ class Client {
391400
std::vector<GreeksTick> option_history_greeks_eod(const std::string& symbol, const std::string& expiration,
392401
const std::string& strike, const std::string& right,
393402
const std::string& start_date, const std::string& end_date,
394-
const OptionRequestOptions& options = {}) const;
403+
const EndpointRequestOptions& options = {}) const;
395404
std::vector<GreeksTick> option_history_greeks_all(const std::string& symbol, const std::string& expiration,
396405
const std::string& strike, const std::string& right,
397406
const std::string& date, const std::string& interval) const;

0 commit comments

Comments
 (0)