Skip to content

feat!: move the option chain onto the OpenAPI endpoint - #588

Draft
sunli829 wants to merge 1 commit into
mainfrom
feat/option-chain-openapi-endpoint
Draft

feat!: move the option chain onto the OpenAPI endpoint#588
sunli829 wants to merge 1 commit into
mainfrom
feat/option-chain-openapi-endpoint

Conversation

@sunli829

@sunli829 sunli829 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Background

longbridge/developers#1244 moves the option chain off the quote socket business
commands onto the fundamental-data OpenAPI service (gemini OpenAPIOptionChain).
The contract list ships as its own plain-HTTP endpoint:

GET /v1/gemini/option/option_chain_list
  symbol         string  required  underlying, ticker.region, e.g. BABA.US
  expiry_date    string  required  YYYYMMDD, e.g. 20261218
  standard_only  bool    optional  true/1 returns standard contracts only;
                                   omitted or false returns everything

This is not a URL swap — the response shape changes, so the SDK's return type
had to be rebuilt.

What changed

QuoteContext.option_chain_info_by_date keeps its name (it still takes an
expiry date, so the name is still accurate) but is now backed by the HTTP
endpoint instead of socket command 21.

Return type. The old command paired contracts by strike price
(price + call_symbol + put_symbol + standard). The new endpoint returns
one entry per contract, so the paired StrikePriceInfo is removed in favour
of a flat OptionChainContract:

field type note
symbol string option contract code, ticker.region
expiry_date date US Eastern time
strike_price decimal
direction OptionDirection existing enum, reused
option_type OptionExpiryCycleType new enum
standard_attr OptionStandardAttr new enum
days_to_expiry int32 0 on the expiry day, negative once expired

A strike listed on one side only now yields a single entry instead of an entry
with an empty call_symbol/put_symbol. Callers that read those two fields
must filter on direction instead.

Two new enums. OptionExpiryCycleType (Unknown / Monthly / Weekly /
Quarterly) — the server's empty option_type means a standard monthly option.
OptionStandardAttr (Unknown / Normal / Old) marks the legacy contracts
left over from a corporate action. Unknown is the first variant in every layer
and is the Default, i.e. the fallback for an unrecognized server value; the
meaningful empty string maps explicitly to Monthly / Normal.

New standard_only parameter. Positional bool in Rust (incl. blocking),
C, C++ and Java; optional standardOnly?: boolean in Node.js;
standard_only: bool = False in Python. It is omitted from the query string
when false, which is the endpoint's documented "return everything" path.

Cache dropped. The 30-minute client-side cache of the chain is gone,
matching the other HTTP quote endpoints — and days_to_expiry changes daily, so
caching it was wrong anyway. The separate option_chain_expiry_date_list cache
is untouched.

cmd_code::GET_OPTION_CHAIN_INFO_BY_DATE (21) is removed; command 20 stays.

Deliberately out of scope

QuoteContext.option_chain_expiry_date_list is unchanged and still uses
socket command 20. expiry_date is a hard requirement of the new endpoint, so
it cannot enumerate expiry dates, and the gateway path for the replacement
expiry-date-list endpoint is not settled upstream (the developers PR says as
much). Probed on the canary to be sure:

request result
symbol + expiry_date=20261218 206 rows, 1 distinct expiry_date
expiry_date omitted 310010 invalid parameter
expiry_date= (empty) 310010 invalid parameter
expiry_date=0 310010 invalid parameter

Five guessed paths (option_expiry_date_list, expiry_date_list,
option_chain_date_list, option_dates, expiry_dates under
/v1/gemini/option/) all returned 404000 api not found.

Layers touched

All six, per CLAUDE.md:

  • Rustoption_chain_info_by_date rewritten on http_cli (modelled on
    security_list / option_volume); OptionChainContract + the two enums in
    quote/types.rs; blocking wrapper; exports; cmd_code.
  • Clb_option_chain_contract_t, lb_option_expiry_cycle_type_t,
    lb_option_standard_attr_t (lb_strike_price_info_t removed);
    lb_quote_context_option_chain_info_by_date gains bool standard_only
    before the callback; three cbindgen.toml renames + include entry so the
    header does not leak the raw Rust names.
  • C++quote::OptionChainContract and the two enum classes, explicit
    switch converters (not static_cast), and the copy-pasted
    option_chain_info_by_date doc comment fixed (it read "Get option chain
    expiry date list").
  • Java — new OptionChainContract / OptionExpiryCycleType /
    OptionStandardAttr classes, StrikePriceInfo deleted, java/Makefile.toml
    source list updated. The JNI parameter is jboolean (not Rust bool), and
    all three halves agree:
    (JLjava/lang/String;Ljava/time/LocalDate;ZLcom/longbridge/AsyncCallback;)V.
  • Node.jsOptionChainContract + the two napi enums;
    index.d.ts/index.js regenerated.
  • Python — sync + async, #[pyo3(signature = (…, standard_only = false))],
    three new add_class registrations, and openapi.pyi updated by hand.

Implementation note

The wire values ("C", "W", "old", "20261218", "50") are parsed in a
private RawOptionChainContract inside the method, following the option_volume
pattern. OptionDirection's existing serde representation is deliberately left
untouched, because OptionQuote embeds it. option_type, standard_attr,
symbol and days_to_expiry tolerate an explicit JSON null.

The now-dead OptionChainDateStrikeInfoRequest/Response/StrikePriceInfo
protobuf messages are left in place: openapi-protobufs cannot be regenerated
here, and as pub items of a separate crate they trigger no dead_code
warning. They will drop out with the next submodule update.

Verification

cargo clippy --all --all-features — 0 errors. cargo +nightly fmt --all.
cargo test --doc -p longbridge — 56 passed. cargo build clean for
longbridge-c / -java / -python; all CMake targets clean;
npm run build:debug clean; openapi.pyi parses.

Probed live against the canary (BABA.US / 20261218 — the same data as the
response example in the developers PR):

  • standard_only=false → 206 rows (103 calls / 103 puts, 112 of them
    standard_attr = Old, including BABA2261218C10000.US);
    standard_only=true → 94 rows, no Old.
  • All three option_type values occur in the wild, so the mapping is not just
    paper: ""Monthly (AAPL 2026-09-18), "W"Weekly (six AAPL weekly
    expiries), "Q"Quarterly (QQQ / IWM 2027-06-30).
  • days_to_expiry is negative for already-expired contracts (-5 for AAPL
    2026-09-02), as documented.
  • Zero Unknown fallbacks across every sample — no unmapped server value.

Not run here: javac / cargo make javah and maturin develop — no JDK or
maturin on this machine. java/c/com_longbridge_SdkNative.h was therefore
synced by hand (a mechanical one-line change) and should be re-generated on a
machine with a JDK. cargo build -p longbridge-python passing is the real
compile check for the PyO3 side; openapi.pyi has no build step.

🤖 Generated with Claude Code

`QuoteContext.option_chain_info_by_date` moves off quote socket business
command `21` onto the plain HTTP endpoint
`GET /v1/gemini/option/option_chain_list` (longbridge/developers#1244).

The wire shape changes with it. The old command paired contracts by strike
price (`price` + `call_symbol` + `put_symbol` + `standard`); the new endpoint
returns one entry per contract, so the paired `StrikePriceInfo` is removed in
favour of a flat `OptionChainContract` carrying `symbol`, `expiry_date`,
`strike_price`, `direction`, `option_type`, `standard_attr` and
`days_to_expiry`. A strike listed on one side only now yields a single entry
instead of an entry with an empty `call_symbol`/`put_symbol`, and callers that
read those two fields must filter on `direction` instead.

Two new enums come with the new fields: `OptionExpiryCycleType`
(`Unknown` / `Monthly` / `Weekly` / `Quarterly`) for the expiration cycle —
the server's empty `option_type` means a standard monthly option — and
`OptionStandardAttr` (`Unknown` / `Normal` / `Old`) marking the legacy
contracts left over from a corporate action. Both fall back to `Unknown` for
an unrecognized server value.

The method also gains a `standard_only` parameter that filters those legacy
contracts out server-side. It is omitted from the query string when false,
which is the endpoint's documented "return everything" default. The 30-minute
client-side cache of the chain is dropped, matching the other HTTP quote
endpoints — and `days_to_expiry` changes daily, so caching it was wrong
anyway.

`QuoteContext.option_chain_expiry_date_list` is deliberately left alone and
still uses socket command `20`: the new endpoint requires `expiry_date`, so it
cannot enumerate expiry dates, and the gateway path for the replacement
expiry-date-list endpoint is not settled upstream yet.

Verified live against the canary environment.
@sunli829
sunli829 marked this pull request as draft September 9, 2026 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant