Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **US overnight alignment:** the WebSocket only returns US overnight data when `Config::enable_overnight` is set, while REST always does. On the HTTP path (and only there) the SDK applies the WebSocket's rule for US equities: `quote().overnight_quote` is cleared, overnight `intraday` lines are dropped, and for `TradeSessions::All` candlestick queries overnight bars are dropped and count/range-capped windows are topped up with `POST /quote/history-candlesticks` offset queries (≤1000 bars per request, ≤8 extra requests; a failure in a top-up request fails the call). `trades` and `calc_indexes` are passed through unchanged.
- **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. A time at or before `1970-01-01T00:00:01Z` would collide with the wire sentinels (`0` = no time condition, `1` = clear) and is sent as `2` instead, so the server rejects it as a past time rather than silently placing a regular order or clearing the condition; an out-of-range C timestamp is reported through the callback instead of aborting. `Order` and `OrderDetail` gain `delayed_until` (unset when the order is not a time-condition order), read from `today_orders` / `history_orders` / `order_detail`; the new field also accepts a JSON integer as well as a string, and a malformed or out-of-range value (non-numeric string, float, negative, beyond year 9999) reads as unset instead of failing the whole response. Rust: as with earlier additions, the new `OrderStatus` variant and `Order` / `OrderDetail` fields affect exhaustive `match`es and struct literals. 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`) and on `lb_order_t` / `lb_order_detail_t`, every new field appended at the end of its struct so existing field offsets are unchanged — the option structs grow, so C/C++ code must be rebuilt against the new header and should zero-initialize option structs (`= {0}`) so the new pointers are null; 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 (a timezone-aware `datetime` is converted by its actual offset, a naive one is read as UTC) and `Order.delayed_until` / `OrderDetail.delayed_until`

## [5.2.0] - 2026-09-30

Expand Down
35 changes: 35 additions & 0 deletions c/csrc/include/longbridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,10 @@ typedef enum lb_order_status_t {
* Partial Withdrawal
*/
OrderStatusPartialWithdrawal,
/**
* Monitoring (Time-Conditional Order)
*/
OrderStatusDelayedNotReported,
} lb_order_status_t;

/**
Expand Down Expand Up @@ -4694,6 +4698,17 @@ typedef struct lb_replace_order_options_t {
* Attached order parameters (can be null)
*/
const struct CReplaceAttachedParams *attached_params;
/**
* New time-condition submit time, Unix timestamp in seconds; must be
* later than now and no more than 90 days ahead (can be null to keep the
* current time condition)
*/
const int64_t *delayed_until;
/**
* Clear the time condition, turning the order into a regular order
* (takes precedence over `delayed_until`)
*/
bool clear_delayed_until;
} lb_replace_order_options_t;

/**
Expand Down Expand Up @@ -4817,6 +4832,12 @@ typedef struct lb_submit_order_options_t {
* Attached order parameters (can be null)
*/
const struct CSubmitAttachedParams *attached_params;
/**
* Time-condition submit time, Unix timestamp in seconds (can be null).
* The order is held in `DelayedNotReported` status and only submitted at
* this time, which must be later than now and no more than 90 days ahead.
*/
const int64_t *delayed_until;
} lb_submit_order_options_t;

/**
Expand Down Expand Up @@ -4879,6 +4900,12 @@ typedef struct CSubmitMultiLegOrderOptions {
* The server caches this ID for 10 minutes.
*/
const char *client_request_id;
/**
* Time-condition submit time, Unix timestamp in seconds (can be null);
* must be later than now and no more than 90 days ahead. Only `LO` / `MO`
* support time conditions.
*/
const int64_t *delayed_until;
} CSubmitMultiLegOrderOptions;

/**
Expand Down Expand Up @@ -5943,6 +5970,10 @@ typedef struct lb_order_t {
* Multi-leg strategy information (only valid when has_multi_leg is true)
*/
struct CMultiLegInfo multi_leg;
/**
* Time-condition submit time (maybe null)
*/
const int64_t *delayed_until;
} lb_order_t;

/**
Expand Down Expand Up @@ -6579,6 +6610,10 @@ typedef struct lb_order_detail_t {
* Multi-leg strategy information (only valid when has_multi_leg is true)
*/
struct CMultiLegInfo multi_leg;
/**
* Time-condition submit time (maybe null)
*/
const int64_t *delayed_until;
} lb_order_detail_t;

/**
Expand Down
35 changes: 35 additions & 0 deletions c/src/trade_context/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@ pub unsafe extern "C" fn lb_trade_context_replace_order(
if !(*opts).remark.is_null() {
opts2 = opts2.remark(cstr_to_rust((*opts).remark));
}
let delayed_until = if (*opts).clear_delayed_until {
opts2 = opts2.clear_delayed_until();
Ok(None)
} else {
parse_delayed_until((*opts).delayed_until)
};
if !(*opts).attached_params.is_null() {
let ap = &*(*opts).attached_params;
let attached_order_type: AttachedOrderType = ap.attached_order_type.into();
Expand Down Expand Up @@ -521,6 +527,9 @@ pub unsafe extern "C" fn lb_trade_context_replace_order(
opts2 = opts2.attached_params(rp);
}
execute_async(callback, ctx, userdata, async move {
if let Some(delayed_until) = delayed_until.map_err(delayed_until_error)? {
opts2 = opts2.delayed_until(delayed_until);
}
ctx_inner.replace_order(opts2).await?;
Ok(())
});
Expand Down Expand Up @@ -585,6 +594,7 @@ pub unsafe extern "C" fn lb_trade_context_submit_order(
if !(*opts).client_request_id.is_null() {
opts2 = opts2.client_request_id(cstr_to_rust((*opts).client_request_id));
}
let delayed_until = parse_delayed_until((*opts).delayed_until);
if !(*opts).attached_params.is_null() {
let ap = &*(*opts).attached_params;
let attached_order_type: AttachedOrderType = ap.attached_order_type.into();
Expand Down Expand Up @@ -616,6 +626,9 @@ pub unsafe extern "C" fn lb_trade_context_submit_order(
opts2 = opts2.attached_params(sp);
}
execute_async(callback, ctx, userdata, async move {
if let Some(delayed_until) = delayed_until.map_err(delayed_until_error)? {
opts2 = opts2.delayed_until(delayed_until);
}
let resp: CCow<CSubmitOrderResponseOwned> = CCow::new(ctx_inner.submit_order(opts2).await?);
Ok(resp)
});
Expand Down Expand Up @@ -655,7 +668,11 @@ pub unsafe extern "C" fn lb_trade_context_submit_multileg(
if !(*opts).client_request_id.is_null() {
opts2 = opts2.client_request_id(cstr_to_rust((*opts).client_request_id));
}
let delayed_until = parse_delayed_until((*opts).delayed_until);
execute_async(callback, ctx, userdata, async move {
if let Some(delayed_until) = delayed_until.map_err(delayed_until_error)? {
opts2 = opts2.delayed_until(delayed_until);
}
let resp: CCow<CSubmitOrderResponseOwned> =
CCow::new(ctx_inner.submit_multileg(opts2).await?);
Ok(resp)
Expand Down Expand Up @@ -873,3 +890,21 @@ pub unsafe extern "C" fn lb_trade_context_estimate_max_purchase_quantity(
Ok(resp)
});
}

/// Converts an optional C `delayed_until` (Unix seconds) into a time, reporting
/// an out-of-range value through the callback instead of panicking.
unsafe fn parse_delayed_until(
value: *const i64,
) -> Result<Option<OffsetDateTime>, time::error::ComponentRange> {
if value.is_null() {
return Ok(None);
}
unsafe { OffsetDateTime::from_unix_timestamp(*value) }.map(Some)
}

fn delayed_until_error(err: time::error::ComponentRange) -> longbridge::Error {
longbridge::Error::ParseField {
name: "delayed_until",
error: err.to_string(),
}
}
3 changes: 3 additions & 0 deletions c/src/trade_context/enum_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ pub enum COrderStatus {
/// Partial Withdrawal
#[c(remote = "PartialWithdrawal")]
OrderStatusPartialWithdrawal,
/// Monitoring (Time-Conditional Order)
#[c(remote = "DelayedNotReported")]
OrderStatusDelayedNotReported,
}

/// Order tag
Expand Down
35 changes: 35 additions & 0 deletions c/src/trade_context/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,8 @@ pub struct COrder {
pub has_multi_leg: bool,
/// Multi-leg strategy information (only valid when has_multi_leg is true)
pub multi_leg: CMultiLegInfo,
/// Time-condition submit time (maybe null)
pub delayed_until: *const i64,
}

#[derive(Debug)]
Expand Down Expand Up @@ -982,6 +984,7 @@ pub(crate) struct COrderOwned {
trigger_count: Option<i32>,
monitor_price: Option<CDecimal>,
remark: CString,
delayed_until: Option<i64>,
attached_orders: CVec<CAttachedOrderDetailOwned>,
multi_leg: Option<CMultiLegInfoOwned>,
}
Expand Down Expand Up @@ -1018,6 +1021,7 @@ impl From<Order> for COrderOwned {
trigger_count,
monitor_price,
remark,
delayed_until,
attached_orders,
multi_leg,
} = order;
Expand Down Expand Up @@ -1051,6 +1055,7 @@ impl From<Order> for COrderOwned {
trigger_count,
monitor_price: monitor_price.map(Into::into),
remark: remark.into(),
delayed_until: delayed_until.map(OffsetDateTime::unix_timestamp),
attached_orders: attached_orders.into(),
multi_leg: multi_leg.map(Into::into),
}
Expand Down Expand Up @@ -1091,6 +1096,7 @@ impl ToFFI for COrderOwned {
trigger_count,
monitor_price,
remark,
delayed_until,
attached_orders,
multi_leg,
} = self;
Expand Down Expand Up @@ -1169,6 +1175,10 @@ impl ToFFI for COrderOwned {
.map(ToFFI::to_ffi_type)
.unwrap_or(std::ptr::null()),
remark: remark.to_ffi_type(),
delayed_until: delayed_until
.as_ref()
.map(|value| value as *const i64)
.unwrap_or(std::ptr::null()),
attached_orders: attached_orders.to_ffi_type(),
num_attached_orders: attached_orders.len(),
has_multi_leg: multi_leg.is_some(),
Expand Down Expand Up @@ -1248,6 +1258,13 @@ pub struct CReplaceOrderOptions {
pub remark: *const c_char,
/// Attached order parameters (can be null)
pub attached_params: *const CReplaceAttachedParams,
/// New time-condition submit time, Unix timestamp in seconds; must be
/// later than now and no more than 90 days ahead (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,
}

/// Options for submit order request
Expand Down Expand Up @@ -1293,6 +1310,10 @@ pub struct CSubmitOrderOptions {
pub client_request_id: *const c_char,
/// Attached order parameters (can be null)
pub attached_params: *const CSubmitAttachedParams,
/// Time-condition submit time, Unix timestamp in seconds (can be null).
/// The order is held in `DelayedNotReported` status and only submitted at
/// this time, which must be later than now and no more than 90 days ahead.
pub delayed_until: *const i64,
Comment on lines +1313 to +1316
}

/// Response for submit order request
Expand Down Expand Up @@ -1361,6 +1382,10 @@ pub struct CSubmitMultiLegOrderOptions {
/// If not specified, idempotency control is skipped.
/// The server caches this ID for 10 minutes.
pub client_request_id: *const c_char,
/// Time-condition submit time, Unix timestamp in seconds (can be null);
/// must be later than now and no more than 90 days ahead. Only `LO` / `MO`
/// support time conditions.
pub delayed_until: *const i64,
}

/// Account balance
Expand Down Expand Up @@ -2401,6 +2426,8 @@ pub struct COrderDetail {
pub has_multi_leg: bool,
/// Multi-leg strategy information (only valid when has_multi_leg is true)
pub multi_leg: CMultiLegInfo,
/// Time-condition submit time (maybe null)
pub delayed_until: *const i64,
}

#[derive(Debug)]
Expand Down Expand Up @@ -2434,6 +2461,7 @@ pub(crate) struct COrderDetailOwned {
trigger_count: Option<i32>,
monitor_price: Option<CDecimal>,
remark: CString,
delayed_until: Option<i64>,
free_status: CCommissionFreeStatus,
free_amount: Option<CDecimal>,
free_currency: Option<CString>,
Expand Down Expand Up @@ -2481,6 +2509,7 @@ impl From<OrderDetail> for COrderDetailOwned {
trigger_count,
monitor_price,
remark,
delayed_until,
free_status,
free_amount,
free_currency,
Expand Down Expand Up @@ -2525,6 +2554,7 @@ impl From<OrderDetail> for COrderDetailOwned {
trigger_count,
monitor_price: monitor_price.map(Into::into),
remark: remark.into(),
delayed_until: delayed_until.map(OffsetDateTime::unix_timestamp),
free_status: free_status.into(),
free_amount: free_amount.map(Into::into),
free_currency: free_currency.map(Into::into),
Expand Down Expand Up @@ -2576,6 +2606,7 @@ impl ToFFI for COrderDetailOwned {
trigger_count,
monitor_price,
remark,
delayed_until,
free_status,
free_amount,
free_currency,
Expand Down Expand Up @@ -2665,6 +2696,10 @@ impl ToFFI for COrderDetailOwned {
.map(ToFFI::to_ffi_type)
.unwrap_or(std::ptr::null()),
remark: remark.to_ffi_type(),
delayed_until: delayed_until
.as_ref()
.map(|value| value as *const i64)
.unwrap_or(std::ptr::null()),
free_status: *free_status,
free_amount: free_amount
.as_ref()
Expand Down
22 changes: 22 additions & 0 deletions cpp/include/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,8 @@ enum class OrderStatus
Expired,
/// Partial Withdrawal
PartialWithdrawal,
/// Monitoring (Time-Conditional Order)
DelayedNotReported,
};

/// Order type
Expand Down Expand Up @@ -1831,6 +1833,9 @@ struct Order
std::optional<Decimal> monitor_price;
/// Remark
std::string remark;
/// Time-condition submit time (Unix timestamp in seconds, not set when
/// the order is not a time-condition order)
std::optional<int64_t> delayed_until;
/// Attached orders
std::vector<AttachedOrderDetail> attached_orders;
/// Multi-leg strategy information (only present for multi-leg option
Expand Down Expand Up @@ -1957,6 +1962,13 @@ struct ReplaceOrderOptions
std::optional<std::string> remark;
/// Attached order params
std::optional<ReplaceAttachedParams> attached_params;
/// New time-condition submit time (Unix timestamp in seconds), later than
/// now and no more than 90 days ahead; not set keeps the current time
/// condition
std::optional<int64_t> delayed_until;
/// Clear the time condition, turning the order into a regular order (takes
/// precedence over `delayed_until`)
bool clear_delayed_until = false;
};

/// Options for submit order request
Expand Down Expand Up @@ -2001,6 +2013,10 @@ struct SubmitOrderOptions
std::optional<std::string> client_request_id;
/// Attached order params
std::optional<SubmitAttachedParams> attached_params;
/// Time-condition submit time (Unix timestamp in seconds). The order is held
/// in `DelayedNotReported` status and only submitted at this time, which
/// must be later than now and no more than 90 days ahead.
std::optional<int64_t> delayed_until;
};

/// Response for submit order request
Expand Down Expand Up @@ -2043,6 +2059,9 @@ struct SubmitMultiLegOrderOptions
/// If not specified, idempotency control is skipped.
/// The server caches this ID for 10 minutes.
std::optional<std::string> client_request_id;
/// Time-condition submit time (Unix timestamp in seconds), later than now
/// and no more than 90 days ahead. Only `LO` / `MO` support time conditions.
std::optional<int64_t> delayed_until;
};

/// Cash info
Expand Down Expand Up @@ -2397,6 +2416,9 @@ struct OrderDetail
std::optional<Decimal> monitor_price;
/// Remark
std::string remark;
/// Time-condition submit time (Unix timestamp in seconds, not set when
/// the order is not a time-condition order)
std::optional<int64_t> delayed_until;
/// Commission-free Status
CommissionFreeStatus free_status;
/// Commission-free amount
Expand Down
Loading
Loading