Skip to content

feat(trade): add DelayedNotReported order status - #595

Open
sunli829 wants to merge 8 commits into
mainfrom
feat/order-status-delayed-not-reported
Open

sunli829 wants to merge 8 commits into
mainfrom
feat/order-status-delayed-not-reported

Conversation

@sunli829

@sunli829 sunli829 commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Follows longbridge/developers#1263, which documents the new DelayedNotReported order status — 监控中 (时间条件单), the state a time-conditional order sits in before it is reported to the exchange.

Problem

No SDK enum variant matched that wire value. OrderStatus deserializes through impl_serde_for_enum_string!, which falls back to unwrap_or_default() → OrderStatus::Unknown, so nothing failed loudly — monitoring time-conditional orders simply became indistinguishable from a genuinely unrecognized status in today_orders / history_orders / order_detail and the order-changed push.

Changes

All six layers, per the checklist in CLAUDE.md:

Layer File
Rust rust/src/trade/types.rs (#[strum(serialize = "DelayedNotReported")])
C c/src/trade_context/enum_types.rs; longbridge.h regenerated by cbindgen
C++ cpp/include/types.hpp + both conversion directions in cpp/src/convert.hpp
Java java/src/types/enum_types.rs + java/javasrc/.../trade/OrderStatus.java
Node.js nodejs/src/trade/types.rs; index.d.ts regenerated by npm run build:debug
Python python/src/trade/types.rs + the openapi.pyi stub

Why the variant is appended instead of grouped

The natural place would be next to the other *NotReported values, matching the docs table. It is appended after PartialWithdrawal instead, because the C/C++ and Node.js enums use implicit discriminants:

  • lb_order_status_t in longbridge.h has no explicit values — inserting mid-list shifts every following one, an ABI break for anything linked against a prebuilt C/C++ library.
  • nodejs/index.d.ts emits export declare const enum OrderStatus { … Filled = 5 … }, and TypeScript inlines const enum members at the consumer's compile time. A renumbering would make already-compiled application code read its hard-coded 5 as a different status — a silent mismatch rather than a build error.

Appending keeps every existing discriminant stable (DelayedNotReported = 18 in TS). The declaration order does not have to match the docs table.

Second commit: regenerated nodejs/index.js

npm run build:debug regenerates index.js alongside index.d.ts, and the committed copy turns out to predate the @napi-rs/cli@3.8.6 the release workflow pins — so it comes back with ~160 lines of loader churn that has nothing to do with the enum. It is committed separately (38184a7) to bring the two generated files back in step with each other and with CI. Review it as a standalone change; what the newer generator emits:

  • require('node:fs') → require('fs'), and the optional chaining in the musl / win32 probes is expanded — older-Node compatibility
  • NAPI_RS_FORCE_WASI becomes tri-state ('true' / 'error' / unset) instead of any-non-empty-string truthy, so NAPI_RS_FORCE_WASI=false or =0 no longer takes the WASI path and fails with ENOENT
  • adds NAPI_RS_WASI_FLAVOR for pinning one exact generated flavor
  • WASI candidates are require.resolve'd before being required, and the load-error chain is built without mutating the original errors; error.cause is assigned rather than passed via the Error options form, which Node < 16.9 ignores

Verification

  • cargo clippy --all --all-features — no new warnings
  • cargo +nightly fmt --all
  • cargo build -p longbridge-c — header regenerated
  • g++ -fsyntax-only -std=c++17 over cpp/src/convert.hpp — both convert() overloads compile
  • npm run build:debug — index.d.ts / index.js regenerated
  • Java classes not compiled locally (no maven available); the change is one enum constant, and the JNI macro maps by static-field name, which matches

🤖 Generated with Claude Code

The gateway reports time-conditional orders that are being monitored,
before they are reported to the exchange, with the order status
`DelayedNotReported` (see longbridge/developers#1263). No SDK enum
matched it, so `impl_serde_for_enum_string!`'s `unwrap_or_default()`
turned it into `OrderStatus::Unknown` — monitoring orders were
indistinguishable from a genuinely unrecognized status in
`today_orders` / `history_orders` / `order_detail` and the
order-changed push.

Added across all six layers: Rust, C (header regenerated by cbindgen),
C++ (enum plus both conversion directions), Java (JNI list plus the
Java enum), Node.js (`index.d.ts` regenerated) and Python (plus the
`openapi.pyi` stub).

The variant is appended after `PartialWithdrawal` rather than placed
next to the other `*NotReported` values: the C/C++ and Node.js enums
use implicit discriminants, so inserting mid-list would renumber every
following value — an ABI break for the C/C++ bindings, and a silent
mismatch for TypeScript code that inlined the old `const enum` values.
The committed index.js was generated by an older CLI than the 3.8.6 the
release workflow pins, so regenerating it alongside index.d.ts produces
loader churn unrelated to the enum change. Committing it here keeps the
two generated files in step with each other and with CI.

What the regenerated loader changes:

- `require('node:fs')` → `require('fs')` and the optional chaining in the
  musl / win32 probes is expanded, restoring older-Node compatibility
- NAPI_RS_FORCE_WASI is now tri-state ('true' / 'error' / unset) instead
  of any-non-empty-string truthy, so NAPI_RS_FORCE_WASI=false or =0 no
  longer takes the WASI path and fails with ENOENT
- adds NAPI_RS_WASI_FLAVOR for selecting one exact generated flavor
- WASI candidates are resolved before being required, and the load-error
  chain is built without mutating the original errors; `error.cause` is
  assigned rather than passed to the Error options form, which Node
  < 16.9 ignores
@sunli829
sunli829 marked this pull request as draft September 20, 2026 07:03
…ayed-not-reported

# Conflicts:
#	CHANGELOG.md
@hogan-yuan
hogan-yuan marked this pull request as ready for review October 13, 2026 10:24
…ayed-not-reported

# Conflicts:
#	CHANGELOG.md
Documented in longbridge/developers#1289. A time-condition order is held
in DelayedNotReported status and sent automatically at a set time (later
than now, at most 90 days ahead).

- Rust: SubmitOrderOptions / SubmitMultiLegOrderOptions gain
  delayed_until(OffsetDateTime); ReplaceOrderOptions gains
  delayed_until(OffsetDateTime) and clear_delayed_until() (sends 1).
  Order / OrderDetail gain delayed_until: Option<OffsetDateTime>,
  defaulting to None when the field is absent or "0".
- C: delayed_until pointers (and clear_delayed_until on replace)
  appended to the option structs; delayed_until on lb_order_t /
  lb_order_detail_t. Header regenerated by cbindgen.
- C++, Java, Node.js (index.d.ts regenerated), Python (+ openapi.pyi)
  mirror the Rust API.
Copilot AI balanced review requested due to automatic review settings October 13, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The C API has binary-layout regressions and abort-prone timestamp conversion paths.

6 open findings
What changed in this PR

Adds time-conditional order support across all SDK layers, including the DelayedNotReported status and scheduled submission fields.

Changes:

  • Adds DelayedNotReported across Rust and language bindings.
  • Adds delayed_until submission, replacement, and query support.
  • Regenerates C and Node.js artifacts and updates the changelog.
File Description
rust/​src/​trade/​types.rs Adds status and delayed-time response fields.
rust/​src/​trade/​requests/​submit_order.rs Adds delayed regular-order submission.
rust/​src/​trade/​requests/​submit_multileg.rs Adds delayed multi-leg submission.
rust/​src/​trade/​requests/​replace_order.rs Adds updating and clearing delayed submission.
python/​src/​trade/​types.rs Exposes status and response fields to Python.
python/​src/​trade/​context.rs Adds synchronous Python parameters.
python/​src/​trade/​context_async.rs Adds asynchronous Python parameters.
python/​pysrc/​longbridge/​openapi.pyi Updates Python type declarations.
nodejs/​src/​trade/​types.rs Exposes status and response fields to Node.js.
nodejs/​src/​trade/​requests/​submit_order.rs Adds delayed regular-order submission.
nodejs/​src/​trade/​requests/​submit_multileg.rs Adds delayed multi-leg submission.
nodejs/​src/​trade/​requests/​replace_order.rs Adds delayed-time replacement controls.
nodejs/​index.js Regenerates the native/WASI loader.
nodejs/​index.d.ts Regenerates Node.js declarations.
java/​src/​types/​enum_types.rs Maps the new Java status.
java/​src/​types/​classes.rs Maps delayed-time response fields.
java/​src/​trade_context.rs Forwards Java delayed-order options.
java/​javasrc/​src/​main/​java/​com/​longbridge/​trade/​SubmitOrderOptions.java Adds regular-order scheduling.
java/​javasrc/​src/​main/​java/​com/​longbridge/​trade/​SubmitMultiLegOrderOptions.java Adds multi-leg scheduling.
java/​javasrc/​src/​main/​java/​com/​longbridge/​trade/​ReplaceOrderOptions.java Adds delayed-time replacement controls.
java/​javasrc/​src/​main/​java/​com/​longbridge/​trade/​OrderStatus.java Adds the new status constant.
java/​javasrc/​src/​main/​java/​com/​longbridge/​trade/​OrderDetail.java Exposes delayed submission time.
java/​javasrc/​src/​main/​java/​com/​longbridge/​trade/​Order.java Exposes delayed submission time.
cpp/​src/​trade_context.cpp Forwards C++ scheduling options.
cpp/​src/​convert.hpp Converts status and response fields.
cpp/​include/​types.hpp Extends public C++ types.
CHANGELOG.md Documents both trade features.
c/​src/​trade_context/​types.rs Extends public C FFI structures.
c/​src/​trade_context/​enum_types.rs Adds the C status value.
c/​src/​trade_context/​context.rs Converts delayed timestamps for requests.
c/​csrc/​include/​longbridge.h Regenerates the public C header.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread c/src/trade_context/context.rs Outdated
Comment on lines +478 to +480
opts2 = opts2.delayed_until(
OffsetDateTime::from_unix_timestamp(*(*opts).delayed_until)
.expect("invalid delayed until"),
Comment thread c/src/trade_context/types.rs Outdated
Comment on lines +943 to +944
/// Time-condition submit time (maybe null)
pub delayed_until: *const i64,
Comment thread c/src/trade_context/types.rs Outdated
Comment on lines +1261 to +1266
/// New time-condition submit time, Unix timestamp in seconds (can be
/// null to keep the current time condition)
pub delayed_until: *const i64,
/// Clear the time condition, turning the order into a regular order
/// (takes precedence over `delayed_until`)
pub clear_delayed_until: bool,
Comment on lines +1312 to +1315
/// Time-condition submit time, Unix timestamp in seconds (can be null).
/// The order is held in `DelayedNotReported` status and only submitted at
/// this time.
pub delayed_until: *const i64,
Comment thread c/src/trade_context/types.rs Outdated
Comment on lines +1384 to +1386
/// Time-condition submit time, Unix timestamp in seconds (can be null).
/// Only `LO` / `MO` support time conditions.
pub delayed_until: *const i64,
Comment thread CHANGELOG.md Outdated
- **Known differences:** HTTP errors are `Error::HttpClient(OpenApi { .. })` rather than `Error::WsClient(ResponseError { .. })` (`Error::openapi_error_code()` / `into_simple_error()` cover both, but a non-JSON gateway error surfaces as `HttpClientError::UnexpectedHttpResponse` with no code); the REST gateway currently reports business errors (e.g. `301600`, `301607`) as `500`; the per-command rate limits the WebSocket applies from the quote profile are not applied on the HTTP path (the HTTP client retries `429` with back-off instead), and a candlestick top-up turns one call into up to 9 requests against `/quote/history-candlesticks`; during the US overnight session (20:00–04:00 New York) the REST gateway has already rolled over to the next trading day, so `intraday` for a US equity can be empty over HTTP (its lines are all overnight and dropped) and snapshot values of `quote`, `depth`, `calc_indexes` and `capital_flow` may reflect overnight trading that the WebSocket (without `enable_overnight`) does not report; `trades` may include overnight ticks during and shortly after the overnight session. The HTTP path has no client-side rate limiting.
- **C / C++ / Java / Node.js / Python:** the `QuoteTransport` enum and the corresponding config option are exposed in every binding layer.
- **All SDKs:** `OrderStatus` gains a `DelayedNotReported` variant (wire value `DelayedNotReported`) — "monitoring", the state a time-conditional order sits in before it is reported to the exchange. Previously this status deserialized to `OrderStatus::Unknown`, so time-conditional orders being monitored were indistinguishable from a genuinely unrecognized status in `today_orders` / `history_orders` / `order_detail` and the order-changed push. The variant is appended **after** `PartialWithdrawal` rather than inserted next to the other `*NotReported` values on purpose: the C/C++/Node.js enums use implicit discriminants, and inserting mid-list would renumber every following value (an ABI break for the C/C++ bindings, and a silent mismatch for TypeScript code that inlined the old `const enum` values)
- **All SDKs:** time-condition orders (longbridge/developers#1289). `SubmitOrderOptions` and `SubmitMultiLegOrderOptions` gain `delayed_until`: the order is held in `DelayedNotReported` status and sent automatically at that time (later than now, at most 90 days ahead; multi-leg orders support only `LO` / `MO`). `ReplaceOrderOptions` gains `delayed_until` to move the time and `clear_delayed_until` to turn the order back into a regular one; leaving both unset keeps the current time condition. `Order` and `OrderDetail` gain `delayed_until` (unset when the order is not a time-condition order), read from `today_orders` / `history_orders` / `order_detail`. Per layer: Rust `delayed_until(OffsetDateTime)` / `clear_delayed_until()` builder methods and `Option<OffsetDateTime>` fields; C `const int64_t *delayed_until` (Unix seconds) on `lb_submit_order_options_t` / `CSubmitMultiLegOrderOptions` / `lb_replace_order_options_t` (plus `bool clear_delayed_until`; all appended at the end of the structs) and on `lb_order_t` / `lb_order_detail_t`; C++ `std::optional<int64_t> delayed_until` (+ `bool clear_delayed_until` on `ReplaceOrderOptions`); Java `setDelayedUntil(OffsetDateTime)` / `ReplaceOrderOptions.clearDelayedUntil()` and `getDelayedUntil()`; Node.js `delayedUntil?: Date` / `clearDelayedUntil?: boolean` and `get delayedUntil(): Date | null`; Python `delayed_until: Optional[datetime]` / `clear_delayed_until: bool = False` keyword arguments and `Order.delayed_until` / `OrderDetail.delayed_until`
- Rust: send a delayed_until that maps to 0/1 (the wire sentinels) as 2,
  so the server rejects it as a past time instead of silently placing a
  regular order or clearing the condition. Document last-call-wins for
  delayed_until / clear_delayed_until on replace.
- C: report an out-of-range delayed_until through the callback instead
  of panicking across the FFI boundary; append delayed_until at the end
  of lb_order_t / lb_order_detail_t so existing offsets are unchanged.
- Python: honour tzinfo on aware datetimes (previously their wall-clock
  time was read as UTC); document the naive/aware rules in the stub.
- Node: doc comment refers to delayedUntil.
- Tests for submit / multi-leg serialization, the sentinel clamp,
  OrderDetail delayed_until and the DelayedNotReported status.
- Python: revert the SDK-wide tzinfo change to PyOffsetDateTimeWrapper,
  which shifted wall-clock parameters such as
  history_candlesticks_by_offset(time=...). delayed_until now uses a
  dedicated PyInstantWrapper: aware datetimes (utcoffset() not None)
  convert by their offset, naive ones are read as UTC.
- Node: the submit / replace / multi-leg option conversions are now
  TryFrom, so an out-of-range delayedUntil Date is an InvalidArg error
  instead of a panic.
- Rust: test that the last of delayed_until / clear_delayed_until wins.

Verified end to end against a local fake HTTP server with the built
Python wheel: aware +08:00, aware UTC and naive inputs all send
1767225600; epoch is clamped to 2; clear sends 1 and wins over a time.
- Document the later-than-now / 90-day limit on delayed_until in the C,
  C++, Node multi-leg and Python multi-leg docs (header and index.d.ts
  regenerated).
- C: parse_delayed_until returns the small ComponentRange error and
  converts it to longbridge::Error at the call site, avoiding a new
  clippy::result_large_err warning.
@hogan-yuan
hogan-yuan force-pushed the feat/order-status-delayed-not-reported branch from d5a71db to fa2d455 Compare October 13, 2026 14:42

This branch has not been deployed

No deployments
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.

3 participants