Skip to content

feat(trade): add DelayedNotReported order status and time-condition orders - #595

Merged
hogan-yuan merged 11 commits into
mainfrom
feat/order-status-delayed-not-reported
Oct 14, 2026
Merged

hogan-yuan merged 11 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 (the new DelayedNotReported order status) and longbridge/developers#1289 (time-condition orders).

1. OrderStatus::DelayedNotReported

Monitoring (time-condition order): the state a time-condition order sits in before it is reported to the exchange.

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. Monitored time-condition orders simply became 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 grouped with the other *NotReported values. The C/C++ and Node.js enums use implicit discriminants:

  • lb_order_status_t has no explicit values, so inserting mid-list would shift every following value. That is an ABI break for anything linked against a prebuilt C/C++ library.
  • nodejs/index.d.ts emits a const enum, which TypeScript inlines at the consumer's compile time. Renumbering would silently change already-compiled code. Appending keeps every existing discriminant stable (DelayedNotReported = 18).

2. Time-condition orders (delayed_until)

Endpoint SDK
Submit order / submit multi-leg order delayed_until: hold the order in DelayedNotReported and submit it at that time (later than now, at most 90 days ahead; multi-leg supports only LO / MO)
Replace order delayed_until moves the time; clear_delayed_until turns it back into a regular order (wire 1); leaving both unset keeps the current time condition
today_orders / history_orders / order_detail Order.delayed_until / OrderDetail.delayed_until, unset when the server returns "0" or omits it

Per layer:

  • Rust: delayed_until(OffsetDateTime) builders and ReplaceOrderOptions::clear_delayed_until() (last call wins); Option<OffsetDateTime> response fields.
  • C: const int64_t *delayed_until (Unix seconds) on the three option structs, plus bool clear_delayed_until on replace. Also on lb_order_t / lb_order_detail_t. Every new field is appended at the end of its struct, so existing field offsets are unchanged.
  • C++: std::optional<int64_t> delayed_until, plus bool clear_delayed_until = false.
  • Java: setDelayedUntil(OffsetDateTime), ReplaceOrderOptions.clearDelayedUntil(), getDelayedUntil().
  • Node.js: delayedUntil?: Date, clearDelayedUntil?: boolean, get delayedUntil(): Date | null.
  • Python: delayed_until: Optional[datetime] / clear_delayed_until: bool = False keyword arguments (appended, so positional callers are unaffected), and Order.delayed_until / OrderDetail.delayed_until.

In the bindings, clear takes precedence over a time when both are set.

Edge cases handled

  • Wire sentinels: a time at or before 1970-01-01T00:00:01Z would map to 0 (no condition) or 1 (clear), so it is sent as 2 instead. The server then rejects it as a past time rather than silently placing a regular order or clearing the condition.
  • Out-of-range timestamps:
    • C reports the error through the callback instead of panicking across the FFI boundary.
    • Node.js returns an InvalidArg error instead of aborting the process.
  • Lenient response parsing: the response field also accepts a JSON integer. A malformed, negative or out-of-range value reads as unset instead of failing the whole today_orders / history_orders / order_detail call, so users who upgrade are not exposed to a field the server never sent before.
  • Python time zones: delayed_until uses a dedicated converter.
    • A timezone-aware datetime (one whose utcoffset() is not None) converts by its real offset; a naive one is read as UTC.
    • Other datetime parameters keep their existing wall-clock semantics. For example, history_candlesticks_by_offset(time=...) relies on that.

Second commit: regenerated nodejs/index.js

npm run build:debug regenerates index.js alongside index.d.ts. The committed copy predated the @napi-rs/cli@3.8.6 that the release workflow pins, so it was regenerated separately (38184a7); it is loader churn unrelated to the API changes.

Verification

  • cargo clippy --all --all-features: no new warnings. cargo +nightly fmt --all applied.
  • cargo test -p longbridge --lib: 70 passed. New tests cover:
    • request serialization for submit / multi-leg / replace
    • the sentinel clamp, and last-call-wins on replace
    • "0" / absent / null / integer / malformed responses
    • the new status
  • cargo build -p longbridge-c (header regenerated); clang++ -fsyntax-only on cpp/src/trade_context.cpp.
  • cargo build -p longbridge-java and mvn compile for the Java classes.
  • npm run build:debug: index.d.ts regenerated. Checked with a script that an out-of-range delayedUntil returns InvalidArg and the process stays alive.
  • Python: built the wheel and called submit_order / replace_order against a local fake HTTP server.
    • Aware +08:00, aware UTC and naive inputs all send 1767225600.
    • The epoch is sent as 2.
    • Clear sends 1 and wins over a time.
    • The field is omitted when unset.
  • Not verified: an end-to-end order against a real account.

Go SDK counterpart: longbridge/openapi-go#123.

🤖 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
A malformed or out-of-range delayed_until (or a JSON number instead of
the documented string) now reads as unset instead of failing the whole
today_orders / history_orders / order_detail response, so upgrading
users are not exposed to a field the server never sent before.

CHANGELOG: spell out that the C option structs grow (rebuild against
the new header, zero-initialize options) and the Rust source-level
impact of the new variant and fields.
…ding

The lenient parser accepts JSON integers (the CHANGELOG said they read as
unset); negative values now read as unset too.
@hogan-yuan hogan-yuan changed the title feat(trade): add DelayedNotReported order status feat(trade): add DelayedNotReported order status and time-condition orders Oct 14, 2026
@hogan-yuan
hogan-yuan merged commit 3a564fe into main Oct 14, 2026
99 of 112 checks passed
@hogan-yuan
hogan-yuan deleted the feat/order-status-delayed-not-reported branch October 14, 2026 03:43
hogan-yuan added a commit to longbridge/openapi-go that referenced this pull request Oct 14, 2026
…(DelayedUntil) (#123)

Ports longbridge/openapi#595: the `DelayedNotReported` order status
([longbridge/developers#1263](longbridge/developers#1263))
and time-condition orders
([longbridge/developers#1289](longbridge/developers#1289)).

## Changes

- **`trade.OrderDelayedNotReported`:** a new `OrderStatus` constant for
the wire value `"DelayedNotReported"`, meaning a time-condition order
that is held and not yet reported to the exchange. `OrderStatus` is a
plain string, so the value was already passed through; the constant lets
callers compare against it instead of a literal.
- **`DelayedUntil int64`** (Unix seconds; `0` is not sent) on
`SubmitOrder`, `SubmitMultiLegOrder` and `ReplaceOrder`.
- On submit, the order is held in `OrderDelayedNotReported` and
submitted at that time (later than now, at most 90 days ahead; multi-leg
supports only `LO` / `MO`).
- On replace, `0` keeps the current time condition, the new
`ClearDelayedUntil` constant (`1`) clears it, and any other value sets a
new time.
- **`DelayedUntil string`** on `Order` / `OrderDetail`: the raw
Unix-seconds string from `TodayOrders` / `HistoryOrders` /
`OrderDetail`. It is `"0"`, or empty if the server omits the field, when
the order is not a time-condition order. This mirrors the existing
`TriggerAt string`. The wire field also accepts a JSON number (or
`null`), so a server-side type change in this new field cannot fail the
whole response.

Raw `int64` seconds follow the SDK's existing request conventions
(`ExpireTime`, `GetCashFlow.StartAt`). The mapping to the wire types
goes through `util.Copy`, the same as the neighbouring fields.

## Verification

- `go build ./...`, `go vet ./trade/...` and `go test ./trade/...` pass.
- `trade/delayed_until_test.go` exercises the real `util.Copy` +
`encoding/json` path:
- the wire values for submit / multi-leg / replace, including
`ClearDelayedUntil` and a new time
  - the field is omitted at zero for all three request types
- responses with `"0"`, a timestamp, a JSON number, `null`, and no field

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hogan-yuan added a commit to longbridge/openapi-go that referenced this pull request Oct 14, 2026
Cut **v0.29.1** — closes out the `[Unreleased]` CHANGELOG section as
`[v0.29.1] - 2026-10-14`.

## Included since v0.29.0
- **Added — time-condition orders** (#123): `DelayedUntil` on
`SubmitOrder` / `SubmitMultiLegOrder` / `ReplaceOrder`
(`ClearDelayedUntil` clears it on replace), `DelayedUntil` on `Order` /
`OrderDetail`, and the `OrderDelayedNotReported` status constant. Ports
longbridge/openapi#595.
- **Added — quote transport** (#130): `quote.Transport` to serve quote
pull APIs over HTTP, plus the related `Changed` / `Fixed` items
(`LONGBRIDGE_ENABLE_OVERNIGHT`, idempotent `QuoteContext.Close`,
`Subscribe` error propagation, WebSocket leak fixes).

Only `CHANGELOG.md` changes. The `v0.29.1` tag is created after merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hogan-yuan added a commit that referenced this pull request Oct 14, 2026
Release **v5.2.1**. Bumps workspace `5.2.0` → `5.2.1`.

### New since v5.2.0
- **Added:** `OrderStatus::DelayedNotReported` — monitoring, the state a
time-condition order sits in before it is reported to the exchange;
appended after `PartialWithdrawal` so C/C++/Node.js discriminants stay
stable (#595)
- **Added:** time-condition orders — `delayed_until` on submit /
multi-leg submit (held in `DelayedNotReported` and sent at that time, ≤
90 days ahead; multi-leg `LO` / `MO` only) and on replace (move the
time, or `clear_delayed_until` to turn it back into a regular order);
`Order` / `OrderDetail` gain `delayed_until`. Across Rust, C, C++, Java,
Node.js and Python (the Go SDK ships separately, openapi-go#123) (#595)
- **Added:** `QuoteTransport` config option to serve quote pull APIs
over HTTP, exposed in every binding layer (#604)

Only `Cargo.toml` (workspace version + internal dep reqs) and
`CHANGELOG.md` change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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