feat(fund): add FundContext for the mutual-fund channel (all layers) - #598
Open
hogan-yuan wants to merge 12 commits into
Open
hogan-yuan wants to merge 12 commits into
hogan-yuan wants to merge 12 commits into
Conversation
Add the `fund` module with `FundContext` (async) and `FundContextSync` (blocking) covering 28 mutual-fund OpenAPI endpoints: - catalog & market data: hot funds, fund list, filters, detail, analysis / analysis detail / trend, annual & quarterly returns, performance & comparison, latest & historical NAV, top-10 holdings, reverse stock holdings - user positions: overview, single position, performance, profits, NAV history, dividends - orders & trading: order list & detail, transactions, order validate / submit / cancel Identifiers are exposed as `symbol`; the OpenAPI gateway maps them to the backend `counter_id`. Response models use `#[serde(default)]` throughout. Propagation to C / C++ / Go / Java / Node.js / Python is pending.
Mirror the grid_context C layer: `c/src/fund_context/{mod,types,context}.rs`
exposing the 28 fund methods as `lb_fund_context_*` extern "C" functions with
`C*`/`C*Owned` type pairs and `C*Options` request structs. Header
`c/csrc/include/longbridge.h` regenerated by cbindgen.
serde_json::Value ("any") fields are exposed as JSON strings; unix-second
timestamps as raw i64.
Mirror the Rust-core fund module (28 methods) across the binding layers: - C: `lb_fund_context_*` FFI (c/src/fund_context) + option structs, header regenerated. Fixes a cbindgen name collision where the fund `CFundPosition`/`CGetFundPositionsOptions` clashed with the portfolio types and corrupted longbridge.h (renamed to `CFundPositionItem` / `CFundPositionsOptions`). - C++: `longbridge::fund::FundContext` wrapping the C layer; CMake now compiles fund_context.cpp into the shared lib. - Java: `com.longbridge.fund.FundContext` (JNI + gson classes). - Node.js: napi `FundContext` (position entry exposed as `FundHoldingPosition` to avoid the trade `FundPosition` clash). - Python: PyO3 `FundContext` + openapi.pyi stub. Server-defined "any" JSON fields are surfaced as raw JSON strings across all layers; unix-second timestamps as integers; numeric-string fields as strings. Known follow-up: the fund response structs are not yet emitted into the public C header (reached via the void* async-result pointer); the Go SDK (separate repo) is not yet done.
Add the 39 fund response view structs to the cbindgen `[export] include` list (with `lb_fund_*_t` renames), so C consumers can read the results of the `lb_fund_context_*` calls instead of only receiving an opaque `void*`. All target typedef names are collision-free with the existing header. C and C++ builds verified.
…dpoints The fund identifier (counter_id, e.g. UT/FD/HK0000384492) contains "/", so it cannot be a URL path segment. Move the 18 single-fund endpoints onto fixed sub-paths (e.g. /v1/fund/funds/detail, /v1/fund/funds/nav) that carry the id as a `counter_id` query parameter, and expose the identifier as `counter_id` (not `symbol`). The three batch-backed endpoints — latest NAV, daily performance and held-fund performance — send the id as a one-element JSON array in a `counter_ids` query parameter, matching their backend contract. Propagated across Rust core (async + blocking), C, C++, Java, Node.js and Python. The order-flow body keeps its `symbol` field (the gateway passes the counter_id value through unchanged).
The backend returns several nested fund objects as null for many funds: FundDetail.asset_allocation, FundTrend.contrast_performances, FundPositionDetail.detail_values and FundOrderDetail.order. They were typed non-optional, so those responses failed to deserialize — e.g. `trend` on any fund without benchmark contrast data errored with "invalid type: null, expected struct FundTrendContrast". Model all four as optional/nullable across every layer: Rust `Option` (+ serde default), C nullable pointer via `COption`, C++ `std::optional`, Node.js `T | null`, Python `Optional[T]`; Java is unchanged (its gson bridge maps null to null). Regenerated longbridge.h and index.d.ts; updated openapi.pyi.
Fund endpoints are counter_id-only (request and response). Rename the remaining request-side symbol fields to counter_id across every layer: - GetFundOrdersOptions: symbols -> counter_ids (query key symbol -> counter_id) - ValidateFundOrderOptions / SubmitFundOrderOptions: symbol -> counter_id Rust core + C, C++, Java, Node.js, Python (Go handled separately). Responses were already counter_id. Backend accepts counter_id on both the order body and the orders filter (verified). Regenerated longbridge.h and index.d.ts; updated openapi.pyi.
- Rust core: make int64_str accept both JSON numbers and quoted strings (untagged), so a numeric int64 value never fails the whole response; add a regression test. This fix crosses all six layers since the core decodes to i64 before FFI. - Node.js: rename the fund position wrapper to FundHoldingPosition (drop the js_name alias) so the generated index.d.ts no longer emits a `FundPosition` alias that collided (TS2300) with the trade FundPosition class; regenerated index.d.ts / index.js. - Python: rename the fund pyclass to FundHoldingPosition so it no longer collides with the trade FundPosition when registered into the same module; update mod.rs registration and the openapi.pyi stub. - C: add CFundContext and the 14 fund option structs to the cbindgen [export.rename] map so they emit as lb_*_t like every other channel instead of raw C… names; update the C++ fund layer to the lb_*_t names. - Rust: fix a broken rustdoc intra-doc link (FundContext::list_funds -> FundContext::funds).
This was referenced Sep 28, 2026
TradeContext.fund_positions returned the fund's ISIN in a field named `symbol`. The ISIN cannot be converted back to a fund `counter_id` (the gateway does no ISIN->counter_id conversion and counter_ids have several formats), so it could not be used with the fund channel. Expose the full `counter_id` instead (the ISIN is its last `/`-separated segment). Renamed across all six layers; the Rust core keeps a serde alias for the legacy `symbol` key during transition.
…name The fund_position_response unit test still asserted position.symbol, which no longer compiles under cargo test / clippy --all-targets. Assert counter_id; the fixture still sends the legacy `symbol` JSON key, so it also proves the serde alias deserializes the old key.
The Java JNI field-mapping macro (impl_java_class!) still listed `symbol`, failing the java clippy check after the trade FundPosition.symbol -> counter_id rename. Map `counter_id` (the macro camelCases it to the Java `counterId` field).
- fund_position_response test now sends a full counter_id (UT/FD/HK0000447943) matching the documented format, plus a second case proving the legacy `symbol` key still deserializes via the serde alias. - CHANGELOG: correct the CFundPositionItem note (it IS emitted as lb_fund_position_item_t), and note the request-side filter stays symbol-based while the response returns counter_id.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New
FundContextfor the Hong Kong mutual-fund channel — 28 endpoints(catalog & market data, user positions, orders & trading) — across all six SDK
layers: Rust core (async + blocking), C, C++, Java, Node.js, Python.
Design notes
counter_id-only, request and response (e.g.UT/FD/HK0000384492); nosymbolfield. It contains/, so it's passed as acounter_idquery param(fixed sub-paths like
/v1/fund/funds/detail), never a path segment. Batchendpoints (
nav,performance,position_performance) use a one-elementcounter_idsJSON array.asset_allocation,contrast_performances,detail_values,order) are optional/nullable in every layer; int64fields accept a JSON number or a quoted string.
FundHoldingPositiontoavoid a clash with the trade channel's
FundPosition; C uses thelb_*_tnaming convention like every other channel.
Related
Go: longbridge/openapi-go#124 · CLI: longbridge/longbridge-terminal#327 · MCP: longbridge/longbridge-mcp#161 · Docs (CLI only; excluded from SDK/API reference by the counter_id policy): longbridge/developers#1269