From d8435cc21c5f3de4619e06926380660b53eedb18 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 8 Sep 2026 10:41:16 +0800 Subject: [PATCH 1/2] feat: add calendar spreads to MultiLegStrategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `MultiLegStrategy` gains `CalendarCallSpread` (`7`) and `CalendarPutSpread` (`8`) — calendar (horizontal) spreads, accepted by `TradeContext.submit_multileg` and reported back on the `multi_leg` field of `today_orders` / `history_orders` / `order_detail` and the order-changed push. Both variants are appended after `Strangle`, so the discriminants of the existing variants are unchanged. Without them the SDK parsed the two new server values as `Unknown` and could not submit either strategy. Propagated to all six layers: Rust, C, C++ (incl. both `convert()` directions), Java (JNI + `MultiLegStrategy.java`), Node.js, and Python (incl. the `openapi.pyi` stub). `longbridge.h` and `index.d.ts` regenerated. Documented in longbridge/developers#1251. --- CHANGELOG.md | 1 + c/csrc/include/longbridge.h | 8 ++++++++ c/src/trade_context/enum_types.rs | 6 ++++++ cpp/include/types.hpp | 4 ++++ cpp/src/convert.hpp | 8 ++++++++ .../java/com/longbridge/trade/MultiLegStrategy.java | 4 ++++ java/src/types/enum_types.rs | 4 +++- nodejs/index.d.ts | 6 +++++- nodejs/src/trade/types.rs | 4 ++++ python/pysrc/longbridge/openapi.pyi | 10 ++++++++++ python/src/trade/types.rs | 4 ++++ rust/src/trade/types.rs | 6 ++++++ 12 files changed, 63 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ede6a26..1a382638b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **All languages:** `MultiLegStrategy` gains `CalendarCallSpread` (`7`) and `CalendarPutSpread` (`8`) — calendar (horizontal) spreads, accepted by `TradeContext.submit_multileg` and reported back on the `multi_leg` field of order queries and the order-changed push. Both are appended after `Strangle`, so the discriminants of the existing variants are unchanged. Added across Rust, C, C++, Java, Node.js, and Python. Documented in longbridge/developers#1251 - **Rust:** `Signal.status` is now a `SignalStatus` enum (pending / active / deleted / ai-failed / filtered-by-manual / ai-submit-failed), `SignalsResponse.total` is `i32` to match the wire contract, and the `risk_level` / `display_control` fields were dropped — neither is part of the API contract nor served in production - **Rust:** `SignalContext.security_facts` now returns a typed `SecurityFact` instead of raw JSON — fact id / type / direction, the securities it is about, the factors behind it (with their anomaly test and groups), the data sources, and the natural-language `nl_info`. Adds the `FactType` and `FactDirection` enums, and `FactNlInfo::summary_tags()` / `invest_anal_tags()` / `eli_explain_tags()` for the `{tag, value}` entries the API carries as JSON inside a string - **Rust:** add `SignalContext` — strategy signals and the catalyst facts behind them. `signals` (`GET /v1/signals`) queries signals with symbol / strategy / catalyst / time-range filters and paging; `signal` (`GET /v1/signals/{signal_id}`) returns one signal including the full strategy analysis in `json_data`; `security_facts` (`GET /v1/facts/security_facts`) lists a security's fact (catalyst) events. Bindings for the other languages are not wired up yet diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index a458d19b9..40319b22a 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1343,6 +1343,14 @@ typedef enum CMultiLegStrategy { * Strangle */ MultiLegStrategyStrangle, + /** + * Calendar call spread + */ + MultiLegStrategyCalendarCallSpread, + /** + * Calendar put spread + */ + MultiLegStrategyCalendarPutSpread, } CMultiLegStrategy; /** diff --git a/c/src/trade_context/enum_types.rs b/c/src/trade_context/enum_types.rs index dc7260af3..8ab96821a 100644 --- a/c/src/trade_context/enum_types.rs +++ b/c/src/trade_context/enum_types.rs @@ -353,6 +353,12 @@ pub enum CMultiLegStrategy { /// Strangle #[c(remote = "Strangle")] MultiLegStrategyStrangle, + /// Calendar call spread + #[c(remote = "CalendarCallSpread")] + MultiLegStrategyCalendarCallSpread, + /// Calendar put spread + #[c(remote = "CalendarPutSpread")] + MultiLegStrategyCalendarPutSpread, } /// Multi-leg position direction diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index 81fb3eabd..fb7d59011 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1653,6 +1653,10 @@ enum class MultiLegStrategy Straddle, /// Strangle Strangle, + /// Calendar call spread + CalendarCallSpread, + /// Calendar put spread + CalendarPutSpread, }; /// Multi-leg position direction diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index f18c05ff9..cefb8eecc 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -1495,6 +1495,10 @@ convert(CMultiLegStrategy strategy) return MultiLegStrategy::Straddle; case MultiLegStrategyStrangle: return MultiLegStrategy::Strangle; + case MultiLegStrategyCalendarCallSpread: + return MultiLegStrategy::CalendarCallSpread; + case MultiLegStrategyCalendarPutSpread: + return MultiLegStrategy::CalendarPutSpread; default: return MultiLegStrategy::Unknown; } @@ -1520,6 +1524,10 @@ convert(MultiLegStrategy strategy) return MultiLegStrategyStraddle; case MultiLegStrategy::Strangle: return MultiLegStrategyStrangle; + case MultiLegStrategy::CalendarCallSpread: + return MultiLegStrategyCalendarCallSpread; + case MultiLegStrategy::CalendarPutSpread: + return MultiLegStrategyCalendarPutSpread; default: return MultiLegStrategyUnknown; } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/MultiLegStrategy.java b/java/javasrc/src/main/java/com/longbridge/trade/MultiLegStrategy.java index 7e58e4b8b..7997455e5 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/MultiLegStrategy.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/MultiLegStrategy.java @@ -20,4 +20,8 @@ public enum MultiLegStrategy { Straddle, /** Strangle */ Strangle, + /** Calendar call spread */ + CalendarCallSpread, + /** Calendar put spread */ + CalendarPutSpread, } diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 38ceb4fca..b4bc9804b 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -470,7 +470,9 @@ impl_java_enum!( VerticalPutSpread, Collar, Straddle, - Strangle + Strangle, + CalendarCallSpread, + CalendarPutSpread ] ); diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index e40e0e025..2ae18b355 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -6002,7 +6002,11 @@ export declare const enum MultiLegStrategy { /** Straddle */ Straddle = 6, /** Strangle */ - Strangle = 7 + Strangle = 7, + /** Calendar call spread */ + CalendarCallSpread = 8, + /** Calendar put spread */ + CalendarPutSpread = 9 } /** Options for listing topics created by the current authenticated user */ diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs index b251bc9c7..ccaf76ac8 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -291,6 +291,10 @@ pub enum MultiLegStrategy { Straddle, /// Strangle Strangle, + /// Calendar call spread + CalendarCallSpread, + /// Calendar put spread + CalendarPutSpread, } /// Multi-leg position direction diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 09c16f65d..00abe9d2d 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -6266,6 +6266,16 @@ class MultiLegStrategy: Strangle """ + class CalendarCallSpread(MultiLegStrategy): + """ + Calendar call spread + """ + + class CalendarPutSpread(MultiLegStrategy): + """ + Calendar put spread + """ + class MultiLegPosition: """ Multi-leg position direction diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index 0baefd345..0cdd0ef39 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -417,6 +417,10 @@ pub(crate) enum MultiLegStrategy { Straddle, /// Strangle Strangle, + /// Calendar call spread + CalendarCallSpread, + /// Calendar put spread + CalendarPutSpread, } /// Multi-leg position direction diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index 46f079c17..fbd7b0ce5 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -341,6 +341,12 @@ pub enum MultiLegStrategy { /// Strangle #[strum(to_string = "Strangle", serialize = "6")] Strangle, + /// Calendar call spread + #[strum(to_string = "CalendarCallSpread", serialize = "7")] + CalendarCallSpread, + /// Calendar put spread + #[strum(to_string = "CalendarPutSpread", serialize = "8")] + CalendarPutSpread, } /// Multi-leg position direction From 82c080d53d8f1fcddabb17d10d7b98cdc0f16873 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 20 Sep 2026 07:07:21 +0000 Subject: [PATCH 2/2] chore: resolve CHANGELOG merge conflicts Co-authored-by: sunli829 <20092316+sunli829@users.noreply.github.com> --- CHANGELOG.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb14d28b..436360a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,8 +56,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **C/C++ SDKs:** every list argument that crosses the FFI boundary now tolerates a null pointer with a zero length. `std::vector::data()` is allowed to return `nullptr` for an empty vector, which is exactly what the C++ binding passes for an omitted list argument, but the C layer fed it straight to `std::slice::from_raw_parts` — undefined behaviour that **aborts the process** under the debug UB checks. Hit live by `QuoteContext::warrant_list` with no filters (`c/src/quote_context/context.rs:784`); all 17 call sites across `quote_context`, `trade_context`, `agent_context`, `alert_context`, and `types` now go through a null-tolerant `slice_from_raw_parts` helper - **C++ SDK:** `asset::AssetContext` (`statements` / `statement_download_url`) is now actually built and usable. `longbridge.hpp` has always included `asset_context.hpp`, but `cpp/src/asset_context.cpp` was never listed in `cpp/CMakeLists.txt`, so the class was declared to users and then failed to link. It had also never compiled: it included neither `longbridge.h` nor the C declarations, and `statement_download_url` read `res->data` as a `lb_statement_download_url_response_t*` — a type that does not exist anywhere in the C layer, which delivers the URL as a bare `const char*` (the same convention as `QuoteContext::quote_level`). Fixed the include and the callback, and added the file to the build - **C SDK:** export `lb_statement_item_t` from `longbridge.h`. `CStatementItem` is only reachable through the `void*` async-result pointer, so cbindgen did not emit it and no C or C++ caller could read what `lb_asset_context_statements` returns. Also added the missing `CAssetContext` → `lb_asset_context_t` entry to the cbindgen rename map: every other context type was mapped, so the header exposed the raw Rust name (`const struct CAssetContext *lb_asset_context_new(...)`) while the C++ side forward-declared `lb_asset_context_t` -<<<<<<< HEAD -- **C/C++ SDKs:** every list argument that crosses the FFI boundary now tolerates a null pointer with a zero length. `std::vector::data()` is allowed to return `nullptr` for an empty vector, which is exactly what the C++ binding passes for an omitted list argument, but the C layer fed it straight to `std::slice::from_raw_parts` — undefined behaviour that **aborts the process** under the debug UB checks. Hit live by `QuoteContext::warrant_list` with no filters (`c/src/quote_context/context.rs`); all 17 call sites across `quote_context`, `trade_context`, `agent_context`, `alert_context`, and `types` now go through a null-tolerant `slice_from_raw_parts` helper +- **All SDKs:** document that `SubmitMultiLegOrderLeg.ratio_quantity` must be a positive number. A leg's direction comes from the `strategy` plus the order `side`, not from the sign of the ratio; the server rejects a negative or zero ratio with `602001` (`value does not match regex pattern "^([1-9]\\d*(\\.\\d+)?)$"`). Doc comments only — no behaviour or type changes +- **All SDKs:** `FundamentalContext.executive` (`GET /v1/quote/company-professionals`) sent its security in a `symbol` query parameter, but the endpoint expects the plural `symbols` (it takes a comma-separated list). The server silently ignored the unknown parameter and answered with an empty group (`total: 0`, no `symbol`, a `forward_url` missing the security id), so the call appeared to succeed while returning nothing. Verified live against `700.HK` and `AAPL.US` +- **All SDKs:** every optional response field now tolerates an explicit JSON `null`. `#[serde(default)]` alone only covers a *missing* key, so a server that sent `null` for any of ~700 optional fields aborted the whole response with `deserialize response body error: invalid type: null`. All `#[serde(default)]` response fields across every module (quote, trade, fundamental, market, dca, alert, sharelist, portfolio, calendar, content, screener, agent) now map `null` to the field's default value. First observed live as `institution_rating_detail` `"target"/"evaluate": null` (symbols without analyst coverage) and `us_company_dividends` `"recent_dividends": null` (no trailing dividends). No field types changed, so the language bindings are unaffected ### Added @@ -72,11 +73,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -======= -- **All SDKs:** document that `SubmitMultiLegOrderLeg.ratio_quantity` must be a positive number. A leg's direction comes from the `strategy` plus the order `side`, not from the sign of the ratio; the server rejects a negative or zero ratio with `602001` (`value does not match regex pattern "^([1-9]\\d*(\\.\\d+)?)$"`). Doc comments only — no behaviour or type changes -- **All SDKs:** `FundamentalContext.executive` (`GET /v1/quote/company-professionals`) sent its security in a `symbol` query parameter, but the endpoint expects the plural `symbols` (it takes a comma-separated list). The server silently ignored the unknown parameter and answered with an empty group (`total: 0`, no `symbol`, a `forward_url` missing the security id), so the call appeared to succeed while returning nothing. Verified live against `700.HK` and `AAPL.US` -- **All SDKs:** every optional response field now tolerates an explicit JSON `null`. `#[serde(default)]` alone only covers a *missing* key, so a server that sent `null` for any of ~700 optional fields aborted the whole response with `deserialize response body error: invalid type: null`. All `#[serde(default)]` response fields across every module (quote, trade, fundamental, market, dca, alert, sharelist, portfolio, calendar, content, screener, agent) now map `null` to the field's default value. First observed live as `institution_rating_detail` `"target"/"evaluate": null` (symbols without analyst coverage) and `us_company_dividends` `"recent_dividends": null` (no trailing dividends). No field types changed, so the language bindings are unaffected ->>>>>>> origin/main - **All languages:** the AI Agent streamed conversation no longer errors mid-run when the server sends an explicit `"outputs": null`. `WorkflowFinishedPayload.outputs`, `NodeToolUseFinishedPayload.outputs`, and `SubagentFinishedPayload.outputs` were annotated `#[serde(default)]`, which only covers a *missing* key, not an explicit `null` — so a `workflow_finished` / `node_tool_use_finished` / `subagent_finished` event carrying `null` outputs failed to deserialize and aborted the whole event stream (`invalid type: null, expected struct WorkflowOutputs`). These fields now map `null` to the type's default - **All languages:** likewise, list-typed fields on the streamed AI Agent event payloads no longer error on an explicit `null` (`invalid type: null, expected a sequence`). `tip_chips` (on the node / subagent / agent-tool `*_started` payloads), `WorkflowFinishedPayload.process_data`, and `SubagentStartedPayload.tools` were `#[serde(default)]`, which does not accept an explicit `null`; they now deserialize `null` to an empty list - **All languages:** clarified that `CompanyOverview.employees` is typed as a **string** (not an integer) across all SDK languages — the Longbridge API returns this field as a JSON string (e.g. `"10000"`). Doc comments have been updated to make this explicit and prevent downstream tools from incorrectly treating the value as an integer