From 9c23414ea2b959e24a17d1c90b79108766d4139e Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 16 Jun 2026 16:38:11 +0800 Subject: [PATCH 1/9] feat(trade): add attached order (take-profit/stop-loss) support Add attached order API support across all language SDKs based on developers PR #1092. New types: - AttachedOrderType enum (ProfitTaker / StopLoss / Bracket) - AttachedOrderDetail response struct - SubmitAttachedParams / ReplaceAttachedParams request builders - GetOrderDetailOptions (with is_attached flag) API changes (non-breaking): - Order / OrderDetail: new attached_orders field - SubmitOrderOptions: new attached_params builder - ReplaceOrderOptions: new attached_params builder - GetTodayOrdersOptions: new is_attached() flag - order_detail: accepts impl Into - New order_detail_attached method (Rust/Python/Node.js/Java/C/C++) --- c/csrc/include/longbridge.h | 252 ++++++++++++++ c/src/trade_context/context.rs | 110 +++++- c/src/trade_context/types.rs | 323 +++++++++++++++++- cpp/include/trade_context.hpp | 5 + cpp/include/types.hpp | 83 +++++ cpp/src/convert.hpp | 183 +++++++++- cpp/src/trade_context.cpp | 43 ++- .../main/java/com/longbridge/SdkNative.java | 2 + .../longbridge/trade/AttachedOrderDetail.java | 233 +++++++++++++ .../longbridge/trade/AttachedOrderType.java | 11 + .../trade/GetTodayOrdersOptions.java | 20 ++ .../main/java/com/longbridge/trade/Order.java | 10 + .../com/longbridge/trade/OrderDetail.java | 10 + .../trade/ReplaceAttachedParams.java | 84 +++++ .../longbridge/trade/ReplaceOrderOptions.java | 21 ++ .../trade/SubmitAttachedParams.java | 54 +++ .../longbridge/trade/SubmitOrderOptions.java | 21 ++ .../com/longbridge/trade/TradeContext.java | 14 + java/src/init.rs | 2 + java/src/trade_context.rs | 181 +++++++++- java/src/types/classes.rs | 36 +- java/src/types/enum_types.rs | 13 + nodejs/index.d.ts | 125 +++++++ nodejs/index.js | 2 + nodejs/src/trade/context.rs | 9 + nodejs/src/trade/requests/get_today_orders.rs | 5 + nodejs/src/trade/requests/replace_order.rs | 94 ++++- nodejs/src/trade/requests/submit_order.rs | 61 +++- nodejs/src/trade/types.rs | 75 ++++ python/pysrc/longbridge/openapi.pyi | 201 ++++++++++- python/src/trade/context.rs | 34 +- python/src/trade/context_async.rs | 41 ++- python/src/trade/mod.rs | 4 + python/src/trade/types.rs | 199 ++++++++++- rust/src/blocking/trade.rs | 16 +- rust/src/trade/context.rs | 24 +- rust/src/trade/mod.rs | 7 +- rust/src/trade/requests/get_order_detail.rs | 38 +++ rust/src/trade/requests/get_today_orders.rs | 10 + rust/src/trade/requests/mod.rs | 6 +- rust/src/trade/requests/replace_order.rs | 168 +++++++++ rust/src/trade/requests/submit_order.rs | 108 +++++- rust/src/trade/types.rs | 80 +++++ 43 files changed, 2950 insertions(+), 68 deletions(-) create mode 100644 java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java create mode 100644 java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java create mode 100644 java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java create mode 100644 java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java create mode 100644 rust/src/trade/requests/get_order_detail.rs diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index f0b71ea527..6d7d48a561 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1114,6 +1114,24 @@ typedef enum lb_topic_type_t { TopicPrivate, } lb_topic_type_t; +/** + * Attached order type + */ +typedef enum CAttachedOrderType { + /** + * Take profit + */ + AttachedOrderTypeProfitTaker = 0, + /** + * Stop loss + */ + AttachedOrderTypeStopLoss = 1, + /** + * Bracket order + */ + AttachedOrderTypeBracket = 2, +} CAttachedOrderType; + /** * Time in force Type */ @@ -2325,8 +2343,78 @@ typedef struct lb_get_today_orders_options_t { * Order id (can be null) */ const char *order_id; + /** + * Filter by attached order (can be null) + */ + const bool *is_attached; } lb_get_today_orders_options_t; +/** + * Options for replace attached order params + */ +typedef struct CReplaceAttachedParams { + /** + * Attached order type + */ + enum CAttachedOrderType attached_order_type; + /** + * Take-profit trigger price (can be null) + */ + const struct lb_decimal_t *profit_taker_price; + /** + * Stop-loss trigger price (can be null) + */ + const struct lb_decimal_t *stop_loss_price; + /** + * Time in force type (can be null) + */ + const enum lb_time_in_force_type_t *time_in_force; + /** + * Expiry time unix timestamp (can be null) + */ + const int64_t *expire_time; + /** + * Order type to submit after trigger (can be null) + */ + const enum lb_order_type_t *activate_order_type; + /** + * Take-profit limit price (can be null) + */ + const struct lb_decimal_t *profit_taker_submit_price; + /** + * Stop-loss limit price (can be null) + */ + const struct lb_decimal_t *stop_loss_submit_price; + /** + * RTH setting for activated order (can be null) + */ + const enum lb_outside_rth_t *activate_rth; + /** + * Take-profit order ID (can be null) + */ + const int64_t *profit_taker_id; + /** + * Stop-loss order ID (can be null) + */ + const int64_t *stop_loss_id; + /** + * Cancel all attached orders flag (can be null) + */ + const bool *cancel_all_attached; + /** + * Main order ID (can be null) + */ + const int64_t *main_id; + /** + * Quantity (can be null) + */ + const struct lb_decimal_t *quantity; + /** + * Market price (can be null) + */ + const struct lb_decimal_t *market_price; +} CReplaceAttachedParams; + /** * Options for replace order request */ @@ -2375,8 +2463,54 @@ typedef struct lb_replace_order_options_t { * Remark (can be null) */ const char *remark; + /** + * Attached order parameters (can be null) + */ + const struct CReplaceAttachedParams *attached_params; } lb_replace_order_options_t; +/** + * Options for submit attached order params + */ +typedef struct CSubmitAttachedParams { + /** + * Attached order type + */ + enum CAttachedOrderType attached_order_type; + /** + * Take-profit trigger price (can be null) + */ + const struct lb_decimal_t *profit_taker_price; + /** + * Stop-loss trigger price (can be null) + */ + const struct lb_decimal_t *stop_loss_price; + /** + * Time in force type (can be null) + */ + const enum lb_time_in_force_type_t *time_in_force; + /** + * Expiry time unix timestamp (can be null) + */ + const int64_t *expire_time; + /** + * Order type to submit after trigger (can be null) + */ + const enum lb_order_type_t *activate_order_type; + /** + * Take-profit limit price (can be null) + */ + const struct lb_decimal_t *profit_taker_submit_price; + /** + * Stop-loss limit price (can be null) + */ + const struct lb_decimal_t *stop_loss_submit_price; + /** + * RTH setting for activated order (can be null) + */ + const enum lb_outside_rth_t *activate_rth; +} CSubmitAttachedParams; + /** * Options for submit order request */ @@ -2452,6 +2586,10 @@ typedef struct lb_submit_order_options_t { * The server caches this ID for 10 minutes. */ const char *client_request_id; + /** + * Attached order parameters (can be null) + */ + const struct CSubmitAttachedParams *attached_params; } lb_submit_order_options_t; /** @@ -3271,6 +3409,96 @@ typedef struct lb_all_executions_response_t { uintptr_t num_trades; } lb_all_executions_response_t; +/** + * Attached order detail + */ +typedef struct CAttachedOrderDetail { + /** + * Attached order ID + */ + const char *order_id; + /** + * Display type: 1=take-profit, 2=stop-loss + */ + int32_t attached_type_display; + /** + * Trigger price (maybe null) + */ + const struct lb_decimal_t *trigger_price; + /** + * Quantity + */ + const struct lb_decimal_t *quantity; + /** + * Executed quantity + */ + const struct lb_decimal_t *executed_qty; + /** + * Order status + */ + enum lb_order_status_t status; + /** + * Last updated time (unix timestamp) + */ + int64_t updated_at; + /** + * Whether withdrawn + */ + bool withdrawn; + /** + * GTD date (maybe null) + */ + const struct lb_date_t *gtd; + /** + * Time in force type + */ + enum lb_time_in_force_type_t time_in_force; + /** + * Counter order ID + */ + const char *counter_id; + /** + * Trigger status + */ + int32_t trigger_status; + /** + * Executed amount + */ + const struct lb_decimal_t *executed_amount; + /** + * Tag + */ + int32_t tag; + /** + * Submitted time (unix timestamp) + */ + int64_t submitted_at; + /** + * Executed price + */ + const struct lb_decimal_t *executed_price; + /** + * Force RTH only (maybe null) + */ + const enum lb_outside_rth_t *force_only_rth; + /** + * Whether reviewed + */ + bool reviewed; + /** + * Order type to submit after trigger + */ + enum lb_order_type_t activate_order_type; + /** + * RTH setting for activated order (maybe null) + */ + const enum lb_outside_rth_t *activate_rth; + /** + * Submit price (maybe null) + */ + const struct lb_decimal_t *submit_price; +} CAttachedOrderDetail; + /** * Order */ @@ -3391,6 +3619,14 @@ typedef struct lb_order_t { * Remark */ const char *remark; + /** + * Attached orders + */ + const struct CAttachedOrderDetail *attached_orders; + /** + * Number of attached orders + */ + uintptr_t num_attached_orders; } lb_order_t; /** @@ -4006,6 +4242,14 @@ typedef struct lb_order_detail_t { * Order charges */ struct lb_order_charge_detail_t charge_detail; + /** + * Attached orders + */ + const struct CAttachedOrderDetail *attached_orders; + /** + * Number of attached orders + */ + uintptr_t num_attached_orders; } lb_order_detail_t; /** @@ -10682,6 +10926,14 @@ void lb_trade_context_order_detail(const struct lb_trade_context_t *ctx, lb_async_callback_t callback, void *userdata); +/** + * Get order detail for attached order + */ +void lb_trade_context_order_detail_attached(const struct lb_trade_context_t *ctx, + const char *order_id, + lb_async_callback_t callback, + void *userdata); + /** * Get order detail */ diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs index fc688e0560..9f7ea2fcc1 100644 --- a/c/src/trade_context/context.rs +++ b/c/src/trade_context/context.rs @@ -3,10 +3,11 @@ use std::{ffi::c_void, os::raw::c_char, sync::Arc, time::Instant}; use longbridge::{ TradeContext, trade::{ - EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, - GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, PushEvent, - ReplaceOrderOptions, SubmitOrderOptions, + AttachedOrderType, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, + GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, + GetHistoryOrdersOptions, GetOrderDetailOptions, GetStockPositionsOptions, + GetTodayExecutionsOptions, GetTodayOrdersOptions, PushEvent, ReplaceAttachedParams, + ReplaceOrderOptions, SubmitAttachedParams, SubmitOrderOptions, }, }; use parking_lot::Mutex; @@ -376,6 +377,9 @@ pub unsafe extern "C" fn lb_trade_context_today_orders( if !(*opts).order_id.is_null() { opts2 = opts2.order_id(cstr_to_rust((*opts).order_id)); } + if !(*opts).is_attached.is_null() && *(*opts).is_attached { + opts2 = opts2.is_attached(); + } } execute_async(callback, ctx, userdata, async move { let rows: CVec = ctx_inner.today_orders(opts2).await?.into(); @@ -424,6 +428,54 @@ pub unsafe extern "C" fn lb_trade_context_replace_order( if !(*opts).remark.is_null() { opts2 = opts2.remark(cstr_to_rust((*opts).remark)); } + if !(*opts).attached_params.is_null() { + let ap = &*(*opts).attached_params; + let attached_order_type: AttachedOrderType = ap.attached_order_type.into(); + let mut rp = ReplaceAttachedParams::new(attached_order_type); + if !ap.profit_taker_price.is_null() { + rp = rp.profit_taker_price((*ap.profit_taker_price).value); + } + if !ap.stop_loss_price.is_null() { + rp = rp.stop_loss_price((*ap.stop_loss_price).value); + } + if !ap.time_in_force.is_null() { + rp = rp.time_in_force((*ap.time_in_force).into()); + } + if !ap.expire_time.is_null() { + rp = rp.expire_time(*ap.expire_time); + } + if !ap.activate_order_type.is_null() { + rp = rp.activate_order_type((*ap.activate_order_type).into()); + } + if !ap.profit_taker_submit_price.is_null() { + rp = rp.profit_taker_submit_price((*ap.profit_taker_submit_price).value); + } + if !ap.stop_loss_submit_price.is_null() { + rp = rp.stop_loss_submit_price((*ap.stop_loss_submit_price).value); + } + if !ap.activate_rth.is_null() { + rp = rp.activate_rth((*ap.activate_rth).into()); + } + if !ap.profit_taker_id.is_null() { + rp = rp.profit_taker_id(*ap.profit_taker_id); + } + if !ap.stop_loss_id.is_null() { + rp = rp.stop_loss_id(*ap.stop_loss_id); + } + if !ap.cancel_all_attached.is_null() && *ap.cancel_all_attached { + rp = rp.cancel_all_attached(); + } + if !ap.main_id.is_null() { + rp = rp.main_id(*ap.main_id); + } + if !ap.quantity.is_null() { + rp = rp.quantity((*ap.quantity).value); + } + if !ap.market_price.is_null() { + rp = rp.market_price((*ap.market_price).value); + } + opts2 = opts2.attached_params(rp); + } execute_async(callback, ctx, userdata, async move { ctx_inner.replace_order(opts2).await?; Ok(()) @@ -489,6 +541,36 @@ 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)); } + if !(*opts).attached_params.is_null() { + let ap = &*(*opts).attached_params; + let attached_order_type: AttachedOrderType = ap.attached_order_type.into(); + let mut sp = SubmitAttachedParams::new(attached_order_type); + if !ap.profit_taker_price.is_null() { + sp = sp.profit_taker_price((*ap.profit_taker_price).value); + } + if !ap.stop_loss_price.is_null() { + sp = sp.stop_loss_price((*ap.stop_loss_price).value); + } + if !ap.time_in_force.is_null() { + sp = sp.time_in_force((*ap.time_in_force).into()); + } + if !ap.expire_time.is_null() { + sp = sp.expire_time(*ap.expire_time); + } + if !ap.activate_order_type.is_null() { + sp = sp.activate_order_type((*ap.activate_order_type).into()); + } + if !ap.profit_taker_submit_price.is_null() { + sp = sp.profit_taker_submit_price((*ap.profit_taker_submit_price).value); + } + if !ap.stop_loss_submit_price.is_null() { + sp = sp.stop_loss_submit_price((*ap.stop_loss_submit_price).value); + } + if !ap.activate_rth.is_null() { + sp = sp.activate_rth((*ap.activate_rth).into()); + } + opts2 = opts2.attached_params(sp); + } execute_async(callback, ctx, userdata, async move { let resp: CCow = CCow::new(ctx_inner.submit_order(opts2).await?); Ok(resp) @@ -637,6 +719,26 @@ pub unsafe extern "C" fn lb_trade_context_order_detail( }); } +/// Get order detail for attached order +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_trade_context_order_detail_attached( + ctx: *const CTradeContext, + order_id: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let order_id = cstr_to_rust(order_id); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new( + ctx_inner + .order_detail(GetOrderDetailOptions::new(order_id).is_attached()) + .await?, + ); + Ok(resp) + }); +} + /// Get order detail #[unsafe(no_mangle)] pub unsafe extern "C" fn lb_trade_context_estimate_max_purchase_quantity( diff --git a/c/src/trade_context/types.rs b/c/src/trade_context/types.rs index 39c3353976..2b043e4bcd 100644 --- a/c/src/trade_context/types.rs +++ b/c/src/trade_context/types.rs @@ -3,12 +3,13 @@ use std::os::raw::c_char; use longbridge::{ Market, trade::{ - AccountBalance, AllExecutionsResponse, BalanceType, CashFlow, CashFlowDirection, CashInfo, - EstimateMaxPurchaseQuantityResponse, Execution, FrozenTransactionFee, FundPosition, - FundPositionChannel, FundPositionsResponse, MarginRatio, Order, OrderChargeDetail, - OrderChargeFee, OrderChargeItem, OrderDetail, OrderHistoryDetail, OrderSide, OrderStatus, - OrderTag, OrderType, PushOrderChanged, StockPosition, StockPositionChannel, - StockPositionsResponse, SubmitOrderResponse, TimeInForceType, + AccountBalance, AllExecutionsResponse, AttachedOrderDetail, AttachedOrderType, + BalanceType, CashFlow, CashFlowDirection, CashInfo, EstimateMaxPurchaseQuantityResponse, + Execution, FrozenTransactionFee, FundPosition, FundPositionChannel, FundPositionsResponse, + MarginRatio, Order, OrderChargeDetail, OrderChargeFee, OrderChargeItem, OrderDetail, + OrderHistoryDetail, OrderSide, OrderStatus, OrderTag, OrderType, PushOrderChanged, + StockPosition, StockPositionChannel, StockPositionsResponse, SubmitOrderResponse, + TimeInForceType, }, }; use time::OffsetDateTime; @@ -22,6 +23,290 @@ use crate::{ types::{CDate, CDecimal, CMarket, CString, CVec, ToFFI}, }; +/// Attached order type +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CAttachedOrderType { + /// Take profit + AttachedOrderTypeProfitTaker = 0, + /// Stop loss + AttachedOrderTypeStopLoss = 1, + /// Bracket order + AttachedOrderTypeBracket = 2, +} + +impl From for CAttachedOrderType { + fn from(value: AttachedOrderType) -> Self { + match value { + AttachedOrderType::ProfitTaker => CAttachedOrderType::AttachedOrderTypeProfitTaker, + AttachedOrderType::StopLoss => CAttachedOrderType::AttachedOrderTypeStopLoss, + AttachedOrderType::Bracket => CAttachedOrderType::AttachedOrderTypeBracket, + } + } +} + +impl From for AttachedOrderType { + fn from(value: CAttachedOrderType) -> Self { + match value { + CAttachedOrderType::AttachedOrderTypeProfitTaker => AttachedOrderType::ProfitTaker, + CAttachedOrderType::AttachedOrderTypeStopLoss => AttachedOrderType::StopLoss, + CAttachedOrderType::AttachedOrderTypeBracket => AttachedOrderType::Bracket, + } + } +} + +/// Attached order detail +#[repr(C)] +pub struct CAttachedOrderDetail { + /// Attached order ID + pub order_id: *const c_char, + /// Display type: 1=take-profit, 2=stop-loss + pub attached_type_display: i32, + /// Trigger price (maybe null) + pub trigger_price: *const CDecimal, + /// Quantity + pub quantity: *const CDecimal, + /// Executed quantity + pub executed_qty: *const CDecimal, + /// Order status + pub status: COrderStatus, + /// Last updated time (unix timestamp) + pub updated_at: i64, + /// Whether withdrawn + pub withdrawn: bool, + /// GTD date (maybe null) + pub gtd: *const CDate, + /// Time in force type + pub time_in_force: CTimeInForceType, + /// Counter order ID + pub counter_id: *const c_char, + /// Trigger status + pub trigger_status: i32, + /// Executed amount + pub executed_amount: *const CDecimal, + /// Tag + pub tag: i32, + /// Submitted time (unix timestamp) + pub submitted_at: i64, + /// Executed price + pub executed_price: *const CDecimal, + /// Force RTH only (maybe null) + pub force_only_rth: *const COutsideRTH, + /// Whether reviewed + pub reviewed: bool, + /// Order type to submit after trigger + pub activate_order_type: COrderType, + /// RTH setting for activated order (maybe null) + pub activate_rth: *const COutsideRTH, + /// Submit price (maybe null) + pub submit_price: *const CDecimal, +} + +#[derive(Debug)] +pub(crate) struct CAttachedOrderDetailOwned { + order_id: CString, + attached_type_display: i32, + trigger_price: Option, + quantity: CDecimal, + executed_qty: CDecimal, + status: OrderStatus, + updated_at: i64, + withdrawn: bool, + gtd: Option, + time_in_force: TimeInForceType, + counter_id: CString, + trigger_status: i32, + executed_amount: CDecimal, + tag: i32, + submitted_at: i64, + executed_price: CDecimal, + force_only_rth: Option, + reviewed: bool, + activate_order_type: OrderType, + activate_rth: Option, + submit_price: Option, +} + +impl From for CAttachedOrderDetailOwned { + fn from(detail: AttachedOrderDetail) -> Self { + let AttachedOrderDetail { + order_id, + attached_type_display, + trigger_price, + quantity, + executed_qty, + status, + updated_at, + withdrawn, + gtd, + time_in_force, + counter_id, + trigger_status, + executed_amount, + tag, + submitted_at, + executed_price, + force_only_rth, + reviewed, + activate_order_type, + activate_rth, + submit_price, + } = detail; + Self { + order_id: order_id.into(), + attached_type_display, + trigger_price: trigger_price.map(Into::into), + quantity: quantity.into(), + executed_qty: executed_qty.into(), + status, + updated_at: updated_at.unix_timestamp(), + withdrawn, + gtd: gtd.map(Into::into), + time_in_force, + counter_id: counter_id.into(), + trigger_status, + executed_amount: executed_amount.into(), + tag, + submitted_at: submitted_at.unix_timestamp(), + executed_price: executed_price.into(), + force_only_rth: force_only_rth.map(Into::into), + reviewed, + activate_order_type, + activate_rth: activate_rth.map(Into::into), + submit_price: submit_price.map(Into::into), + } + } +} + +impl ToFFI for CAttachedOrderDetailOwned { + type FFIType = CAttachedOrderDetail; + + fn to_ffi_type(&self) -> Self::FFIType { + let CAttachedOrderDetailOwned { + order_id, + attached_type_display, + trigger_price, + quantity, + executed_qty, + status, + updated_at, + withdrawn, + gtd, + time_in_force, + counter_id, + trigger_status, + executed_amount, + tag, + submitted_at, + executed_price, + force_only_rth, + reviewed, + activate_order_type, + activate_rth, + submit_price, + } = self; + CAttachedOrderDetail { + order_id: order_id.to_ffi_type(), + attached_type_display: *attached_type_display, + trigger_price: trigger_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + quantity: quantity.to_ffi_type(), + executed_qty: executed_qty.to_ffi_type(), + status: (*status).into(), + updated_at: *updated_at, + withdrawn: *withdrawn, + gtd: gtd + .as_ref() + .map(|value| value as *const CDate) + .unwrap_or(std::ptr::null()), + time_in_force: (*time_in_force).into(), + counter_id: counter_id.to_ffi_type(), + trigger_status: *trigger_status, + executed_amount: executed_amount.to_ffi_type(), + tag: *tag, + submitted_at: *submitted_at, + executed_price: executed_price.to_ffi_type(), + force_only_rth: force_only_rth + .as_ref() + .map(|value| value as *const COutsideRTH) + .unwrap_or(std::ptr::null()), + reviewed: *reviewed, + activate_order_type: (*activate_order_type).into(), + activate_rth: activate_rth + .as_ref() + .map(|value| value as *const COutsideRTH) + .unwrap_or(std::ptr::null()), + submit_price: submit_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + } + } +} + +/// Options for submit attached order params +#[derive(Debug)] +#[repr(C)] +pub struct CSubmitAttachedParams { + /// Attached order type + pub attached_order_type: CAttachedOrderType, + /// Take-profit trigger price (can be null) + pub profit_taker_price: *const CDecimal, + /// Stop-loss trigger price (can be null) + pub stop_loss_price: *const CDecimal, + /// Time in force type (can be null) + pub time_in_force: *const CTimeInForceType, + /// Expiry time unix timestamp (can be null) + pub expire_time: *const i64, + /// Order type to submit after trigger (can be null) + pub activate_order_type: *const COrderType, + /// Take-profit limit price (can be null) + pub profit_taker_submit_price: *const CDecimal, + /// Stop-loss limit price (can be null) + pub stop_loss_submit_price: *const CDecimal, + /// RTH setting for activated order (can be null) + pub activate_rth: *const COutsideRTH, +} + +/// Options for replace attached order params +#[derive(Debug)] +#[repr(C)] +pub struct CReplaceAttachedParams { + /// Attached order type + pub attached_order_type: CAttachedOrderType, + /// Take-profit trigger price (can be null) + pub profit_taker_price: *const CDecimal, + /// Stop-loss trigger price (can be null) + pub stop_loss_price: *const CDecimal, + /// Time in force type (can be null) + pub time_in_force: *const CTimeInForceType, + /// Expiry time unix timestamp (can be null) + pub expire_time: *const i64, + /// Order type to submit after trigger (can be null) + pub activate_order_type: *const COrderType, + /// Take-profit limit price (can be null) + pub profit_taker_submit_price: *const CDecimal, + /// Stop-loss limit price (can be null) + pub stop_loss_submit_price: *const CDecimal, + /// RTH setting for activated order (can be null) + pub activate_rth: *const COutsideRTH, + /// Take-profit order ID (can be null) + pub profit_taker_id: *const i64, + /// Stop-loss order ID (can be null) + pub stop_loss_id: *const i64, + /// Cancel all attached orders flag (can be null) + pub cancel_all_attached: *const bool, + /// Main order ID (can be null) + pub main_id: *const i64, + /// Quantity (can be null) + pub quantity: *const CDecimal, + /// Market price (can be null) + pub market_price: *const CDecimal, +} + /// Order changed message #[repr(C)] pub struct CPushOrderChanged { @@ -459,6 +744,10 @@ pub struct COrder { pub monitor_price: *const CDecimal, /// Remark pub remark: *const c_char, + /// Attached orders + pub attached_orders: *const CAttachedOrderDetail, + /// Number of attached orders + pub num_attached_orders: usize, } #[derive(Debug)] @@ -492,6 +781,7 @@ pub(crate) struct COrderOwned { trigger_count: Option, monitor_price: Option, remark: CString, + attached_orders: CVec, } impl From for COrderOwned { @@ -526,6 +816,7 @@ impl From for COrderOwned { trigger_count, monitor_price, remark, + attached_orders, } = order; COrderOwned { order_id: order_id.into(), @@ -557,6 +848,7 @@ impl From for COrderOwned { trigger_count, monitor_price: monitor_price.map(Into::into), remark: remark.into(), + attached_orders: attached_orders.into(), } } } @@ -595,6 +887,7 @@ impl ToFFI for COrderOwned { trigger_count, monitor_price, remark, + attached_orders, } = self; COrder { order_id: order_id.to_ffi_type(), @@ -671,6 +964,8 @@ impl ToFFI for COrderOwned { .map(ToFFI::to_ffi_type) .unwrap_or(std::ptr::null()), remark: remark.to_ffi_type(), + attached_orders: attached_orders.to_ffi_type(), + num_attached_orders: attached_orders.len(), } } } @@ -711,6 +1006,8 @@ pub struct CGetTodayOrdersOptions { pub market: *const CMarket, /// Order id (can be null) pub order_id: *const c_char, + /// Filter by attached order (can be null) + pub is_attached: *const bool, } /// Options for replace order request @@ -739,6 +1036,8 @@ pub struct CReplaceOrderOptions { pub monitor_price: *const CDecimal, /// Remark (can be null) pub remark: *const c_char, + /// Attached order parameters (can be null) + pub attached_params: *const CReplaceAttachedParams, } /// Options for submit order request @@ -782,6 +1081,8 @@ pub struct CSubmitOrderOptions { /// If not specified, idempotency control is skipped. /// The server caches this ID for 10 minutes. pub client_request_id: *const c_char, + /// Attached order parameters (can be null) + pub attached_params: *const CSubmitAttachedParams, } /// Response for submit order request @@ -1829,6 +2130,10 @@ pub struct COrderDetail { pub num_history: usize, /// Order charges pub charge_detail: COrderChargeDetail, + /// Attached orders + pub attached_orders: *const CAttachedOrderDetail, + /// Number of attached orders + pub num_attached_orders: usize, } #[derive(Debug)] @@ -1873,6 +2178,7 @@ pub(crate) struct COrderDetailOwned { platform_deducted_currency: Option, history: CVec, charge_detail: COrderChargeDetailOwned, + attached_orders: CVec, } impl From for COrderDetailOwned { @@ -1918,6 +2224,7 @@ impl From for COrderDetailOwned { platform_deducted_currency, history, charge_detail, + attached_orders, } = order; COrderDetailOwned { order_id: order_id.into(), @@ -1960,6 +2267,7 @@ impl From for COrderDetailOwned { platform_deducted_currency: platform_deducted_currency.map(Into::into), history: history.into(), charge_detail: charge_detail.into(), + attached_orders: attached_orders.into(), } } } @@ -2009,6 +2317,7 @@ impl ToFFI for COrderDetailOwned { platform_deducted_currency, history, charge_detail, + attached_orders, } = self; COrderDetail { order_id: order_id.to_ffi_type(), @@ -2115,6 +2424,8 @@ impl ToFFI for COrderDetailOwned { history: history.to_ffi_type(), num_history: history.len(), charge_detail: charge_detail.to_ffi_type(), + attached_orders: attached_orders.to_ffi_type(), + num_attached_orders: attached_orders.len(), } } } diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp index 86f9bb9cc6..ef8229582e 100644 --- a/cpp/include/trade_context.hpp +++ b/cpp/include/trade_context.hpp @@ -113,6 +113,11 @@ class TradeContext void order_detail(const std::string& order_id, AsyncCallback callback) const; + /// Get order detail with attached orders + void order_detail_attached( + const std::string& order_id, + AsyncCallback callback) const; + /// Estimating the maximum purchase quantity for Hong Kong and US stocks, /// warrants, and options void estimate_max_purchase_quantity( diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index c161ddedc0..f64b505d13 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1552,6 +1552,79 @@ enum class OutsideRTH OptionPreMarket, }; +/// Attached order type +enum class AttachedOrderType +{ + /// Unknown + Unknown, + /// Take profit + ProfitTaker, + /// Stop loss + StopLoss, + /// Bracket order + Bracket, +}; + +/// Attached order detail +struct AttachedOrderDetail +{ + std::string order_id; + int32_t attached_type_display; + std::optional trigger_price; + Decimal quantity; + Decimal executed_qty; + OrderStatus status; + int64_t updated_at; + bool withdrawn; + std::optional gtd; + TimeInForceType time_in_force; + std::string counter_id; + int32_t trigger_status; + Decimal executed_amount; + int32_t tag; + int64_t submitted_at; + Decimal executed_price; + std::optional force_only_rth; + bool reviewed; + OrderType activate_order_type; + std::optional activate_rth; + std::optional submit_price; +}; + +/// Submit attached order params +struct SubmitAttachedParams +{ + AttachedOrderType attached_order_type; + std::optional profit_taker_price; + std::optional stop_loss_price; + std::optional time_in_force; + std::optional expire_time; + std::optional activate_order_type; + std::optional profit_taker_submit_price; + std::optional stop_loss_submit_price; + std::optional activate_rth; +}; + +/// Replace attached order params +struct ReplaceAttachedParams +{ + AttachedOrderType attached_order_type; + std::optional profit_taker_price; + std::optional stop_loss_price; + std::optional time_in_force; + std::optional expire_time; + std::optional activate_order_type; + std::optional profit_taker_submit_price; + std::optional stop_loss_submit_price; + std::optional activate_rth; + std::optional profit_taker_id; + std::optional stop_loss_id; + std::optional cancel_all_attached; + std::optional main_id; + std::optional quantity; + std::optional market_price; +}; + /// Order struct Order { @@ -1613,6 +1686,8 @@ struct Order std::optional monitor_price; /// Remark std::string remark; + /// Attached orders + std::vector attached_orders; }; /// Order changed message @@ -1700,6 +1775,8 @@ struct GetTodayOrdersOptions std::optional market; /// Order id std::optional order_id; + /// Whether to include attached orders + std::optional is_attached; }; /// Options for replace order request @@ -1727,6 +1804,8 @@ struct ReplaceOrderOptions std::optional monitor_price; /// Remark std::optional remark; + /// Attached order params + std::optional attached_params; }; /// Options for submit order request @@ -1769,6 +1848,8 @@ struct SubmitOrderOptions /// If not specified, idempotency control is skipped. /// The server caches this ID for 10 minutes. std::optional client_request_id; + /// Attached order params + std::optional attached_params; }; /// Response for submit order request @@ -2152,6 +2233,8 @@ struct OrderDetail std::vector history; /// Order charges OrderChargeDetail charge_detail; + /// Attached orders + std::vector attached_orders; }; /// Options for estimate maximum purchase quantity diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index 873d82752b..41bffe1219 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -70,6 +70,8 @@ using longbridge::quote::WarrantType; using longbridge::quote::WatchlistGroup; using longbridge::quote::WatchlistSecurity; using longbridge::trade::AccountBalance; +using longbridge::trade::AttachedOrderDetail; +using longbridge::trade::AttachedOrderType; using longbridge::trade::BalanceType; using longbridge::trade::CashFlow; using longbridge::trade::CashFlowDirection; @@ -99,9 +101,11 @@ using longbridge::trade::OrderTag; using longbridge::trade::OrderType; using longbridge::trade::OutsideRTH; using longbridge::trade::PushOrderChanged; +using longbridge::trade::ReplaceAttachedParams; using longbridge::trade::StockPosition; using longbridge::trade::StockPositionChannel; using longbridge::trade::StockPositionsResponse; +using longbridge::trade::SubmitAttachedParams; using longbridge::trade::SubmitOrderResponse; using longbridge::trade::TimeInForceType; using longbridge::trade::TopicType; @@ -1280,9 +1284,178 @@ convert(OutsideRTH status) } } +inline AttachedOrderType +convert(lb_attached_order_type_t ty) +{ + switch (ty) { + case AttachedOrderTypeUnknown: + return AttachedOrderType::Unknown; + case AttachedOrderTypeProfitTaker: + return AttachedOrderType::ProfitTaker; + case AttachedOrderTypeStopLoss: + return AttachedOrderType::StopLoss; + case AttachedOrderTypeBracket: + return AttachedOrderType::Bracket; + default: + throw std::invalid_argument("unreachable"); + } +} + +inline lb_attached_order_type_t +convert(AttachedOrderType ty) +{ + switch (ty) { + case AttachedOrderType::Unknown: + return AttachedOrderTypeUnknown; + case AttachedOrderType::ProfitTaker: + return AttachedOrderTypeProfitTaker; + case AttachedOrderType::StopLoss: + return AttachedOrderTypeStopLoss; + case AttachedOrderType::Bracket: + return AttachedOrderTypeBracket; + default: + throw std::invalid_argument("unreachable"); + } +} + +inline AttachedOrderDetail +convert(const lb_attached_order_detail_t* detail) +{ + return AttachedOrderDetail{ + detail->order_id, + detail->attached_type_display, + detail->trigger_price ? std::optional{ Decimal(detail->trigger_price) } + : std::nullopt, + Decimal(detail->quantity), + Decimal(detail->executed_qty), + convert(detail->status), + detail->updated_at, + detail->withdrawn, + detail->gtd ? std::optional{ convert(detail->gtd) } : std::nullopt, + convert(detail->time_in_force), + detail->counter_id, + detail->trigger_status, + Decimal(detail->executed_amount), + detail->tag, + detail->submitted_at, + Decimal(detail->executed_price), + detail->force_only_rth ? std::optional{ convert(*detail->force_only_rth) } + : std::nullopt, + detail->reviewed, + convert(detail->activate_order_type), + detail->activate_rth ? std::optional{ convert(*detail->activate_rth) } + : std::nullopt, + detail->submit_price ? std::optional{ Decimal(detail->submit_price) } + : std::nullopt, + }; +} + +struct CSubmitAttachedParamsStorage +{ + lb_time_in_force_type_t time_in_force; + lb_order_type_t activate_order_type; + lb_outside_rth_t activate_rth; + lb_submit_attached_params_t params; +}; + +inline CSubmitAttachedParamsStorage +convert_submit_attached(const SubmitAttachedParams& src) +{ + CSubmitAttachedParamsStorage s{}; + s.params.attached_order_type = convert(src.attached_order_type); + s.params.profit_taker_price = + src.profit_taker_price + ? (const lb_decimal_t*)src.profit_taker_price.value() + : nullptr; + s.params.stop_loss_price = + src.stop_loss_price ? (const lb_decimal_t*)src.stop_loss_price.value() + : nullptr; + if (src.time_in_force) { + s.time_in_force = convert(*src.time_in_force); + s.params.time_in_force = &s.time_in_force; + } + s.params.expire_time = src.expire_time ? &*src.expire_time : nullptr; + if (src.activate_order_type) { + s.activate_order_type = convert(*src.activate_order_type); + s.params.activate_order_type = &s.activate_order_type; + } + s.params.profit_taker_submit_price = + src.profit_taker_submit_price + ? (const lb_decimal_t*)src.profit_taker_submit_price.value() + : nullptr; + s.params.stop_loss_submit_price = + src.stop_loss_submit_price + ? (const lb_decimal_t*)src.stop_loss_submit_price.value() + : nullptr; + if (src.activate_rth) { + s.activate_rth = convert(*src.activate_rth); + s.params.activate_rth = &s.activate_rth; + } + return s; +} + +struct CReplaceAttachedParamsStorage +{ + lb_time_in_force_type_t time_in_force; + lb_order_type_t activate_order_type; + lb_outside_rth_t activate_rth; + lb_replace_attached_params_t params; +}; + +inline CReplaceAttachedParamsStorage +convert_replace_attached(const ReplaceAttachedParams& src) +{ + CReplaceAttachedParamsStorage s{}; + s.params.attached_order_type = convert(src.attached_order_type); + s.params.profit_taker_price = + src.profit_taker_price + ? (const lb_decimal_t*)src.profit_taker_price.value() + : nullptr; + s.params.stop_loss_price = + src.stop_loss_price ? (const lb_decimal_t*)src.stop_loss_price.value() + : nullptr; + if (src.time_in_force) { + s.time_in_force = convert(*src.time_in_force); + s.params.time_in_force = &s.time_in_force; + } + s.params.expire_time = src.expire_time ? &*src.expire_time : nullptr; + if (src.activate_order_type) { + s.activate_order_type = convert(*src.activate_order_type); + s.params.activate_order_type = &s.activate_order_type; + } + s.params.profit_taker_submit_price = + src.profit_taker_submit_price + ? (const lb_decimal_t*)src.profit_taker_submit_price.value() + : nullptr; + s.params.stop_loss_submit_price = + src.stop_loss_submit_price + ? (const lb_decimal_t*)src.stop_loss_submit_price.value() + : nullptr; + if (src.activate_rth) { + s.activate_rth = convert(*src.activate_rth); + s.params.activate_rth = &s.activate_rth; + } + s.params.profit_taker_id = + src.profit_taker_id ? &*src.profit_taker_id : nullptr; + s.params.stop_loss_id = src.stop_loss_id ? &*src.stop_loss_id : nullptr; + s.params.cancel_all_attached = + src.cancel_all_attached ? &*src.cancel_all_attached : nullptr; + s.params.main_id = src.main_id ? &*src.main_id : nullptr; + s.params.quantity = + src.quantity ? (const lb_decimal_t*)src.quantity.value() : nullptr; + s.params.market_price = + src.market_price ? (const lb_decimal_t*)src.market_price.value() : nullptr; + return s; +} + inline Order convert(const lb_order_t* order) { + std::vector attached_orders; + std::transform(order->attached_orders, + order->attached_orders + order->num_attached_orders, + std::back_inserter(attached_orders), + [](auto& item) { return convert(&item); }); return Order{ order->order_id, convert(order->status), @@ -1324,7 +1497,8 @@ convert(const lb_order_t* order) : std::nullopt, order->monitor_price ? std::optional{ Decimal(order->monitor_price) } : std::nullopt, - order->remark + order->remark, + attached_orders, }; } @@ -1705,6 +1879,12 @@ convert(const lb_order_detail_t* order) std::back_inserter(history), [](auto item) { return convert(&item); }); + std::vector attached_orders; + std::transform(order->attached_orders, + order->attached_orders + order->num_attached_orders, + std::back_inserter(attached_orders), + [](auto& item) { return convert(&item); }); + return OrderDetail{ order->order_id, convert(order->status), @@ -1768,6 +1948,7 @@ convert(const lb_order_detail_t* order) : std::nullopt, history, convert(&order->charge_detail), + attached_orders, }; } diff --git a/cpp/src/trade_context.cpp b/cpp/src/trade_context.cpp index c1966fe323..5857c076f2 100644 --- a/cpp/src/trade_context.cpp +++ b/cpp/src/trade_context.cpp @@ -8,6 +8,8 @@ namespace longbridge { namespace trade { using longbridge::convert::convert; +using longbridge::convert::convert_submit_attached; +using longbridge::convert::convert_replace_attached; TradeContext::TradeContext() : ctx_(nullptr) @@ -333,7 +335,7 @@ TradeContext::today_orders( AsyncCallback> callback) const { lb_get_today_orders_options_t opts2 = { - nullptr, nullptr, 0, nullptr, nullptr, nullptr, + nullptr, nullptr, 0, nullptr, nullptr, nullptr, nullptr, }; std::vector order_status; lb_order_side_t side; @@ -364,6 +366,7 @@ TradeContext::today_orders( } opts2.order_id = opts->order_id ? opts->order_id->c_str() : nullptr; + opts2.is_attached = opts->is_attached ? &opts->is_attached.value() : nullptr; } lb_trade_context_today_orders( @@ -410,7 +413,9 @@ TradeContext::replace_order(const ReplaceOrderOptions& opts, nullptr, nullptr, nullptr, + nullptr, }; + longbridge::convert::CReplaceAttachedParamsStorage attached_params_storage; opts2.price = opts.price ? (const lb_decimal_t*)opts.price.value() : nullptr; opts2.trigger_price = opts.trigger_price @@ -433,6 +438,10 @@ TradeContext::replace_order(const ReplaceOrderOptions& opts, ? (const lb_decimal_t*)opts.monitor_price.value() : nullptr; opts2.remark = opts.remark ? opts.remark->c_str() : nullptr; + if (opts.attached_params) { + attached_params_storage = convert_replace_attached(*opts.attached_params); + opts2.attached_params = &attached_params_storage.params; + } lb_trade_context_replace_order( ctx_, @@ -471,9 +480,11 @@ TradeContext::submit_order( nullptr, opts.remark ? opts.remark->c_str() : nullptr, nullptr, + nullptr, }; lb_date_t expire_date; lb_outside_rth_t outside_rth; + longbridge::convert::CSubmitAttachedParamsStorage attached_params_storage; if (opts.submitted_price) { opts2.submitted_price = (const lb_decimal_t*)opts.submitted_price.value(); @@ -510,6 +521,10 @@ TradeContext::submit_order( if (opts.client_request_id) { opts2.client_request_id = opts.client_request_id->c_str(); } + if (opts.attached_params) { + attached_params_storage = convert_submit_attached(*opts.attached_params); + opts2.attached_params = &attached_params_storage.params; + } lb_trade_context_submit_order( ctx_, @@ -799,6 +814,32 @@ TradeContext::order_detail( new AsyncCallback(callback)); } +void +TradeContext::order_detail_attached( + const std::string& order_id, + AsyncCallback callback) const +{ + lb_trade_context_order_detail_attached( + ctx_, + order_id.c_str(), + [](auto res) { + auto callback_ptr = + callback::get_async_callback(res->userdata); + TradeContext ctx((const lb_trade_context_t*)res->ctx); + Status status(res->error); + + if (status) { + OrderDetail resp = convert((const lb_order_detail_t*)res->data); + (*callback_ptr)(AsyncResult( + ctx, std::move(status), &resp)); + } else { + (*callback_ptr)(AsyncResult( + ctx, std::move(status), nullptr)); + } + }, + new AsyncCallback(callback)); +} + void TradeContext::estimate_max_purchase_quantity( const EstimateMaxPurchaseQuantityOptions& opts, diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 41230ace36..629fef5fdd 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -252,6 +252,8 @@ public static native void tradeContextStockPositions(long context, GetStockPosit public static native void tradeContextOrderDetail(long context, String orderId, AsyncCallback callback); + static native void tradeContextOrderDetailAttached(long context, String orderId, Object callback); + public static native void tradeContextEstimateMaxPurchaseQuantity(long context, EstimateMaxPurchaseQuantityOptions opts, AsyncCallback callback); diff --git a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java new file mode 100644 index 0000000000..82605548a7 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java @@ -0,0 +1,233 @@ +package com.longbridge.trade; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.OffsetDateTime; + +/** + * Attached order detail + */ +public class AttachedOrderDetail { + private String orderId; + private int attachedTypeDisplay; + private BigDecimal triggerPrice; + private BigDecimal quantity; + private BigDecimal executedQty; + private OrderStatus status; + private OffsetDateTime updatedAt; + private boolean withdrawn; + private LocalDate gtd; + private TimeInForceType timeInForce; + private String counterId; + private int triggerStatus; + private BigDecimal executedAmount; + private int tag; + private OffsetDateTime submittedAt; + private BigDecimal executedPrice; + private OutsideRTH forceOnlyRth; + private boolean reviewed; + private OrderType activateOrderType; + private OutsideRTH activateRth; + private BigDecimal submitPrice; + + /** + * Returns the order ID. + * + * @return order ID + */ + public String getOrderId() { + return orderId; + } + + /** + * Returns the attached type display (1=take-profit, 2=stop-loss). + * + * @return attached type display + */ + public int getAttachedTypeDisplay() { + return attachedTypeDisplay; + } + + /** + * Returns the trigger price. + * + * @return trigger price + */ + public BigDecimal getTriggerPrice() { + return triggerPrice; + } + + /** + * Returns the quantity. + * + * @return quantity + */ + public BigDecimal getQuantity() { + return quantity; + } + + /** + * Returns the executed quantity. + * + * @return executed quantity + */ + public BigDecimal getExecutedQty() { + return executedQty; + } + + /** + * Returns the order status. + * + * @return order status + */ + public OrderStatus getStatus() { + return status; + } + + /** + * Returns the last update time. + * + * @return last update time + */ + public OffsetDateTime getUpdatedAt() { + return updatedAt; + } + + /** + * Returns whether the order has been withdrawn. + * + * @return withdrawn flag + */ + public boolean isWithdrawn() { + return withdrawn; + } + + /** + * Returns the good-till date. + * + * @return good-till date + */ + public LocalDate getGtd() { + return gtd; + } + + /** + * Returns the time-in-force type. + * + * @return time-in-force type + */ + public TimeInForceType getTimeInForce() { + return timeInForce; + } + + /** + * Returns the counter ID. + * + * @return counter ID + */ + public String getCounterId() { + return counterId; + } + + /** + * Returns the trigger status. + * + * @return trigger status + */ + public int getTriggerStatus() { + return triggerStatus; + } + + /** + * Returns the executed amount. + * + * @return executed amount + */ + public BigDecimal getExecutedAmount() { + return executedAmount; + } + + /** + * Returns the tag. + * + * @return tag + */ + public int getTag() { + return tag; + } + + /** + * Returns the submission time. + * + * @return submission time + */ + public OffsetDateTime getSubmittedAt() { + return submittedAt; + } + + /** + * Returns the executed price. + * + * @return executed price + */ + public BigDecimal getExecutedPrice() { + return executedPrice; + } + + /** + * Returns the force-only-RTH setting. + * + * @return force-only-RTH setting + */ + public OutsideRTH getForceOnlyRth() { + return forceOnlyRth; + } + + /** + * Returns whether the order has been reviewed. + * + * @return reviewed flag + */ + public boolean isReviewed() { + return reviewed; + } + + /** + * Returns the activate order type. + * + * @return activate order type + */ + public OrderType getActivateOrderType() { + return activateOrderType; + } + + /** + * Returns the activate RTH setting. + * + * @return activate RTH setting + */ + public OutsideRTH getActivateRth() { + return activateRth; + } + + /** + * Returns the submit price. + * + * @return submit price + */ + public BigDecimal getSubmitPrice() { + return submitPrice; + } + + @Override + public String toString() { + return "AttachedOrderDetail [orderId=" + orderId + ", attachedTypeDisplay=" + attachedTypeDisplay + + ", triggerPrice=" + triggerPrice + ", quantity=" + quantity + ", executedQty=" + executedQty + + ", status=" + status + ", updatedAt=" + updatedAt + ", withdrawn=" + withdrawn + ", gtd=" + gtd + + ", timeInForce=" + timeInForce + ", counterId=" + counterId + ", triggerStatus=" + triggerStatus + + ", executedAmount=" + executedAmount + ", tag=" + tag + ", submittedAt=" + submittedAt + + ", executedPrice=" + executedPrice + ", forceOnlyRth=" + forceOnlyRth + ", reviewed=" + reviewed + + ", activateOrderType=" + activateOrderType + ", activateRth=" + activateRth + ", submitPrice=" + + submitPrice + "]"; + } +} diff --git a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java new file mode 100644 index 0000000000..bd1d4cefbf --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java @@ -0,0 +1,11 @@ +package com.longbridge.trade; + +/** Attached order type */ +public enum AttachedOrderType { + /** Take profit */ + PROFIT_TAKER, + /** Stop loss */ + STOP_LOSS, + /** Bracket order */ + BRACKET +} diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java index 38d2828f01..37b104bc63 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java @@ -12,6 +12,7 @@ public class GetTodayOrdersOptions { private OrderSide side; private Market market; private String orderId; + private Boolean isAttached; /** * Filters by security symbol. @@ -68,4 +69,23 @@ public GetTodayOrdersOptions setOrderId(String orderId) { return this; } + /** + * Filters to include only attached orders. + * + * @return this instance for chaining + */ + public GetTodayOrdersOptions setIsAttached() { + this.isAttached = true; + return this; + } + + /** + * Returns the is-attached filter. + * + * @return is-attached filter + */ + public Boolean getIsAttached() { + return isAttached; + } + } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/Order.java b/java/javasrc/src/main/java/com/longbridge/trade/Order.java index d489905752..b5f9587574 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/Order.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/Order.java @@ -37,6 +37,7 @@ public class Order { private Integer triggerCount; private BigDecimal monitorPrice; private String remark; + private AttachedOrderDetail[] attachedOrders = new AttachedOrderDetail[0]; /** * Returns the order ID. @@ -299,6 +300,15 @@ public BigDecimal getMonitorPrice() { return monitorPrice; } + /** + * Returns the attached orders. + * + * @return attached orders + */ + public AttachedOrderDetail[] getAttachedOrders() { + return attachedOrders; + } + @Override public String toString() { return "Order [orderId=" + orderId + ", status=" + status + ", stockName=" + stockName + ", quantity=" diff --git a/java/javasrc/src/main/java/com/longbridge/trade/OrderDetail.java b/java/javasrc/src/main/java/com/longbridge/trade/OrderDetail.java index 7c3f115051..ed9c764ab1 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/OrderDetail.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/OrderDetail.java @@ -49,6 +49,7 @@ public class OrderDetail { private String platformDeductedCurrency; private OrderHistoryDetail[] history; private OrderChargeDetail chargeDetail; + private AttachedOrderDetail[] attachedOrders = new AttachedOrderDetail[0]; /** * Returns the order ID. @@ -410,6 +411,15 @@ public OrderChargeDetail getChargeDetail() { return chargeDetail; } + /** + * Returns the attached orders. + * + * @return attached orders + */ + public AttachedOrderDetail[] getAttachedOrders() { + return attachedOrders; + } + @Override public String toString() { return "OrderDetail [orderId=" + orderId + ", status=" + status + ", stockName=" + stockName + ", quantity=" diff --git a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java new file mode 100644 index 0000000000..c1bc9d6ba5 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java @@ -0,0 +1,84 @@ +package com.longbridge.trade; + +import java.math.BigDecimal; + +/** Attached order parameters for replace order */ +public class ReplaceAttachedParams { + private AttachedOrderType attachedOrderType; + private BigDecimal profitTakerPrice; + private BigDecimal stopLossPrice; + private TimeInForceType timeInForce; + private Long expireTime; + private OrderType activateOrderType; + private BigDecimal profitTakerSubmitPrice; + private BigDecimal stopLossSubmitPrice; + private OutsideRTH activateRth; + private Long profitTakerId; + private Long stopLossId; + private Boolean cancelAllAttached; + private Long mainId; + private BigDecimal quantity; + private BigDecimal marketPrice; + + public ReplaceAttachedParams(AttachedOrderType attachedOrderType) { + this.attachedOrderType = attachedOrderType; + } + + public AttachedOrderType getAttachedOrderType() { return attachedOrderType; } + + public ReplaceAttachedParams setProfitTakerPrice(BigDecimal v) { this.profitTakerPrice = v; return this; } + + public BigDecimal getProfitTakerPrice() { return profitTakerPrice; } + + public ReplaceAttachedParams setStopLossPrice(BigDecimal v) { this.stopLossPrice = v; return this; } + + public BigDecimal getStopLossPrice() { return stopLossPrice; } + + public ReplaceAttachedParams setTimeInForce(TimeInForceType v) { this.timeInForce = v; return this; } + + public TimeInForceType getTimeInForce() { return timeInForce; } + + public ReplaceAttachedParams setExpireTime(long v) { this.expireTime = v; return this; } + + public Long getExpireTime() { return expireTime; } + + public ReplaceAttachedParams setActivateOrderType(OrderType v) { this.activateOrderType = v; return this; } + + public OrderType getActivateOrderType() { return activateOrderType; } + + public ReplaceAttachedParams setProfitTakerSubmitPrice(BigDecimal v) { this.profitTakerSubmitPrice = v; return this; } + + public BigDecimal getProfitTakerSubmitPrice() { return profitTakerSubmitPrice; } + + public ReplaceAttachedParams setStopLossSubmitPrice(BigDecimal v) { this.stopLossSubmitPrice = v; return this; } + + public BigDecimal getStopLossSubmitPrice() { return stopLossSubmitPrice; } + + public ReplaceAttachedParams setActivateRth(OutsideRTH v) { this.activateRth = v; return this; } + + public OutsideRTH getActivateRth() { return activateRth; } + + public ReplaceAttachedParams setProfitTakerId(long v) { this.profitTakerId = v; return this; } + + public Long getProfitTakerId() { return profitTakerId; } + + public ReplaceAttachedParams setStopLossId(long v) { this.stopLossId = v; return this; } + + public Long getStopLossId() { return stopLossId; } + + public ReplaceAttachedParams setCancelAllAttached(boolean v) { this.cancelAllAttached = v; return this; } + + public Boolean getCancelAllAttached() { return cancelAllAttached; } + + public ReplaceAttachedParams setMainId(long v) { this.mainId = v; return this; } + + public Long getMainId() { return mainId; } + + public ReplaceAttachedParams setQuantity(BigDecimal v) { this.quantity = v; return this; } + + public BigDecimal getQuantity() { return quantity; } + + public ReplaceAttachedParams setMarketPrice(BigDecimal v) { this.marketPrice = v; return this; } + + public BigDecimal getMarketPrice() { return marketPrice; } +} diff --git a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java index dcadcfcfdc..cbd5d7863f 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java @@ -18,6 +18,7 @@ public class ReplaceOrderOptions { private Integer triggerCount; private BigDecimal monitorPrice; private String remark; + private ReplaceAttachedParams attachedParams; /** * Constructs options for replacing an order. @@ -129,4 +130,24 @@ public ReplaceOrderOptions setRemark(String remark) { return this; } + /** + * Sets the attached order parameters. + * + * @param attachedParams attached order parameters + * @return this instance for chaining + */ + public ReplaceOrderOptions setAttachedParams(ReplaceAttachedParams attachedParams) { + this.attachedParams = attachedParams; + return this; + } + + /** + * Returns the attached order parameters. + * + * @return attached order parameters + */ + public ReplaceAttachedParams getAttachedParams() { + return attachedParams; + } + } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java b/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java new file mode 100644 index 0000000000..83c4b53a8e --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java @@ -0,0 +1,54 @@ +package com.longbridge.trade; + +import java.math.BigDecimal; + +/** Attached order parameters for submit order */ +public class SubmitAttachedParams { + private AttachedOrderType attachedOrderType; + private BigDecimal profitTakerPrice; + private BigDecimal stopLossPrice; + private TimeInForceType timeInForce; + private Long expireTime; + private OrderType activateOrderType; + private BigDecimal profitTakerSubmitPrice; + private BigDecimal stopLossSubmitPrice; + private OutsideRTH activateRth; + + public SubmitAttachedParams(AttachedOrderType attachedOrderType) { + this.attachedOrderType = attachedOrderType; + } + + public AttachedOrderType getAttachedOrderType() { return attachedOrderType; } + + public SubmitAttachedParams setProfitTakerPrice(BigDecimal v) { this.profitTakerPrice = v; return this; } + + public BigDecimal getProfitTakerPrice() { return profitTakerPrice; } + + public SubmitAttachedParams setStopLossPrice(BigDecimal v) { this.stopLossPrice = v; return this; } + + public BigDecimal getStopLossPrice() { return stopLossPrice; } + + public SubmitAttachedParams setTimeInForce(TimeInForceType v) { this.timeInForce = v; return this; } + + public TimeInForceType getTimeInForce() { return timeInForce; } + + public SubmitAttachedParams setExpireTime(long v) { this.expireTime = v; return this; } + + public Long getExpireTime() { return expireTime; } + + public SubmitAttachedParams setActivateOrderType(OrderType v) { this.activateOrderType = v; return this; } + + public OrderType getActivateOrderType() { return activateOrderType; } + + public SubmitAttachedParams setProfitTakerSubmitPrice(BigDecimal v) { this.profitTakerSubmitPrice = v; return this; } + + public BigDecimal getProfitTakerSubmitPrice() { return profitTakerSubmitPrice; } + + public SubmitAttachedParams setStopLossSubmitPrice(BigDecimal v) { this.stopLossSubmitPrice = v; return this; } + + public BigDecimal getStopLossSubmitPrice() { return stopLossSubmitPrice; } + + public SubmitAttachedParams setActivateRth(OutsideRTH v) { this.activateRth = v; return this; } + + public OutsideRTH getActivateRth() { return activateRth; } +} diff --git a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java index 2f536d2b81..027b26015c 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java @@ -25,6 +25,7 @@ public class SubmitOrderOptions { private BigDecimal monitorPrice; private String remark; private String clientRequestId; + private SubmitAttachedParams attachedParams; /** * Constructs options for submitting an order. @@ -181,4 +182,24 @@ public SubmitOrderOptions setClientRequestId(String clientRequestId) { this.clientRequestId = clientRequestId; return this; } + + /** + * Sets the attached order parameters. + * + * @param attachedParams attached order parameters + * @return this instance for chaining + */ + public SubmitOrderOptions setAttachedParams(SubmitAttachedParams attachedParams) { + this.attachedParams = attachedParams; + return this; + } + + /** + * Returns the attached order parameters. + * + * @return attached order parameters + */ + public SubmitAttachedParams getAttachedParams() { + return attachedParams; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java index d2cc5061ad..f6ec64f40b 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -588,6 +588,20 @@ public CompletableFuture getOrderDetail(String orderId) }); } + /** + * Get order detail for an attached order + * + * @param orderId Attached order ID + * @return A Future representing the result + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getOrderDetailAttached(String orderId) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.tradeContextOrderDetailAttached(this.raw, orderId, callback); + }); + } + /** * Estimating the maximum purchase quantity for Hong Kong and US stocks, * warrants, and options diff --git a/java/src/init.rs b/java/src/init.rs index 50c5653ac9..96ad987ba4 100644 --- a/java/src/init.rs +++ b/java/src/init.rs @@ -112,6 +112,7 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init<'a>( longbridge::trade::CommissionFreeStatus, longbridge::trade::DeductionStatus, longbridge::trade::ChargeCategoryCode, + longbridge::trade::AttachedOrderType, longbridge::quote::PinnedMode, longbridge::portfolio::types::FlowDirection, longbridge::portfolio::types::AssetType, @@ -168,6 +169,7 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init<'a>( longbridge::quote::MarketTemperature, longbridge::quote::HistoryMarketTemperatureResponse, longbridge::quote::FilingItem, + longbridge::trade::AttachedOrderDetail, longbridge::trade::PushOrderChanged, longbridge::trade::Execution, longbridge::trade::AllExecutionsResponse, diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index f4e0d21cdb..4003f14626 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -9,11 +9,12 @@ use jni::{ use longbridge::{ Config, Decimal, Market, TradeContext, trade::{ - BalanceType, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, - GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, - GetHistoryOrdersOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, - GetTodayOrdersOptions, OrderSide, OrderStatus, OrderType, OutsideRTH, PushEvent, - QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, TimeInForceType, TopicType, + AttachedOrderType, BalanceType, EstimateMaxPurchaseQuantityOptions, + GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, OrderSide, + OrderStatus, OrderType, OutsideRTH, PushEvent, QueryUSOrdersOptions, ReplaceAttachedParams, + ReplaceOrderOptions, SubmitAttachedParams, SubmitOrderOptions, TimeInForceType, TopicType, }, }; use parking_lot::Mutex; @@ -22,7 +23,7 @@ use time::{Date, OffsetDateTime}; use crate::{ async_util, error::jni_result, - types::{FromJValue, IntoJValue, JavaInteger, ObjectArray, get_field}, + types::{FromJValue, IntoJValue, JavaInteger, JavaLong, ObjectArray, get_field}, }; #[derive(Default)] @@ -331,6 +332,10 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextTodayOrd if let Some(order_id) = order_id { new_opts = new_opts.order_id(order_id); } + let is_attached_obj = env.get_field(&opts, "isAttached", "Ljava/lang/Boolean;")?; + if !is_attached_obj.l()?.is_null() { + new_opts = new_opts.is_attached(); + } Some(new_opts) } else { None @@ -342,6 +347,117 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextTodayOrd }) } +fn read_submit_attached_params( + env: &mut JNIEnv<'_>, + obj: &JObject<'_>, +) -> jni::errors::Result { + let attached_order_type: AttachedOrderType = get_field(env, obj, "attachedOrderType")?; + let mut params = SubmitAttachedParams::new(attached_order_type); + let profit_taker_price: Option = get_field(env, obj, "profitTakerPrice")?; + if let Some(v) = profit_taker_price { + params = params.profit_taker_price(v); + } + let stop_loss_price: Option = get_field(env, obj, "stopLossPrice")?; + if let Some(v) = stop_loss_price { + params = params.stop_loss_price(v); + } + let time_in_force: Option = get_field(env, obj, "timeInForce")?; + if let Some(v) = time_in_force { + params = params.time_in_force(v); + } + let expire_time_obj = env.get_field(obj, "expireTime", "Ljava/lang/Long;")?; + if !expire_time_obj.l()?.is_null() { + let expire_time: JavaLong = get_field(env, obj, "expireTime")?; + params = params.expire_time(i64::from(expire_time)); + } + let activate_order_type: Option = get_field(env, obj, "activateOrderType")?; + if let Some(v) = activate_order_type { + params = params.activate_order_type(v); + } + let profit_taker_submit_price: Option = get_field(env, obj, "profitTakerSubmitPrice")?; + if let Some(v) = profit_taker_submit_price { + params = params.profit_taker_submit_price(v); + } + let stop_loss_submit_price: Option = get_field(env, obj, "stopLossSubmitPrice")?; + if let Some(v) = stop_loss_submit_price { + params = params.stop_loss_submit_price(v); + } + let activate_rth: Option = get_field(env, obj, "activateRth")?; + if let Some(v) = activate_rth { + params = params.activate_rth(v); + } + Ok(params) +} + +fn read_replace_attached_params( + env: &mut JNIEnv<'_>, + obj: &JObject<'_>, +) -> jni::errors::Result { + let attached_order_type: AttachedOrderType = get_field(env, obj, "attachedOrderType")?; + let mut params = ReplaceAttachedParams::new(attached_order_type); + let profit_taker_price: Option = get_field(env, obj, "profitTakerPrice")?; + if let Some(v) = profit_taker_price { + params = params.profit_taker_price(v); + } + let stop_loss_price: Option = get_field(env, obj, "stopLossPrice")?; + if let Some(v) = stop_loss_price { + params = params.stop_loss_price(v); + } + let time_in_force: Option = get_field(env, obj, "timeInForce")?; + if let Some(v) = time_in_force { + params = params.time_in_force(v); + } + let expire_time_obj = env.get_field(obj, "expireTime", "Ljava/lang/Long;")?; + if !expire_time_obj.l()?.is_null() { + let expire_time: JavaLong = get_field(env, obj, "expireTime")?; + params = params.expire_time(i64::from(expire_time)); + } + let activate_order_type: Option = get_field(env, obj, "activateOrderType")?; + if let Some(v) = activate_order_type { + params = params.activate_order_type(v); + } + let profit_taker_submit_price: Option = get_field(env, obj, "profitTakerSubmitPrice")?; + if let Some(v) = profit_taker_submit_price { + params = params.profit_taker_submit_price(v); + } + let stop_loss_submit_price: Option = get_field(env, obj, "stopLossSubmitPrice")?; + if let Some(v) = stop_loss_submit_price { + params = params.stop_loss_submit_price(v); + } + let activate_rth: Option = get_field(env, obj, "activateRth")?; + if let Some(v) = activate_rth { + params = params.activate_rth(v); + } + let profit_taker_id_obj = env.get_field(obj, "profitTakerId", "Ljava/lang/Long;")?; + if !profit_taker_id_obj.l()?.is_null() { + let profit_taker_id: JavaLong = get_field(env, obj, "profitTakerId")?; + params = params.profit_taker_id(i64::from(profit_taker_id)); + } + let stop_loss_id_obj = env.get_field(obj, "stopLossId", "Ljava/lang/Long;")?; + if !stop_loss_id_obj.l()?.is_null() { + let stop_loss_id: JavaLong = get_field(env, obj, "stopLossId")?; + params = params.stop_loss_id(i64::from(stop_loss_id)); + } + let cancel_all_obj = env.get_field(obj, "cancelAllAttached", "Ljava/lang/Boolean;")?; + if !cancel_all_obj.l()?.is_null() { + params = params.cancel_all_attached(); + } + let main_id_obj = env.get_field(obj, "mainId", "Ljava/lang/Long;")?; + if !main_id_obj.l()?.is_null() { + let main_id: JavaLong = get_field(env, obj, "mainId")?; + params = params.main_id(i64::from(main_id)); + } + let quantity: Option = get_field(env, obj, "quantity")?; + if let Some(v) = quantity { + params = params.quantity(v); + } + let market_price: Option = get_field(env, obj, "marketPrice")?; + if let Some(v) = market_price { + params = params.market_price(v); + } + Ok(params) +} + #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextReplaceOrder( mut env: JNIEnv, @@ -391,6 +507,22 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextReplaceO if let Some(remark) = remark { new_opts = new_opts.remark(remark); } + let attached_params_obj = env.get_field( + &opts, + "attachedParams", + "Lcom/longbridge/trade/ReplaceAttachedParams;", + )?; + if !attached_params_obj.l()?.is_null() { + let attached_params_java = env + .get_field( + &opts, + "attachedParams", + "Lcom/longbridge/trade/ReplaceAttachedParams;", + )? + .l()?; + let ap = read_replace_attached_params(env, &attached_params_java)?; + new_opts = new_opts.attached_params(ap); + } async_util::execute(env, callback, async move { Ok(context.ctx.replace_order(new_opts).await?) @@ -464,6 +596,22 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextSubmitOr if let Some(id) = client_request_id { new_opts = new_opts.client_request_id(id); } + let attached_params_obj = env.get_field( + &opts, + "attachedParams", + "Lcom/longbridge/trade/SubmitAttachedParams;", + )?; + if !attached_params_obj.l()?.is_null() { + let attached_params_java = env + .get_field( + &opts, + "attachedParams", + "Lcom/longbridge/trade/SubmitAttachedParams;", + )? + .l()?; + let ap = read_submit_attached_params(env, &attached_params_java)?; + new_opts = new_opts.attached_params(ap); + } async_util::execute(env, callback, async move { Ok(context.ctx.submit_order(new_opts).await?) @@ -779,3 +927,24 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextUsRealiz Ok(()) }) } + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextOrderDetailAttached( + mut env: JNIEnv, + _class: JClass, + context: i64, + order_id: JString, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let order_id: String = FromJValue::from_jvalue(env, order_id.into())?; + async_util::execute(env, callback, async move { + Ok(context + .ctx + .order_detail(GetOrderDetailOptions::new(order_id).is_attached()) + .await?) + })?; + Ok(()) + }) +} diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index e5ce0e338a..a23ec1f176 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -585,6 +585,34 @@ impl_java_class!( ] ); +impl_java_class!( + "com/longbridge/trade/AttachedOrderDetail", + longbridge::trade::AttachedOrderDetail, + [ + order_id, + attached_type_display, + trigger_price, + quantity, + executed_qty, + status, + updated_at, + withdrawn, + gtd, + time_in_force, + counter_id, + trigger_status, + executed_amount, + tag, + submitted_at, + executed_price, + force_only_rth, + reviewed, + activate_order_type, + activate_rth, + submit_price + ] +); + impl_java_class!( "com/longbridge/trade/PushOrderChanged", longbridge::trade::PushOrderChanged, @@ -667,7 +695,9 @@ impl_java_class!( #[java(set_as_opt = crate::types::JavaInteger)] trigger_count, monitor_price, - remark + remark, + #[java(objarray)] + attached_orders ] ); @@ -944,7 +974,9 @@ impl_java_class!( platform_deducted_currency, #[java(objarray)] history, - charge_detail + charge_detail, + #[java(objarray)] + attached_orders ] ); diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index d242600d06..2f10152d62 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -444,6 +444,19 @@ impl_java_enum!( [Unknown, Broker, Third] ); +impl_java_enum!( + "com/longbridge/trade/AttachedOrderType", + longbridge::trade::AttachedOrderType, + [ + #[java(remote = ProfitTaker)] + PROFIT_TAKER, + #[java(remote = StopLoss)] + STOP_LOSS, + #[java(remote = Bracket)] + BRACKET, + ] +); + impl_java_enum!( "com/longbridge/alert/AlertCondition", longbridge::alert::types::AlertCondition, diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index 328373f615..f38419fbf3 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -74,6 +74,54 @@ export declare class AssetContext { statementDownloadUrl(req: GetStatementDownloadUrlRequest): Promise } +/** Attached order detail */ +export declare class AttachedOrderDetail { + toString(): string + toJSON(): any + /** Order ID */ + get orderId(): string + /** Attached type display */ + get attachedTypeDisplay(): number + /** Trigger price */ + get triggerPrice(): Decimal | null + /** Submitted quantity */ + get quantity(): Decimal + /** Executed quantity */ + get executedQty(): Decimal + /** Order status */ + get status(): OrderStatus + /** Last updated time */ + get updatedAt(): Date + /** Whether withdrawn */ + get withdrawn(): boolean + /** Good till date */ + get gtd(): NaiveDate | null + /** Time in force type */ + get timeInForce(): TimeInForceType + /** Counter ID */ + get counterId(): string + /** Trigger status */ + get triggerStatus(): number + /** Executed amount */ + get executedAmount(): Decimal + /** Tag */ + get tag(): number + /** Submitted time */ + get submittedAt(): Date + /** Executed price */ + get executedPrice(): Decimal + /** Force only RTH */ + get forceOnlyRth(): OutsideRTH | null + /** Reviewed */ + get reviewed(): boolean + /** Activate order type */ + get activateOrderType(): OrderType + /** Activate RTH */ + get activateRth(): OutsideRTH | null + /** Submit price */ + get submitPrice(): Decimal | null +} + /** Brokers */ export declare class Brokers { toString(): string @@ -1041,6 +1089,8 @@ export declare class Order { get monitorPrice(): Decimal | null /** Remark */ get remark(): string + /** Attached orders */ + get attachedOrders(): Array } /** Order charge detail */ @@ -1165,6 +1215,8 @@ export declare class OrderDetail { get history(): Array /** Order charges */ get chargeDetail(): OrderChargeDetail + /** Attached orders */ + get attachedOrders(): Array } /** Order history detail */ @@ -2812,6 +2864,7 @@ export declare class TradeContext { * ``` */ orderDetail(orderId: string): Promise + orderDetailAttached(orderId: string): Promise /** * Estimating the maximum purchase quantity for Hong Kong and US stocks, * warrants, and options @@ -3196,6 +3249,16 @@ export declare const enum AssetType { Crypto = 3 } +/** Attached order type */ +export declare const enum AttachedOrderType { + /** Profit taker */ + ProfitTaker = 0, + /** Stop loss */ + StopLoss = 1, + /** Bracket */ + Bracket = 2 +} + export declare const enum BalanceType { /** Unknown */ Unknown = 0, @@ -4274,6 +4337,8 @@ export interface GetTodayOrdersOptions { market?: Market /** Order id */ orderId?: string + /** Filter attached orders only */ + isAttached?: boolean } /** Data granularity */ @@ -5293,6 +5358,40 @@ export interface RecentBuybacks { netBuybackYieldTtm: string } +/** Parameters for replacing an attached order */ +export interface ReplaceAttachedParams { + /** Attached order type */ + attachedOrderType: AttachedOrderType + /** Profit taker price */ + profitTakerPrice?: Decimal + /** Stop loss price */ + stopLossPrice?: Decimal + /** Time in force type */ + timeInForce?: TimeInForceType + /** Expire time (unix timestamp) */ + expireTime?: number + /** Activate order type */ + activateOrderType?: OrderType + /** Profit taker submit price */ + profitTakerSubmitPrice?: Decimal + /** Stop loss submit price */ + stopLossSubmitPrice?: Decimal + /** Activate RTH */ + activateRth?: OutsideRTH + /** Profit taker order ID */ + profitTakerId?: number + /** Stop loss order ID */ + stopLossId?: number + /** Cancel all attached orders */ + cancelAllAttached?: boolean + /** Main order ID */ + mainId?: number + /** Quantity */ + quantity?: Decimal + /** Market price */ + marketPrice?: Decimal +} + /** Options for replace order request */ export interface ReplaceOrderOptions { /** Order id */ @@ -5317,6 +5416,8 @@ export interface ReplaceOrderOptions { monitorPrice?: Decimal /** Remark (Maximum 64 characters) */ remark?: string + /** Attached order parameters */ + attachedParams?: ReplaceAttachedParams } /** A filter condition for screener_search Mode B. */ @@ -5669,6 +5770,28 @@ export interface StockRatings { ratingsJson: string } +/** Parameters for submitting an attached order */ +export interface SubmitAttachedParams { + /** Attached order type */ + attachedOrderType: AttachedOrderType + /** Profit taker price */ + profitTakerPrice?: Decimal + /** Stop loss price */ + stopLossPrice?: Decimal + /** Time in force type */ + timeInForce?: TimeInForceType + /** Expire time (unix timestamp) */ + expireTime?: number + /** Activate order type */ + activateOrderType?: OrderType + /** Profit taker submit price */ + profitTakerSubmitPrice?: Decimal + /** Stop loss submit price */ + stopLossSubmitPrice?: Decimal + /** Activate RTH */ + activateRth?: OutsideRTH +} + /** Options for submit order request */ export interface SubmitOrderOptions { /** Security code */ @@ -5712,6 +5835,8 @@ export interface SubmitOrderOptions { * The server caches this ID for 10 minutes. */ clientRequestId?: string + /** Attached order parameters */ + attachedParams?: SubmitAttachedParams } /** Quote type of subscription */ diff --git a/nodejs/index.js b/nodejs/index.js index 5e5192555e..fe5afa2ab6 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -397,6 +397,7 @@ module.exports.AccountBalance = nativeBinding.AccountBalance module.exports.AlertContext = nativeBinding.AlertContext module.exports.AllExecutionsResponse = nativeBinding.AllExecutionsResponse module.exports.AssetContext = nativeBinding.AssetContext +module.exports.AttachedOrderDetail = nativeBinding.AttachedOrderDetail module.exports.Brokers = nativeBinding.Brokers module.exports.CalendarContext = nativeBinding.CalendarContext module.exports.Candlestick = nativeBinding.Candlestick @@ -487,6 +488,7 @@ module.exports.AhPremiumPeriod = nativeBinding.AhPremiumPeriod module.exports.AlertCondition = nativeBinding.AlertCondition module.exports.AlertFrequency = nativeBinding.AlertFrequency module.exports.AssetType = nativeBinding.AssetType +module.exports.AttachedOrderType = nativeBinding.AttachedOrderType module.exports.BalanceType = nativeBinding.BalanceType module.exports.BrokerHoldingPeriod = nativeBinding.BrokerHoldingPeriod module.exports.CalcIndex = nativeBinding.CalcIndex diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs index e2bc7d1651..fa73594f7e 100644 --- a/nodejs/src/trade/context.rs +++ b/nodejs/src/trade/context.rs @@ -603,6 +603,15 @@ impl TradeContext { }) } + #[napi] + pub async fn order_detail_attached(&self, order_id: String) -> Result { + self.ctx + .order_detail(longbridge::trade::GetOrderDetailOptions::new(order_id).is_attached()) + .await + .map_err(ErrorNewType)? + .try_into() + } + /// Estimating the maximum purchase quantity for Hong Kong and US stocks, /// warrants, and options /// diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs index 77f1cad27e..b5d5e2b9be 100644 --- a/nodejs/src/trade/requests/get_today_orders.rs +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -16,6 +16,8 @@ pub struct GetTodayOrdersOptions { pub market: Option, /// Order id pub order_id: Option, + /// Filter attached orders only + pub is_attached: Option, } impl From for longbridge::trade::GetTodayOrdersOptions { @@ -37,6 +39,9 @@ impl From for longbridge::trade::GetTodayOrdersOptions { if let Some(order_id) = opts.order_id { opts2 = opts2.order_id(order_id); } + if opts.is_attached == Some(true) { + opts2 = opts2.is_attached(); + } opts2 } } diff --git a/nodejs/src/trade/requests/replace_order.rs b/nodejs/src/trade/requests/replace_order.rs index 273dbe6d81..4163c06ba2 100644 --- a/nodejs/src/trade/requests/replace_order.rs +++ b/nodejs/src/trade/requests/replace_order.rs @@ -1,6 +1,93 @@ use napi::bindgen_prelude::ClassInstance; -use crate::decimal::Decimal; +use crate::{ + decimal::Decimal, + trade::types::{AttachedOrderType, OrderType, OutsideRTH, TimeInForceType}, +}; + +/// Parameters for replacing an attached order +#[napi_derive::napi(object)] +pub struct ReplaceAttachedParams<'env> { + /// Attached order type + pub attached_order_type: AttachedOrderType, + /// Profit taker price + pub profit_taker_price: Option>, + /// Stop loss price + pub stop_loss_price: Option>, + /// Time in force type + pub time_in_force: Option, + /// Expire time (unix timestamp) + pub expire_time: Option, + /// Activate order type + pub activate_order_type: Option, + /// Profit taker submit price + pub profit_taker_submit_price: Option>, + /// Stop loss submit price + pub stop_loss_submit_price: Option>, + /// Activate RTH + pub activate_rth: Option, + /// Profit taker order ID + pub profit_taker_id: Option, + /// Stop loss order ID + pub stop_loss_id: Option, + /// Cancel all attached orders + pub cancel_all_attached: Option, + /// Main order ID + pub main_id: Option, + /// Quantity + pub quantity: Option>, + /// Market price + pub market_price: Option>, +} + +impl<'env> From> for longbridge::trade::ReplaceAttachedParams { + fn from(p: ReplaceAttachedParams<'env>) -> Self { + let mut opts = longbridge::trade::ReplaceAttachedParams::new(p.attached_order_type.into()); + if let Some(v) = p.profit_taker_price { + opts = opts.profit_taker_price(v.0); + } + if let Some(v) = p.stop_loss_price { + opts = opts.stop_loss_price(v.0); + } + if let Some(v) = p.time_in_force { + opts = opts.time_in_force(v.into()); + } + if let Some(v) = p.expire_time { + opts = opts.expire_time(v); + } + if let Some(v) = p.activate_order_type { + opts = opts.activate_order_type(v.into()); + } + if let Some(v) = p.profit_taker_submit_price { + opts = opts.profit_taker_submit_price(v.0); + } + if let Some(v) = p.stop_loss_submit_price { + opts = opts.stop_loss_submit_price(v.0); + } + if let Some(v) = p.activate_rth { + opts = opts.activate_rth(v.into()); + } + if let Some(v) = p.profit_taker_id { + opts = opts.profit_taker_id(v); + } + if let Some(v) = p.stop_loss_id { + opts = opts.stop_loss_id(v); + } + if p.cancel_all_attached == Some(true) { + opts = opts.cancel_all_attached(); + } + if let Some(v) = p.main_id { + opts = opts.main_id(v); + } + if let Some(v) = p.quantity { + opts = opts.quantity(v.0); + } + if let Some(v) = p.market_price { + opts = opts.market_price(v.0); + } + opts + } +} /// Options for replace order request #[napi_derive::napi(object)] @@ -27,6 +114,8 @@ pub struct ReplaceOrderOptions<'env> { pub monitor_price: Option>, /// Remark (Maximum 64 characters) pub remark: Option, + /// Attached order parameters + pub attached_params: Option>, } impl<'env> From> for longbridge::trade::ReplaceOrderOptions { @@ -60,6 +149,9 @@ impl<'env> From> for longbridge::trade::ReplaceOrderOp if let Some(remark) = opts.remark { opts2 = opts2.remark(remark); } + if let Some(p) = opts.attached_params { + opts2 = opts2.attached_params(p.into()); + } opts2 } } diff --git a/nodejs/src/trade/requests/submit_order.rs b/nodejs/src/trade/requests/submit_order.rs index bc376aa800..bddab1092b 100644 --- a/nodejs/src/trade/requests/submit_order.rs +++ b/nodejs/src/trade/requests/submit_order.rs @@ -3,9 +3,63 @@ use napi::bindgen_prelude::ClassInstance; use crate::{ decimal::Decimal, time::NaiveDate, - trade::types::{OrderSide, OrderType, OutsideRTH, TimeInForceType}, + trade::types::{AttachedOrderType, OrderSide, OrderType, OutsideRTH, TimeInForceType}, }; +/// Parameters for submitting an attached order +#[napi_derive::napi(object)] +pub struct SubmitAttachedParams<'env> { + /// Attached order type + pub attached_order_type: AttachedOrderType, + /// Profit taker price + pub profit_taker_price: Option>, + /// Stop loss price + pub stop_loss_price: Option>, + /// Time in force type + pub time_in_force: Option, + /// Expire time (unix timestamp) + pub expire_time: Option, + /// Activate order type + pub activate_order_type: Option, + /// Profit taker submit price + pub profit_taker_submit_price: Option>, + /// Stop loss submit price + pub stop_loss_submit_price: Option>, + /// Activate RTH + pub activate_rth: Option, +} + +impl<'env> From> for longbridge::trade::SubmitAttachedParams { + fn from(p: SubmitAttachedParams<'env>) -> Self { + let mut opts = longbridge::trade::SubmitAttachedParams::new(p.attached_order_type.into()); + if let Some(v) = p.profit_taker_price { + opts = opts.profit_taker_price(v.0); + } + if let Some(v) = p.stop_loss_price { + opts = opts.stop_loss_price(v.0); + } + if let Some(v) = p.time_in_force { + opts = opts.time_in_force(v.into()); + } + if let Some(v) = p.expire_time { + opts = opts.expire_time(v); + } + if let Some(v) = p.activate_order_type { + opts = opts.activate_order_type(v.into()); + } + if let Some(v) = p.profit_taker_submit_price { + opts = opts.profit_taker_submit_price(v.0); + } + if let Some(v) = p.stop_loss_submit_price { + opts = opts.stop_loss_submit_price(v.0); + } + if let Some(v) = p.activate_rth { + opts = opts.activate_rth(v.into()); + } + opts + } +} + /// Options for submit order request #[napi_derive::napi(object)] pub struct SubmitOrderOptions<'env> { @@ -46,6 +100,8 @@ pub struct SubmitOrderOptions<'env> { /// If not specified, idempotency control is skipped. /// The server caches this ID for 10 minutes. pub client_request_id: Option, + /// Attached order parameters + pub attached_params: Option>, } impl<'env> From> for longbridge::trade::SubmitOrderOptions { @@ -94,6 +150,9 @@ impl<'env> From> for longbridge::trade::SubmitOrderOpti if let Some(id) = opts.client_request_id { opts2 = opts2.client_request_id(id); } + if let Some(p) = opts.attached_params { + opts2 = opts2.attached_params(p.into()); + } opts2 } } diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs index 202392b86b..e409becf7a 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -195,6 +195,75 @@ pub struct AllExecutionsResponse { trades: Vec, } +/// Attached order type +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq, Copy, Clone)] +#[js(remote = "longbridge::trade::AttachedOrderType")] +pub enum AttachedOrderType { + /// Profit taker + ProfitTaker, + /// Stop loss + StopLoss, + /// Bracket + Bracket, +} + +/// Attached order detail +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::AttachedOrderDetail")] +pub struct AttachedOrderDetail { + /// Order ID + order_id: String, + /// Attached type display + attached_type_display: i32, + /// Trigger price + #[js(opt)] + trigger_price: Option, + /// Submitted quantity + quantity: Decimal, + /// Executed quantity + executed_qty: Decimal, + /// Order status + status: OrderStatus, + /// Last updated time + #[js(datetime)] + updated_at: DateTime, + /// Whether withdrawn + withdrawn: bool, + /// Good till date + #[js(opt)] + gtd: Option, + /// Time in force type + time_in_force: TimeInForceType, + /// Counter ID + counter_id: String, + /// Trigger status + trigger_status: i32, + /// Executed amount + executed_amount: Decimal, + /// Tag + tag: i32, + /// Submitted time + #[js(datetime)] + submitted_at: DateTime, + /// Executed price + executed_price: Decimal, + /// Force only RTH + #[js(opt)] + force_only_rth: Option, + /// Reviewed + reviewed: bool, + /// Activate order type + activate_order_type: OrderType, + /// Activate RTH + #[js(opt)] + activate_rth: Option, + /// Submit price + #[js(opt)] + submit_price: Option, +} + /// Order #[napi_derive::napi] #[derive(Debug, JsObject)] @@ -274,6 +343,9 @@ pub struct Order { monitor_price: Option, /// Remark remark: String, + /// Attached orders + #[js(array)] + attached_orders: Vec, } /// Commission-free Status @@ -493,6 +565,9 @@ pub struct OrderDetail { history: Vec, /// Order charges charge_detail: OrderChargeDetail, + /// Attached orders + #[js(array)] + attached_orders: Vec, } /// Order changed message diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 6596ecdedb..474edaa9fa 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -5883,6 +5883,180 @@ class OutsideRTH: Overnight option """ +class AttachedOrderType: + """ + Attached order type + """ + + class ProfitTaker(AttachedOrderType): + """ + Take profit + """ + + class StopLoss(AttachedOrderType): + """ + Stop loss + """ + + class Bracket(AttachedOrderType): + """ + Bracket order + """ + +class AttachedOrderDetail: + """ + Attached order detail + """ + + order_id: str + """ + Order ID + """ + + attached_type_display: int + """ + Attached type display (1=take-profit, 2=stop-loss) + """ + + trigger_price: Optional[Decimal] + """ + Trigger price + """ + + quantity: Decimal + """ + Quantity + """ + + executed_qty: Decimal + """ + Executed quantity + """ + + status: Type[OrderStatus] + """ + Order status + """ + + updated_at: datetime + """ + Last updated time + """ + + withdrawn: bool + """ + Withdrawn + """ + + gtd: Optional[date] + """ + Good till date + """ + + time_in_force: Type[TimeInForceType] + """ + Time in force type + """ + + counter_id: str + """ + Counter ID + """ + + trigger_status: int + """ + Trigger status + """ + + executed_amount: Decimal + """ + Executed amount + """ + + tag: int + """ + Tag + """ + + submitted_at: datetime + """ + Submitted time + """ + + executed_price: Decimal + """ + Executed price + """ + + force_only_rth: Optional[Type[OutsideRTH]] + """ + Enable or disable outside regular trading hours (force only) + """ + + reviewed: bool + """ + Reviewed + """ + + activate_order_type: Type[OrderType] + """ + Activate order type + """ + + activate_rth: Optional[Type[OutsideRTH]] + """ + Activate RTH + """ + + submit_price: Optional[Decimal] + """ + Submit price + """ + +class SubmitAttachedParams: + """ + Submit attached order parameters + """ + + def __init__( + self, + attached_order_type: Type[AttachedOrderType], + *, + profit_taker_price: Optional[Decimal] = None, + stop_loss_price: Optional[Decimal] = None, + time_in_force: Optional[Type[TimeInForceType]] = None, + expire_time: Optional[int] = None, + activate_order_type: Optional[Type[OrderType]] = None, + profit_taker_submit_price: Optional[Decimal] = None, + stop_loss_submit_price: Optional[Decimal] = None, + activate_rth: Optional[Type[OutsideRTH]] = None, + ) -> None: ... + +class ReplaceAttachedParams: + """ + Replace attached order parameters + """ + + def __init__( + self, + attached_order_type: Type[AttachedOrderType], + *, + profit_taker_price: Optional[Decimal] = None, + stop_loss_price: Optional[Decimal] = None, + time_in_force: Optional[Type[TimeInForceType]] = None, + expire_time: Optional[int] = None, + activate_order_type: Optional[Type[OrderType]] = None, + profit_taker_submit_price: Optional[Decimal] = None, + stop_loss_submit_price: Optional[Decimal] = None, + activate_rth: Optional[Type[OutsideRTH]] = None, + profit_taker_id: Optional[int] = None, + stop_loss_id: Optional[int] = None, + cancel_all_attached: Optional[bool] = None, + main_id: Optional[int] = None, + quantity: Optional[Decimal] = None, + market_price: Optional[Decimal] = None, + ) -> None: ... + class Order: """ Order @@ -6033,6 +6207,11 @@ class Order: Remark """ + attached_orders: List[AttachedOrderDetail] + """ + Attached orders + """ + class CommissionFreeStatus: """ Commission-free Status @@ -6413,6 +6592,11 @@ class OrderDetail: Order charges """ + attached_orders: List[AttachedOrderDetail] + """ + Attached orders + """ + class SubmitOrderResponse: """ Response for submit order request @@ -6982,6 +7166,7 @@ class TradeContext: side: Optional[Type[OrderSide]] = None, market: Optional[Type[Market]] = None, order_id: Optional[str] = None, + is_attached: bool = False, ) -> List[Order]: """ Get today orders @@ -6992,6 +7177,7 @@ class TradeContext: side: Filter by order side market: Filter by market type order_id: Filter by order id + is_attached: Include attached orders Returns: Order list @@ -7029,6 +7215,7 @@ class TradeContext: trigger_count: Optional[int] = None, monitor_price: Optional[Decimal] = None, remark: Optional[str] = None, + attached_params: Optional[ReplaceAttachedParams] = None, ) -> None: """ Replace order @@ -7083,6 +7270,7 @@ class TradeContext: monitor_price: Optional[Decimal] = None, remark: Optional[str] = None, client_request_id: Optional[str] = None, + attached_params: Optional[SubmitAttachedParams] = None, ) -> SubmitOrderResponse: """ Submit order @@ -7105,6 +7293,7 @@ class TradeContext: monitor_price: Monitor price remark: Remark (Maximum 64 characters) client_request_id: Idempotent request ID. If not specified, idempotency control is skipped. The server caches this ID for 10 minutes to prevent duplicate orders. + attached_params: Attached order parameters Returns: Response @@ -7300,12 +7489,13 @@ class TradeContext: print(resp) """ - def order_detail(self, order_id: str) -> OrderDetail: + def order_detail(self, order_id: str, is_attached: bool = False) -> OrderDetail: """ Get order detail Args: order_id: Order id + is_attached: Include attached orders Returns: Order detail @@ -7700,6 +7890,7 @@ class AsyncTradeContext: side: Optional[Type[OrderSide]] = None, market: Optional[Type[Market]] = None, order_id: Optional[str] = None, + is_attached: bool = False, ) -> Awaitable[List[Order]]: """ Get today orders with optional filters. Returns an awaitable that resolves to order list. @@ -7710,6 +7901,7 @@ class AsyncTradeContext: side: Filter by order side. market: Filter by market type. order_id: Filter by order ID. + is_attached: Include attached orders. Examples: :: @@ -7754,6 +7946,7 @@ class AsyncTradeContext: trigger_count: Optional[int] = None, monitor_price: Optional[Decimal] = None, remark: Optional[str] = None, + attached_params: Optional[ReplaceAttachedParams] = None, ) -> Awaitable[None]: """ Replace order. Returns an awaitable. Same parameters as sync TradeContext.replace_order. @@ -7770,6 +7963,7 @@ class AsyncTradeContext: trigger_count: Trigger count. monitor_price: Monitor price. remark: Remark (max 64 characters). + attached_params: Attached order parameters. Examples: :: @@ -7813,6 +8007,7 @@ class AsyncTradeContext: monitor_price: Optional[Decimal] = None, remark: Optional[str] = None, client_request_id: Optional[str] = None, + attached_params: Optional[SubmitAttachedParams] = None, ) -> Awaitable[SubmitOrderResponse]: """ Submit order. Returns an awaitable that resolves to SubmitOrderResponse. Same parameters as sync TradeContext.submit_order. @@ -7835,6 +8030,7 @@ class AsyncTradeContext: monitor_price: Monitor price. remark: Remark (max 64 characters). client_request_id: Idempotent request ID. If not specified, idempotency control is skipped. The server caches this ID for 10 minutes to prevent duplicate orders. + attached_params: Attached order parameters. Examples: :: @@ -8048,12 +8244,13 @@ class AsyncTradeContext: """ ... - def order_detail(self, order_id: str) -> Awaitable[OrderDetail]: + def order_detail(self, order_id: str, is_attached: bool = False) -> Awaitable[OrderDetail]: """ Get order detail by order_id. Returns an awaitable that resolves to order detail. Args: order_id: Order ID. + is_attached: Include attached orders. Examples: :: diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs index 459ea9e7df..6c1aed94e1 100644 --- a/python/src/trade/context.rs +++ b/python/src/trade/context.rs @@ -5,8 +5,8 @@ use longbridge::{ trade::{ EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, - QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, }, }; use parking_lot::Mutex; @@ -23,7 +23,8 @@ use crate::{ AccountBalance, AllExecutionsResponse, BalanceType, CashFlow, EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, MarginRatio, Order, OrderDetail, OrderSide, OrderStatus, OrderType, OutsideRTH, - StockPositionsResponse, SubmitOrderResponse, TimeInForceType, TopicType, + ReplaceAttachedParams, StockPositionsResponse, SubmitAttachedParams, + SubmitOrderResponse, TimeInForceType, TopicType, }, }, types::Market, @@ -205,7 +206,7 @@ impl TradeContext { } /// Get today orders - #[pyo3(signature = (symbol = None, status = None, side = None, market = None, order_id = None))] + #[pyo3(signature = (symbol = None, status = None, side = None, market = None, order_id = None, is_attached = false))] fn today_orders( &self, symbol: Option, @@ -213,6 +214,7 @@ impl TradeContext { side: Option, market: Option, order_id: Option, + is_attached: bool, ) -> PyResult> { let mut opts = GetTodayOrdersOptions::new(); @@ -229,6 +231,9 @@ impl TradeContext { if let Some(order_id) = order_id { opts = opts.order_id(order_id); } + if is_attached { + opts = opts.is_attached(); + } self.ctx .today_orders(Some(opts)) @@ -239,7 +244,7 @@ impl TradeContext { } /// Replace order - #[pyo3(signature = (order_id, quantity, price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None))] + #[pyo3(signature = (order_id, quantity, price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None, attached_params = None))] #[allow(clippy::too_many_arguments)] fn replace_order( &self, @@ -254,6 +259,7 @@ impl TradeContext { trigger_count: Option, monitor_price: Option, remark: Option, + attached_params: Option, ) -> PyResult<()> { let mut opts = ReplaceOrderOptions::new(order_id, quantity.into()); @@ -284,13 +290,16 @@ impl TradeContext { if let Some(remark) = remark { opts = opts.remark(remark); } + if let Some(p) = attached_params { + opts = opts.attached_params(p.0); + } self.ctx.replace_order(opts).map_err(ErrorNewType)?; Ok(()) } /// Submit order - #[pyo3(signature = (symbol, order_type, side, submitted_quantity, time_in_force, submitted_price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, expire_date = None, outside_rth = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None, client_request_id = None))] + #[pyo3(signature = (symbol, order_type, side, submitted_quantity, time_in_force, submitted_price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, expire_date = None, outside_rth = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None, client_request_id = None, attached_params = None))] #[allow(clippy::too_many_arguments)] fn submit_order( &self, @@ -311,6 +320,7 @@ impl TradeContext { monitor_price: Option, remark: Option, client_request_id: Option, + attached_params: Option, ) -> PyResult { let mut opts = SubmitOrderOptions::new( symbol, @@ -356,6 +366,9 @@ impl TradeContext { if let Some(id) = client_request_id { opts = opts.client_request_id(id); } + if let Some(p) = attached_params { + opts = opts.attached_params(p.0); + } self.ctx .submit_order(opts) @@ -441,9 +454,14 @@ impl TradeContext { } /// Get order detail - fn order_detail(&self, order_id: String) -> PyResult { + #[pyo3(signature = (order_id, is_attached = false))] + fn order_detail(&self, order_id: String, is_attached: bool) -> PyResult { + let mut opts = GetOrderDetailOptions::new(order_id); + if is_attached { + opts = opts.is_attached(); + } self.ctx - .order_detail(order_id) + .order_detail(opts) .map_err(ErrorNewType)? .try_into() } diff --git a/python/src/trade/context_async.rs b/python/src/trade/context_async.rs index 02cbb3bdb1..c2c6a1265f 100644 --- a/python/src/trade/context_async.rs +++ b/python/src/trade/context_async.rs @@ -5,8 +5,9 @@ use std::sync::Arc; use longbridge::trade::{ EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, - QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, TradeContext, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, + TradeContext, }; use parking_lot::Mutex; use pyo3::{prelude::*, types::PyType}; @@ -23,7 +24,8 @@ use crate::{ AccountBalance, AllExecutionsResponse, BalanceType, CashFlow, EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, MarginRatio, Order, OrderDetail, OrderSide, OrderStatus, OrderType, OutsideRTH, - StockPositionsResponse, SubmitOrderResponse, TimeInForceType, TopicType, + ReplaceAttachedParams, StockPositionsResponse, SubmitAttachedParams, + SubmitOrderResponse, TimeInForceType, TopicType, }, }, types::Market, @@ -241,7 +243,8 @@ impl AsyncTradeContext { } /// Get today orders. Returns awaitable. - #[pyo3(signature = (symbol = None, status = None, side = None, market = None, order_id = None))] + #[allow(clippy::too_many_arguments)] + #[pyo3(signature = (symbol = None, status = None, side = None, market = None, order_id = None, is_attached = false))] fn today_orders( &self, py: Python<'_>, @@ -250,6 +253,7 @@ impl AsyncTradeContext { side: Option, market: Option, order_id: Option, + is_attached: bool, ) -> PyResult> { let ctx = self.ctx.clone(); let mut opts = GetTodayOrdersOptions::new(); @@ -266,6 +270,9 @@ impl AsyncTradeContext { if let Some(o) = order_id { opts = opts.order_id(o); } + if is_attached { + opts = opts.is_attached(); + } pyo3_async_runtimes::tokio::future_into_py(py, async move { let v = ctx.today_orders(Some(opts)).await.map_err(ErrorNewType)?; v.into_iter() @@ -276,7 +283,7 @@ impl AsyncTradeContext { } /// Replace order. Returns awaitable. - #[pyo3(signature = (order_id, quantity, price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None))] + #[pyo3(signature = (order_id, quantity, price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None, attached_params = None))] #[allow(clippy::too_many_arguments)] fn replace_order( &self, @@ -292,6 +299,7 @@ impl AsyncTradeContext { trigger_count: Option, monitor_price: Option, remark: Option, + attached_params: Option, ) -> PyResult> { let ctx = self.ctx.clone(); let mut opts = ReplaceOrderOptions::new(order_id, quantity.into()); @@ -322,6 +330,9 @@ impl AsyncTradeContext { if let Some(r) = remark { opts = opts.remark(r); } + if let Some(p) = attached_params { + opts = opts.attached_params(p.0); + } pyo3_async_runtimes::tokio::future_into_py(py, async move { ctx.replace_order(opts).await.map_err(ErrorNewType)?; Ok(()) @@ -330,7 +341,7 @@ impl AsyncTradeContext { } /// Submit order. Returns awaitable. - #[pyo3(signature = (symbol, order_type, side, submitted_quantity, time_in_force, submitted_price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, expire_date = None, outside_rth = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None, client_request_id = None))] + #[pyo3(signature = (symbol, order_type, side, submitted_quantity, time_in_force, submitted_price = None, trigger_price = None, limit_offset = None, trailing_amount = None, trailing_percent = None, expire_date = None, outside_rth = None, limit_depth_level = None, trigger_count = None, monitor_price = None, remark = None, client_request_id = None, attached_params = None))] #[allow(clippy::too_many_arguments)] fn submit_order( &self, @@ -352,6 +363,7 @@ impl AsyncTradeContext { monitor_price: Option, remark: Option, client_request_id: Option, + attached_params: Option, ) -> PyResult> { let ctx = self.ctx.clone(); let mut opts = SubmitOrderOptions::new( @@ -397,6 +409,9 @@ impl AsyncTradeContext { if let Some(id) = client_request_id { opts = opts.client_request_id(id); } + if let Some(p) = attached_params { + opts = opts.attached_params(p.0); + } pyo3_async_runtimes::tokio::future_into_py(py, async move { let r: SubmitOrderResponse = ctx .submit_order(opts) @@ -517,11 +532,21 @@ impl AsyncTradeContext { } /// Get order detail. Returns awaitable. - fn order_detail(&self, py: Python<'_>, order_id: String) -> PyResult> { + #[pyo3(signature = (order_id, is_attached = false))] + fn order_detail( + &self, + py: Python<'_>, + order_id: String, + is_attached: bool, + ) -> PyResult> { let ctx = self.ctx.clone(); + let mut opts = GetOrderDetailOptions::new(order_id); + if is_attached { + opts = opts.is_attached(); + } pyo3_async_runtimes::tokio::future_into_py(py, async move { let r: OrderDetail = ctx - .order_detail(order_id) + .order_detail(opts) .await .map_err(ErrorNewType)? .try_into()?; diff --git a/python/src/trade/mod.rs b/python/src/trade/mod.rs index 3a2748ec3a..1d08718792 100644 --- a/python/src/trade/mod.rs +++ b/python/src/trade/mod.rs @@ -16,6 +16,10 @@ pub(crate) fn register_types(parent: &Bound) -> PyResult<()> { parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index eeae5b750b..e545710123 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -1,5 +1,5 @@ use longbridge_python_macros::{PyEnum, PyObject}; -use pyo3::pyclass; +use pyo3::{pyclass, pymethods}; use crate::{ decimal::PyDecimal, @@ -199,6 +199,197 @@ pub(crate) enum OutsideRTH { OptionPreMarket, } +/// Attached order type +#[pyclass(eq, eq_int, from_py_object)] +#[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] +#[py(remote = "longbridge::trade::AttachedOrderType")] +pub(crate) enum AttachedOrderType { + /// Take profit + ProfitTaker, + /// Stop loss + StopLoss, + /// Bracket order + Bracket, +} + +/// Attached order detail +#[pyclass(skip_from_py_object)] +#[derive(Debug, PyObject, Clone)] +#[py(remote = "longbridge::trade::AttachedOrderDetail")] +pub(crate) struct AttachedOrderDetail { + /// Order ID + order_id: String, + /// Attached type display (1=take-profit, 2=stop-loss) + attached_type_display: i32, + /// Trigger price + #[py(opt)] + trigger_price: Option, + /// Quantity + quantity: PyDecimal, + /// Executed quantity + executed_qty: PyDecimal, + /// Order status + status: OrderStatus, + /// Last updated time + updated_at: PyOffsetDateTimeWrapper, + /// Withdrawn + withdrawn: bool, + /// Good till date + #[py(opt)] + gtd: Option, + /// Time in force type + time_in_force: TimeInForceType, + /// Counter ID + counter_id: String, + /// Trigger status + trigger_status: i32, + /// Executed amount + executed_amount: PyDecimal, + /// Tag + tag: i32, + /// Submitted time + submitted_at: PyOffsetDateTimeWrapper, + /// Executed price + executed_price: PyDecimal, + /// Enable or disable outside regular trading hours (force only) + #[py(opt)] + force_only_rth: Option, + /// Reviewed + reviewed: bool, + /// Activate order type + activate_order_type: OrderType, + /// Activate RTH + #[py(opt)] + activate_rth: Option, + /// Submit price + #[py(opt)] + submit_price: Option, +} + +/// Submit attached order parameters +#[pyclass(from_py_object)] +#[derive(Clone)] +pub(crate) struct SubmitAttachedParams(pub(crate) longbridge::trade::SubmitAttachedParams); + +#[pymethods] +impl SubmitAttachedParams { + #[new] + #[allow(clippy::too_many_arguments)] + #[pyo3(signature = (attached_order_type, *, profit_taker_price=None, stop_loss_price=None, time_in_force=None, expire_time=None, activate_order_type=None, profit_taker_submit_price=None, stop_loss_submit_price=None, activate_rth=None))] + fn new( + attached_order_type: AttachedOrderType, + profit_taker_price: Option, + stop_loss_price: Option, + time_in_force: Option, + expire_time: Option, + activate_order_type: Option, + profit_taker_submit_price: Option, + stop_loss_submit_price: Option, + activate_rth: Option, + ) -> Self { + let mut p = longbridge::trade::SubmitAttachedParams::new(attached_order_type.into()); + if let Some(v) = profit_taker_price { + p = p.profit_taker_price(v.into()); + } + if let Some(v) = stop_loss_price { + p = p.stop_loss_price(v.into()); + } + if let Some(v) = time_in_force { + p = p.time_in_force(v.into()); + } + if let Some(v) = expire_time { + p = p.expire_time(v); + } + if let Some(v) = activate_order_type { + p = p.activate_order_type(v.into()); + } + if let Some(v) = profit_taker_submit_price { + p = p.profit_taker_submit_price(v.into()); + } + if let Some(v) = stop_loss_submit_price { + p = p.stop_loss_submit_price(v.into()); + } + if let Some(v) = activate_rth { + p = p.activate_rth(v.into()); + } + Self(p) + } +} + +/// Replace attached order parameters +#[pyclass(from_py_object)] +#[derive(Clone)] +pub(crate) struct ReplaceAttachedParams(pub(crate) longbridge::trade::ReplaceAttachedParams); + +#[pymethods] +impl ReplaceAttachedParams { + #[new] + #[pyo3(signature = (attached_order_type, *, profit_taker_price=None, stop_loss_price=None, time_in_force=None, expire_time=None, activate_order_type=None, profit_taker_submit_price=None, stop_loss_submit_price=None, activate_rth=None, profit_taker_id=None, stop_loss_id=None, cancel_all_attached=None, main_id=None, quantity=None, market_price=None))] + #[allow(clippy::too_many_arguments)] + fn new( + attached_order_type: AttachedOrderType, + profit_taker_price: Option, + stop_loss_price: Option, + time_in_force: Option, + expire_time: Option, + activate_order_type: Option, + profit_taker_submit_price: Option, + stop_loss_submit_price: Option, + activate_rth: Option, + profit_taker_id: Option, + stop_loss_id: Option, + cancel_all_attached: Option, + main_id: Option, + quantity: Option, + market_price: Option, + ) -> Self { + let mut p = longbridge::trade::ReplaceAttachedParams::new(attached_order_type.into()); + if let Some(v) = profit_taker_price { + p = p.profit_taker_price(v.into()); + } + if let Some(v) = stop_loss_price { + p = p.stop_loss_price(v.into()); + } + if let Some(v) = time_in_force { + p = p.time_in_force(v.into()); + } + if let Some(v) = expire_time { + p = p.expire_time(v); + } + if let Some(v) = activate_order_type { + p = p.activate_order_type(v.into()); + } + if let Some(v) = profit_taker_submit_price { + p = p.profit_taker_submit_price(v.into()); + } + if let Some(v) = stop_loss_submit_price { + p = p.stop_loss_submit_price(v.into()); + } + if let Some(v) = activate_rth { + p = p.activate_rth(v.into()); + } + if let Some(v) = profit_taker_id { + p = p.profit_taker_id(v); + } + if let Some(v) = stop_loss_id { + p = p.stop_loss_id(v); + } + if cancel_all_attached == Some(true) { + p = p.cancel_all_attached(); + } + if let Some(v) = main_id { + p = p.main_id(v); + } + if let Some(v) = quantity { + p = p.quantity(v.into()); + } + if let Some(v) = market_price { + p = p.market_price(v.into()); + } + Self(p) + } +} + /// Order #[pyclass(skip_from_py_object)] #[derive(Debug, PyObject)] @@ -277,6 +468,9 @@ pub(crate) struct Order { monitor_price: Option, /// Remark remark: String, + /// Attached orders + #[py(array)] + attached_orders: Vec, } /// Commission-free Status @@ -496,6 +690,9 @@ pub(crate) struct OrderDetail { history: Vec, /// Order charges charge_detail: OrderChargeDetail, + /// Attached orders + #[py(array)] + attached_orders: Vec, } /// Order changed message diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs index 4335af62cd..4aaada2b40 100644 --- a/rust/src/blocking/trade.rs +++ b/rust/src/blocking/trade.rs @@ -7,12 +7,12 @@ use crate::{ AccountBalance, AllExecutionsResponse, CashFlow, EstimateMaxPurchaseQuantityOptions, EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, GetUSHistoryOrders, - GetUSRealizedPLOptions, MarginRatio, Order, OrderDetail, PushEvent, QueryUSOrdersOptions, - QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse, SubmitOrderOptions, - SubmitOrderResponse, TopicType, TradeContext, USAssetOverview, USOrderDetailResponse, - USRealizedPL, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, + GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, OrderDetail, PushEvent, + QueryUSOrdersOptions, QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse, + SubmitOrderOptions, SubmitOrderResponse, TopicType, TradeContext, USAssetOverview, + USOrderDetailResponse, USRealizedPL, }, }; @@ -453,10 +453,10 @@ impl TradeContextSync { /// ``` pub fn order_detail( &self, - order_id: impl Into + Send + 'static, + options: impl Into + Send + 'static, ) -> Result { self.rt - .call(move |ctx| async move { ctx.order_detail(order_id).await }) + .call(move |ctx| async move { ctx.order_detail(options).await }) } /// Estimating the maximum purchase quantity for Hong Kong and US stocks, diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs index 4285a51674..3ff2ab6cce 100644 --- a/rust/src/trade/context.rs +++ b/rust/src/trade/context.rs @@ -13,11 +13,11 @@ use crate::{ AccountBalance, AllExecutionsResponse, CashFlow, EstimateMaxPurchaseQuantityOptions, Execution, FundPositionsResponse, GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, - GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, OrderDetail, OrderSide, - PushEvent, QueryUSOrdersOptions, QueryUSOrdersResponse, ReplaceOrderOptions, - StockPositionsResponse, SubmitOrderOptions, TopicType, USAssetOverview, - USOrderDetailResponse, USRealizedPL, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, + OrderDetail, OrderSide, PushEvent, QueryUSOrdersOptions, QueryUSOrdersResponse, + ReplaceOrderOptions, StockPositionsResponse, SubmitOrderOptions, TopicType, + USAssetOverview, USOrderDetailResponse, USRealizedPL, core::{Command, Core}, }, }; @@ -785,20 +785,16 @@ impl TradeContext { /// # Ok::<_, Box>(()) /// # }); /// ``` - pub async fn order_detail(&self, order_id: impl Into) -> Result { - #[derive(Debug, Serialize)] - struct Request { - order_id: String, - } - + pub async fn order_detail( + &self, + options: impl Into, + ) -> Result { Ok(self .0 .http_cli .request(Method::GET, "/v1/trade/order") .response::>() - .query_params(Request { - order_id: order_id.into(), - }) + .query_params(options.into()) .send() .with_subscriber(self.0.log_subscriber.clone()) .await? diff --git a/rust/src/trade/mod.rs b/rust/src/trade/mod.rs index 26b6e42c61..3000965e43 100644 --- a/rust/src/trade/mod.rs +++ b/rust/src/trade/mod.rs @@ -12,12 +12,15 @@ pub use push_types::{PushEvent, PushOrderChanged, TopicType}; pub use requests::{ EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, - ReplaceOrderOptions, SubmitOrderOptions, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, ReplaceAttachedParams, ReplaceOrderOptions, SubmitAttachedParams, + SubmitOrderOptions, }; pub use types::{ AccountBalance, AllExecutionsResponse, + AttachedOrderDetail, + AttachedOrderType, BalanceType, CashFlow, CashFlowDirection, diff --git a/rust/src/trade/requests/get_order_detail.rs b/rust/src/trade/requests/get_order_detail.rs new file mode 100644 index 0000000000..23c472df0d --- /dev/null +++ b/rust/src/trade/requests/get_order_detail.rs @@ -0,0 +1,38 @@ +use serde::Serialize; + +/// Options for get order detail request +#[derive(Debug, Serialize, Clone)] +pub struct GetOrderDetailOptions { + order_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + is_attached: Option, +} + +impl GetOrderDetailOptions { + /// Create new options with order ID + pub fn new(order_id: impl Into) -> Self { + Self { + order_id: order_id.into(), + is_attached: None, + } + } + /// Query by attached order + pub fn is_attached(self) -> Self { + Self { + is_attached: Some(true), + ..self + } + } +} + +impl From for GetOrderDetailOptions { + fn from(order_id: String) -> Self { + Self::new(order_id) + } +} + +impl<'a> From<&'a str> for GetOrderDetailOptions { + fn from(order_id: &'a str) -> Self { + Self::new(order_id) + } +} diff --git a/rust/src/trade/requests/get_today_orders.rs b/rust/src/trade/requests/get_today_orders.rs index 832d24b149..5b67cca399 100644 --- a/rust/src/trade/requests/get_today_orders.rs +++ b/rust/src/trade/requests/get_today_orders.rs @@ -18,6 +18,8 @@ pub struct GetTodayOrdersOptions { market: Option, #[serde(skip_serializing_if = "Option::is_none")] order_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + is_attached: Option, } impl GetTodayOrdersOptions { @@ -76,4 +78,12 @@ impl GetTodayOrdersOptions { ..self } } + + /// Filter by attached order + pub fn is_attached(self) -> Self { + Self { + is_attached: Some(true), + ..self + } + } } diff --git a/rust/src/trade/requests/mod.rs b/rust/src/trade/requests/mod.rs index 532a9b324d..b4d9613355 100644 --- a/rust/src/trade/requests/mod.rs +++ b/rust/src/trade/requests/mod.rs @@ -4,6 +4,7 @@ mod get_cash_flow; mod get_fund_positions; mod get_history_executions; mod get_history_orders; +mod get_order_detail; mod get_stock_positions; mod get_today_executions; mod get_today_orders; @@ -16,8 +17,9 @@ pub use get_cash_flow::GetCashFlowOptions; pub use get_fund_positions::GetFundPositionsOptions; pub use get_history_executions::GetHistoryExecutionsOptions; pub use get_history_orders::GetHistoryOrdersOptions; +pub use get_order_detail::GetOrderDetailOptions; pub use get_stock_positions::GetStockPositionsOptions; pub use get_today_executions::GetTodayExecutionsOptions; pub use get_today_orders::GetTodayOrdersOptions; -pub use replace_order::ReplaceOrderOptions; -pub use submit_order::SubmitOrderOptions; +pub use replace_order::{ReplaceAttachedParams, ReplaceOrderOptions}; +pub use submit_order::{SubmitAttachedParams, SubmitOrderOptions}; diff --git a/rust/src/trade/requests/replace_order.rs b/rust/src/trade/requests/replace_order.rs index 26151a5add..f6a72eb051 100644 --- a/rust/src/trade/requests/replace_order.rs +++ b/rust/src/trade/requests/replace_order.rs @@ -1,6 +1,8 @@ use rust_decimal::Decimal; use serde::Serialize; +use crate::trade::{AttachedOrderType, OrderType, OutsideRTH, TimeInForceType}; + /// Options for replace order request #[derive(Debug, Serialize, Clone)] pub struct ReplaceOrderOptions { @@ -24,6 +26,8 @@ pub struct ReplaceOrderOptions { monitor_price: Option, #[serde(skip_serializing_if = "Option::is_none")] remark: Option, + #[serde(skip_serializing_if = "Option::is_none")] + attached_params: Option, } impl ReplaceOrderOptions { @@ -42,6 +46,7 @@ impl ReplaceOrderOptions { trigger_count: None, monitor_price: None, remark: None, + attached_params: None, } } @@ -134,4 +139,167 @@ impl ReplaceOrderOptions { ..self } } + + /// Set attached order parameters + pub fn attached_params(self, params: ReplaceAttachedParams) -> Self { + Self { + attached_params: Some(params), + ..self + } + } +} + +/// Attached order parameters for replace order +#[derive(Debug, Serialize, Clone)] +pub struct ReplaceAttachedParams { + attached_order_type: AttachedOrderType, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + time_in_force: Option, + #[serde(skip_serializing_if = "Option::is_none")] + expire_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + activate_order_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_submit_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_submit_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + activate_rth: Option, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + cancel_all_attached: Option, + #[serde(skip_serializing_if = "Option::is_none")] + main_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + quantity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + market_price: Option, +} + +impl ReplaceAttachedParams { + /// Create new ReplaceAttachedParams + pub fn new(attached_order_type: AttachedOrderType) -> Self { + Self { + attached_order_type, + profit_taker_price: None, + stop_loss_price: None, + time_in_force: None, + expire_time: None, + activate_order_type: None, + profit_taker_submit_price: None, + stop_loss_submit_price: None, + activate_rth: None, + profit_taker_id: None, + stop_loss_id: None, + cancel_all_attached: None, + main_id: None, + quantity: None, + market_price: None, + } + } + /// Set the take-profit trigger price + pub fn profit_taker_price(self, v: Decimal) -> Self { + Self { + profit_taker_price: Some(v), + ..self + } + } + /// Set the stop-loss trigger price + pub fn stop_loss_price(self, v: Decimal) -> Self { + Self { + stop_loss_price: Some(v), + ..self + } + } + /// Set the time in force type + pub fn time_in_force(self, v: TimeInForceType) -> Self { + Self { + time_in_force: Some(v), + ..self + } + } + /// Set the expiry time (unix timestamp seconds) + pub fn expire_time(self, v: i64) -> Self { + Self { + expire_time: Some(v), + ..self + } + } + /// Set the order type to submit after trigger + pub fn activate_order_type(self, v: OrderType) -> Self { + Self { + activate_order_type: Some(v), + ..self + } + } + /// Set the take-profit limit price + pub fn profit_taker_submit_price(self, v: Decimal) -> Self { + Self { + profit_taker_submit_price: Some(v), + ..self + } + } + /// Set the stop-loss limit price + pub fn stop_loss_submit_price(self, v: Decimal) -> Self { + Self { + stop_loss_submit_price: Some(v), + ..self + } + } + /// Set the RTH setting for the activated order + pub fn activate_rth(self, v: OutsideRTH) -> Self { + Self { + activate_rth: Some(v), + ..self + } + } + /// Set the take-profit order ID (for modifying existing attached order) + pub fn profit_taker_id(self, v: i64) -> Self { + Self { + profit_taker_id: Some(v), + ..self + } + } + /// Set the stop-loss order ID (for modifying existing attached order) + pub fn stop_loss_id(self, v: i64) -> Self { + Self { + stop_loss_id: Some(v), + ..self + } + } + /// Cancel all attached orders + pub fn cancel_all_attached(self) -> Self { + Self { + cancel_all_attached: Some(true), + ..self + } + } + /// Set the main order ID + pub fn main_id(self, v: i64) -> Self { + Self { + main_id: Some(v), + ..self + } + } + /// Set the quantity + pub fn quantity(self, v: Decimal) -> Self { + Self { + quantity: Some(v), + ..self + } + } + /// Set the market price + pub fn market_price(self, v: Decimal) -> Self { + Self { + market_price: Some(v), + ..self + } + } } diff --git a/rust/src/trade/requests/submit_order.rs b/rust/src/trade/requests/submit_order.rs index 8a4dc96068..6f3f04f889 100644 --- a/rust/src/trade/requests/submit_order.rs +++ b/rust/src/trade/requests/submit_order.rs @@ -4,7 +4,7 @@ use time::Date; use crate::{ serde_utils, - trade::{OrderSide, OrderType, OutsideRTH, TimeInForceType}, + trade::{AttachedOrderType, OrderSide, OrderType, OutsideRTH, TimeInForceType}, }; /// Options for submit order request @@ -39,6 +39,8 @@ pub struct SubmitOrderOptions { remark: Option, #[serde(skip_serializing_if = "Option::is_none")] client_request_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + attached_params: Option, } impl SubmitOrderOptions { @@ -69,6 +71,7 @@ impl SubmitOrderOptions { monitor_price: None, remark: None, client_request_id: None, + attached_params: None, } } @@ -187,4 +190,107 @@ impl SubmitOrderOptions { ..self } } + + /// Set attached order parameters + pub fn attached_params(self, params: SubmitAttachedParams) -> Self { + Self { + attached_params: Some(params), + ..self + } + } +} + +/// Attached order parameters for submit order +#[derive(Debug, Serialize, Clone)] +pub struct SubmitAttachedParams { + attached_order_type: AttachedOrderType, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + time_in_force: Option, + #[serde(skip_serializing_if = "Option::is_none")] + expire_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + activate_order_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_submit_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_submit_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + activate_rth: Option, +} + +impl SubmitAttachedParams { + /// Create new SubmitAttachedParams + pub fn new(attached_order_type: AttachedOrderType) -> Self { + Self { + attached_order_type, + profit_taker_price: None, + stop_loss_price: None, + time_in_force: None, + expire_time: None, + activate_order_type: None, + profit_taker_submit_price: None, + stop_loss_submit_price: None, + activate_rth: None, + } + } + /// Set the take-profit trigger price + pub fn profit_taker_price(self, v: Decimal) -> Self { + Self { + profit_taker_price: Some(v), + ..self + } + } + /// Set the stop-loss trigger price + pub fn stop_loss_price(self, v: Decimal) -> Self { + Self { + stop_loss_price: Some(v), + ..self + } + } + /// Set the time in force type + pub fn time_in_force(self, v: TimeInForceType) -> Self { + Self { + time_in_force: Some(v), + ..self + } + } + /// Set the expiry time (unix timestamp seconds) + pub fn expire_time(self, v: i64) -> Self { + Self { + expire_time: Some(v), + ..self + } + } + /// Set the order type to submit after trigger + pub fn activate_order_type(self, v: OrderType) -> Self { + Self { + activate_order_type: Some(v), + ..self + } + } + /// Set the take-profit limit price + pub fn profit_taker_submit_price(self, v: Decimal) -> Self { + Self { + profit_taker_submit_price: Some(v), + ..self + } + } + /// Set the stop-loss limit price + pub fn stop_loss_submit_price(self, v: Decimal) -> Self { + Self { + stop_loss_submit_price: Some(v), + ..self + } + } + /// Set the RTH setting for the activated order + pub fn activate_rth(self, v: OutsideRTH) -> Self { + Self { + activate_rth: Some(v), + ..self + } + } } diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index ea86f07945..30c60800d8 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -235,6 +235,80 @@ pub enum OutsideRTH { OptionPreMarket, } +/// Attached order type +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display, Serialize, Deserialize)] +pub enum AttachedOrderType { + /// Take profit + #[strum(serialize = "PROFIT_TAKER")] + ProfitTaker, + /// Stop loss + #[strum(serialize = "STOP_LOSS")] + StopLoss, + /// Bracket order + #[strum(serialize = "BRACKET")] + Bracket, +} + +/// Attached order detail +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AttachedOrderDetail { + /// Attached order ID + pub order_id: String, + /// Display type: 1=take-profit, 2=stop-loss + pub attached_type_display: i32, + /// Trigger price + #[serde(with = "serde_utils::decimal_opt_empty_is_none")] + pub trigger_price: Option, + /// Quantity + pub quantity: Decimal, + /// Executed quantity + pub executed_qty: Decimal, + /// Order status + pub status: OrderStatus, + /// Last updated time (unix timestamp seconds) + #[serde( + serialize_with = "time::serde::rfc3339::serialize", + deserialize_with = "serde_utils::timestamp::deserialize" + )] + pub updated_at: OffsetDateTime, + /// Whether withdrawn + pub withdrawn: bool, + /// GTD date + #[serde(with = "serde_utils::date_opt")] + pub gtd: Option, + /// Time in force + pub time_in_force: TimeInForceType, + /// Counter order ID + pub counter_id: String, + /// Trigger status (0=not activated,1=monitoring,2=cancelled,4=triggered) + pub trigger_status: i32, + /// Executed amount + pub executed_amount: Decimal, + /// Tag + pub tag: i32, + /// Submitted time (unix timestamp seconds) + #[serde( + serialize_with = "time::serde::rfc3339::serialize", + deserialize_with = "serde_utils::timestamp::deserialize" + )] + pub submitted_at: OffsetDateTime, + /// Executed price + pub executed_price: Decimal, + /// Force RTH only + #[serde(with = "serde_utils::outside_rth")] + pub force_only_rth: Option, + /// Whether reviewed + pub reviewed: bool, + /// Order type to submit after trigger + pub activate_order_type: OrderType, + /// RTH setting for activated order + #[serde(with = "serde_utils::outside_rth")] + pub activate_rth: Option, + /// Submit price (limit price) + #[serde(with = "serde_utils::decimal_opt_empty_is_none")] + pub submit_price: Option, +} + /// Order #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Order { @@ -321,6 +395,9 @@ pub struct Order { pub monitor_price: Option, /// Remark pub remark: String, + /// Attached orders + #[serde(default)] + pub attached_orders: Vec, } /// Commission-free Status @@ -543,6 +620,9 @@ pub struct OrderDetail { pub history: Vec, /// Order charges pub charge_detail: OrderChargeDetail, + /// Attached orders + #[serde(default)] + pub attached_orders: Vec, } /// Cash info From 41aeb8fd6d9aacc4b5c471826474bbf4ea933cff Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 17 Jun 2026 19:46:58 +0800 Subject: [PATCH 2/9] fix(trade): fix attached order serialization and type mapping bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs fixed: - AttachedOrderType serialized as "Bracket" instead of "BRACKET" — switched from #[derive(Serialize)] to impl_serde_for_enum_string! to use strum Display - SubmitOrderOptions.expire_date serialized as null when absent — added skip_serializing_if = "Option::is_none" - AttachedOrderDetail.attached_type_display was i32, now AttachedOrderType - AttachedOrderDetail.trigger_status was i32, now Option - AttachedOrderDetail.tag was i32, now OrderTag - AttachedOrderDetail.executed_price was Decimal, now Option - OrderDetail.charge_detail was non-optional, now Option (C SDK: added has_charge_detail bool flag) - Java SdkNative.tradeContextOrderDetailAttached missing public modifier Add AttachedOrderType::Unknown variant across all language bindings. Add submit_order_with_attached examples for all languages. Update CLAUDE.md: python/pyproject.toml uses dynamic = ["version"]. --- CLAUDE.md | 10 + c/csrc/include/longbridge.h | 26 +- c/src/trade_context/types.rs | 72 +++-- cpp/include/types.hpp | 12 +- cpp/src/convert.hpp | 20 +- .../submit_order_with_attached/CMakeLists.txt | 7 + examples/c/submit_order_with_attached/main.c | 94 +++++++ .../submit_order_with_attached/CMakeLists.txt | 4 + .../cpp/submit_order_with_attached/main.cpp | 63 +++++ .../java/submit_order_with_attached/pom.xml | 54 ++++ .../src/main/java/main.java | 31 +++ examples/nodejs/submit_order_with_attached.js | 35 +++ examples/python/submit_order_with_attached.py | 33 +++ .../submit_order_with_attached_async.py | 41 +++ .../submit_order_with_attached/Cargo.toml | 9 + .../submit_order_with_attached/src/main.rs | 43 +++ .../main/java/com/longbridge/SdkNative.java | 2 +- .../longbridge/trade/AttachedOrderDetail.java | 12 +- .../longbridge/trade/AttachedOrderType.java | 2 + java/src/types/enum_types.rs | 2 + nodejs/index.d.ts | 20 +- nodejs/index.js | 253 +++++++++++++++--- nodejs/src/trade/types.rs | 17 +- python/pyproject.toml | 1 + python/pysrc/longbridge/openapi.pyi | 15 +- python/src/trade/types.rs | 17 +- rust/src/trade/requests/submit_order.rs | 5 +- rust/src/trade/types.rs | 22 +- 28 files changed, 798 insertions(+), 124 deletions(-) create mode 100644 examples/c/submit_order_with_attached/CMakeLists.txt create mode 100644 examples/c/submit_order_with_attached/main.c create mode 100644 examples/cpp/submit_order_with_attached/CMakeLists.txt create mode 100644 examples/cpp/submit_order_with_attached/main.cpp create mode 100644 examples/java/submit_order_with_attached/pom.xml create mode 100644 examples/java/submit_order_with_attached/src/main/java/main.java create mode 100644 examples/nodejs/submit_order_with_attached.js create mode 100644 examples/python/submit_order_with_attached.py create mode 100644 examples/python/submit_order_with_attached_async.py create mode 100644 examples/rust/submit_order_with_attached/Cargo.toml create mode 100644 examples/rust/submit_order_with_attached/src/main.rs diff --git a/CLAUDE.md b/CLAUDE.md index c427daea71..cabc900ebf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,6 +50,16 @@ When you add, remove, or change any `#[pyclass]`/`#[pymethods]` definitions in signatures, type annotations, and docstrings in sync with the Rust implementation. +To build and install the Python SDK locally (from the `python/` directory): + +```bash +maturin develop +``` + +`python/pyproject.toml` uses `dynamic = ["version"]` — maturin reads the +version from `python/Cargo.toml` automatically. Do **not** add a hardcoded +`version` field to `pyproject.toml`. + ## After modifying the C SDK (`c/`) `c/csrc/include/longbridge.h` is **auto-generated** by `cbindgen` during the diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index 6d7d48a561..f785b9f36e 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1118,18 +1118,22 @@ typedef enum lb_topic_type_t { * Attached order type */ typedef enum CAttachedOrderType { + /** + * Unknown + */ + AttachedOrderTypeUnknown = 0, /** * Take profit */ - AttachedOrderTypeProfitTaker = 0, + AttachedOrderTypeProfitTaker = 1, /** * Stop loss */ - AttachedOrderTypeStopLoss = 1, + AttachedOrderTypeStopLoss = 2, /** * Bracket order */ - AttachedOrderTypeBracket = 2, + AttachedOrderTypeBracket = 3, } CAttachedOrderType; /** @@ -3418,9 +3422,9 @@ typedef struct CAttachedOrderDetail { */ const char *order_id; /** - * Display type: 1=take-profit, 2=stop-loss + * Attached order type */ - int32_t attached_type_display; + enum CAttachedOrderType attached_type_display; /** * Trigger price (maybe null) */ @@ -3458,9 +3462,9 @@ typedef struct CAttachedOrderDetail { */ const char *counter_id; /** - * Trigger status + * Trigger status (maybe null) */ - int32_t trigger_status; + const enum lb_trigger_status_t *trigger_status; /** * Executed amount */ @@ -3468,7 +3472,7 @@ typedef struct CAttachedOrderDetail { /** * Tag */ - int32_t tag; + enum lb_order_tag_t tag; /** * Submitted time (unix timestamp) */ @@ -4239,7 +4243,11 @@ typedef struct lb_order_detail_t { */ uintptr_t num_history; /** - * Order charges + * Whether charge_detail is valid (false when the order has no charge info) + */ + bool has_charge_detail; + /** + * Order charges (only valid when has_charge_detail is true) */ struct lb_order_charge_detail_t charge_detail; /** diff --git a/c/src/trade_context/types.rs b/c/src/trade_context/types.rs index 2b043e4bcd..377ca7d564 100644 --- a/c/src/trade_context/types.rs +++ b/c/src/trade_context/types.rs @@ -28,17 +28,20 @@ use crate::{ #[allow(clippy::enum_variant_names)] #[repr(C)] pub enum CAttachedOrderType { + /// Unknown + AttachedOrderTypeUnknown = 0, /// Take profit - AttachedOrderTypeProfitTaker = 0, + AttachedOrderTypeProfitTaker = 1, /// Stop loss - AttachedOrderTypeStopLoss = 1, + AttachedOrderTypeStopLoss = 2, /// Bracket order - AttachedOrderTypeBracket = 2, + AttachedOrderTypeBracket = 3, } impl From for CAttachedOrderType { fn from(value: AttachedOrderType) -> Self { match value { + AttachedOrderType::Unknown => CAttachedOrderType::AttachedOrderTypeUnknown, AttachedOrderType::ProfitTaker => CAttachedOrderType::AttachedOrderTypeProfitTaker, AttachedOrderType::StopLoss => CAttachedOrderType::AttachedOrderTypeStopLoss, AttachedOrderType::Bracket => CAttachedOrderType::AttachedOrderTypeBracket, @@ -49,6 +52,7 @@ impl From for CAttachedOrderType { impl From for AttachedOrderType { fn from(value: CAttachedOrderType) -> Self { match value { + CAttachedOrderType::AttachedOrderTypeUnknown => AttachedOrderType::Unknown, CAttachedOrderType::AttachedOrderTypeProfitTaker => AttachedOrderType::ProfitTaker, CAttachedOrderType::AttachedOrderTypeStopLoss => AttachedOrderType::StopLoss, CAttachedOrderType::AttachedOrderTypeBracket => AttachedOrderType::Bracket, @@ -61,8 +65,8 @@ impl From for AttachedOrderType { pub struct CAttachedOrderDetail { /// Attached order ID pub order_id: *const c_char, - /// Display type: 1=take-profit, 2=stop-loss - pub attached_type_display: i32, + /// Attached order type + pub attached_type_display: CAttachedOrderType, /// Trigger price (maybe null) pub trigger_price: *const CDecimal, /// Quantity @@ -81,12 +85,12 @@ pub struct CAttachedOrderDetail { pub time_in_force: CTimeInForceType, /// Counter order ID pub counter_id: *const c_char, - /// Trigger status - pub trigger_status: i32, + /// Trigger status (maybe null) + pub trigger_status: *const CTriggerStatus, /// Executed amount pub executed_amount: *const CDecimal, /// Tag - pub tag: i32, + pub tag: COrderTag, /// Submitted time (unix timestamp) pub submitted_at: i64, /// Executed price @@ -106,7 +110,7 @@ pub struct CAttachedOrderDetail { #[derive(Debug)] pub(crate) struct CAttachedOrderDetailOwned { order_id: CString, - attached_type_display: i32, + attached_type_display: AttachedOrderType, trigger_price: Option, quantity: CDecimal, executed_qty: CDecimal, @@ -116,11 +120,11 @@ pub(crate) struct CAttachedOrderDetailOwned { gtd: Option, time_in_force: TimeInForceType, counter_id: CString, - trigger_status: i32, + trigger_status: Option, executed_amount: CDecimal, - tag: i32, + tag: OrderTag, submitted_at: i64, - executed_price: CDecimal, + executed_price: Option, force_only_rth: Option, reviewed: bool, activate_order_type: OrderType, @@ -165,11 +169,11 @@ impl From for CAttachedOrderDetailOwned { gtd: gtd.map(Into::into), time_in_force, counter_id: counter_id.into(), - trigger_status, + trigger_status: trigger_status.map(Into::into), executed_amount: executed_amount.into(), tag, submitted_at: submitted_at.unix_timestamp(), - executed_price: executed_price.into(), + executed_price: executed_price.map(Into::into), force_only_rth: force_only_rth.map(Into::into), reviewed, activate_order_type, @@ -208,7 +212,7 @@ impl ToFFI for CAttachedOrderDetailOwned { } = self; CAttachedOrderDetail { order_id: order_id.to_ffi_type(), - attached_type_display: *attached_type_display, + attached_type_display: (*attached_type_display).into(), trigger_price: trigger_price .as_ref() .map(ToFFI::to_ffi_type) @@ -224,11 +228,17 @@ impl ToFFI for CAttachedOrderDetailOwned { .unwrap_or(std::ptr::null()), time_in_force: (*time_in_force).into(), counter_id: counter_id.to_ffi_type(), - trigger_status: *trigger_status, + trigger_status: trigger_status + .as_ref() + .map(|value| value as *const CTriggerStatus) + .unwrap_or(std::ptr::null()), executed_amount: executed_amount.to_ffi_type(), - tag: *tag, + tag: (*tag).into(), submitted_at: *submitted_at, - executed_price: executed_price.to_ffi_type(), + executed_price: executed_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), force_only_rth: force_only_rth .as_ref() .map(|value| value as *const COutsideRTH) @@ -2000,6 +2010,7 @@ impl ToFFI for COrderChargeItemOwned { } /// Order charge detail +#[derive(Clone)] #[repr(C)] pub struct COrderChargeDetail { /// Total charges amount @@ -2012,6 +2023,17 @@ pub struct COrderChargeDetail { pub num_items: usize, } +impl Default for COrderChargeDetail { + fn default() -> Self { + Self { + total_amount: std::ptr::null(), + currency: std::ptr::null(), + items: std::ptr::null(), + num_items: 0, + } + } +} + #[derive(Debug)] pub(crate) struct COrderChargeDetailOwned { /// Total charges amount @@ -2128,7 +2150,9 @@ pub struct COrderDetail { pub history: *const COrderHistoryDetail, /// Number of history pub num_history: usize, - /// Order charges + /// Whether charge_detail is valid (false when the order has no charge info) + pub has_charge_detail: bool, + /// Order charges (only valid when has_charge_detail is true) pub charge_detail: COrderChargeDetail, /// Attached orders pub attached_orders: *const CAttachedOrderDetail, @@ -2177,7 +2201,7 @@ pub(crate) struct COrderDetailOwned { platform_deducted_amount: Option, platform_deducted_currency: Option, history: CVec, - charge_detail: COrderChargeDetailOwned, + charge_detail: Option, attached_orders: CVec, } @@ -2266,7 +2290,7 @@ impl From for COrderDetailOwned { platform_deducted_amount: platform_deducted_amount.map(Into::into), platform_deducted_currency: platform_deducted_currency.map(Into::into), history: history.into(), - charge_detail: charge_detail.into(), + charge_detail: charge_detail.map(Into::into), attached_orders: attached_orders.into(), } } @@ -2423,7 +2447,11 @@ impl ToFFI for COrderDetailOwned { .unwrap_or(std::ptr::null()), history: history.to_ffi_type(), num_history: history.len(), - charge_detail: charge_detail.to_ffi_type(), + has_charge_detail: charge_detail.is_some(), + charge_detail: charge_detail + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or_default(), attached_orders: attached_orders.to_ffi_type(), num_attached_orders: attached_orders.len(), } diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index f64b505d13..39b3a86c89 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1569,7 +1569,7 @@ enum class AttachedOrderType struct AttachedOrderDetail { std::string order_id; - int32_t attached_type_display; + AttachedOrderType attached_type_display; std::optional trigger_price; Decimal quantity; Decimal executed_qty; @@ -1579,11 +1579,11 @@ struct AttachedOrderDetail std::optional gtd; TimeInForceType time_in_force; std::string counter_id; - int32_t trigger_status; + std::optional trigger_status; Decimal executed_amount; - int32_t tag; + OrderTag tag; int64_t submitted_at; - Decimal executed_price; + std::optional executed_price; std::optional force_only_rth; bool reviewed; OrderType activate_order_type; @@ -2231,8 +2231,8 @@ struct OrderDetail std::optional platform_deducted_currency; /// Order history details std::vector history; - /// Order charges - OrderChargeDetail charge_detail; + /// Order charges (maybe null) + std::optional charge_detail; /// Attached orders std::vector attached_orders; }; diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index 41bffe1219..9392f9f8bd 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -1285,7 +1285,7 @@ convert(OutsideRTH status) } inline AttachedOrderType -convert(lb_attached_order_type_t ty) +convert(CAttachedOrderType ty) { switch (ty) { case AttachedOrderTypeUnknown: @@ -1301,7 +1301,7 @@ convert(lb_attached_order_type_t ty) } } -inline lb_attached_order_type_t +inline CAttachedOrderType convert(AttachedOrderType ty) { switch (ty) { @@ -1319,11 +1319,11 @@ convert(AttachedOrderType ty) } inline AttachedOrderDetail -convert(const lb_attached_order_detail_t* detail) +convert(const CAttachedOrderDetail* detail) { return AttachedOrderDetail{ detail->order_id, - detail->attached_type_display, + convert(detail->attached_type_display), detail->trigger_price ? std::optional{ Decimal(detail->trigger_price) } : std::nullopt, Decimal(detail->quantity), @@ -1334,11 +1334,11 @@ convert(const lb_attached_order_detail_t* detail) detail->gtd ? std::optional{ convert(detail->gtd) } : std::nullopt, convert(detail->time_in_force), detail->counter_id, - detail->trigger_status, + detail->trigger_status ? std::optional{ convert(*detail->trigger_status) } : std::nullopt, Decimal(detail->executed_amount), - detail->tag, + convert(detail->tag), detail->submitted_at, - Decimal(detail->executed_price), + detail->executed_price ? std::optional{ Decimal(detail->executed_price) } : std::nullopt, detail->force_only_rth ? std::optional{ convert(*detail->force_only_rth) } : std::nullopt, detail->reviewed, @@ -1355,7 +1355,7 @@ struct CSubmitAttachedParamsStorage lb_time_in_force_type_t time_in_force; lb_order_type_t activate_order_type; lb_outside_rth_t activate_rth; - lb_submit_attached_params_t params; + CSubmitAttachedParams params; }; inline CSubmitAttachedParamsStorage @@ -1399,7 +1399,7 @@ struct CReplaceAttachedParamsStorage lb_time_in_force_type_t time_in_force; lb_order_type_t activate_order_type; lb_outside_rth_t activate_rth; - lb_replace_attached_params_t params; + CReplaceAttachedParams params; }; inline CReplaceAttachedParamsStorage @@ -1947,7 +1947,7 @@ convert(const lb_order_detail_t* order) ? std::optional{ std::string(order->platform_deducted_currency) } : std::nullopt, history, - convert(&order->charge_detail), + order->has_charge_detail ? std::optional{ convert(&order->charge_detail) } : std::nullopt, attached_orders, }; } diff --git a/examples/c/submit_order_with_attached/CMakeLists.txt b/examples/c/submit_order_with_attached/CMakeLists.txt new file mode 100644 index 0000000000..45c918a9b7 --- /dev/null +++ b/examples/c/submit_order_with_attached/CMakeLists.txt @@ -0,0 +1,7 @@ +include_directories(../../../c/csrc/include) + +add_executable(submit_order_with_attached_c main.c) +target_link_libraries(submit_order_with_attached_c longbridge_c) +if(NOT CMAKE_HOST_WIN32) + target_link_libraries(submit_order_with_attached_c ncurses) +endif() diff --git a/examples/c/submit_order_with_attached/main.c b/examples/c/submit_order_with_attached/main.c new file mode 100644 index 0000000000..f45b3a92b3 --- /dev/null +++ b/examples/c/submit_order_with_attached/main.c @@ -0,0 +1,94 @@ +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif + +static const char* CLIENT_ID = "your-client-id"; + +void +on_submit_order(const struct lb_async_result_t* res) +{ + if (res->error) { + printf("failed to submit order: %s\n", lb_error_message(res->error)); + return; + } + + const lb_submit_order_response_t* resp = res->data; + printf("order id: %s\n", resp->order_id); +} + +void +on_open_url(const char* url, void* userdata) +{ + (void)userdata; + printf("Open this URL to authorize: %s\n", url); +} + +void +on_oauth_ready(const struct lb_async_result_t* res) +{ + if (res->error) { + printf("OAuth failed: %s\n", lb_error_message(res->error)); + return; + } + + const lb_oauth_t* oauth = (const lb_oauth_t*)res->data; + lb_config_t* config = lb_config_from_oauth(oauth); + lb_oauth_free((lb_oauth_t*)oauth); + const lb_trade_context_t* ctx = lb_trade_context_new(config); + lb_config_free(config); + + if (!ctx) { + printf("failed to create trade context\n"); + return; + } + + *((const lb_trade_context_t**)res->userdata) = ctx; + + lb_decimal_t* submitted_quantity = lb_decimal_from_str("1"); + lb_decimal_t* submitted_price = lb_decimal_from_str("200"); + lb_decimal_t* profit_taker_price = lb_decimal_from_str("220"); + lb_decimal_t* stop_loss_price = lb_decimal_from_str("180"); + + CSubmitAttachedParams attached = { + .attached_order_type = AttachedOrderTypeBracket, + .profit_taker_price = profit_taker_price, + .stop_loss_price = stop_loss_price, + }; + + lb_submit_order_options_t opts = { + .symbol = "AAPL.US", + .order_type = OrderTypeLO, + .side = OrderSideBuy, + .submitted_quantity = submitted_quantity, + .time_in_force = TimeInForceDay, + .submitted_price = submitted_price, + .attached_params = &attached, + }; + + lb_trade_context_submit_order(ctx, &opts, on_submit_order, NULL); + + lb_decimal_free(submitted_quantity); + lb_decimal_free(submitted_price); + lb_decimal_free(profit_taker_price); + lb_decimal_free(stop_loss_price); +} + +int +main(int argc, char const* argv[]) +{ +#ifdef WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + + const lb_trade_context_t* ctx = NULL; + + lb_oauth_new(CLIENT_ID, 0, on_open_url, NULL, on_oauth_ready, &ctx); + + getchar(); + lb_trade_context_release(ctx); + return 0; +} diff --git a/examples/cpp/submit_order_with_attached/CMakeLists.txt b/examples/cpp/submit_order_with_attached/CMakeLists.txt new file mode 100644 index 0000000000..02b0ce5d8c --- /dev/null +++ b/examples/cpp/submit_order_with_attached/CMakeLists.txt @@ -0,0 +1,4 @@ +include_directories(../../../c/csrc/include ../../../cpp/include) + +add_executable(submit_order_with_attached_cpp main.cpp) +target_link_libraries(submit_order_with_attached_cpp longbridge_cpp longbridge_c) diff --git a/examples/cpp/submit_order_with_attached/main.cpp b/examples/cpp/submit_order_with_attached/main.cpp new file mode 100644 index 0000000000..29da684c69 --- /dev/null +++ b/examples/cpp/submit_order_with_attached/main.cpp @@ -0,0 +1,63 @@ +#include +#include + +#ifdef WIN32 +#include +#endif + +using namespace longbridge; +using namespace longbridge::trade; + +static void +run(const OAuth& oauth) +{ + Config config = Config::from_oauth(oauth); + TradeContext ctx = TradeContext::create(config); + + SubmitAttachedParams attached{ + AttachedOrderType::Bracket, + Decimal("220"), // profit_taker_price + Decimal("180"), // stop_loss_price + }; + + SubmitOrderOptions opts{ + "AAPL.US", OrderType::LO, OrderSide::Buy, + Decimal("1"), TimeInForceType::Day, Decimal("200"), + }; + opts.attached_params = attached; + + ctx.submit_order(opts, [](auto res) { + if (!res) { + std::cout << "failed to submit order: " << *res.status().message() + << std::endl; + return; + } + std::cout << "order id: " << res->order_id << std::endl; + }); +} + +int +main(int argc, char const* argv[]) +{ +#ifdef WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + + const std::string client_id = "your-client-id"; + + OAuthBuilder(client_id).build( + [](const std::string& url) { + std::cout << "Open this URL to authorize: " << url << std::endl; + }, + [](auto res) { + if (!res) { + std::cout << "authorization failed: " << *res.status().message() + << std::endl; + return; + } + run(*res); + }); + + std::cin.get(); + return 0; +} diff --git a/examples/java/submit_order_with_attached/pom.xml b/examples/java/submit_order_with_attached/pom.xml new file mode 100644 index 0000000000..a59d244757 --- /dev/null +++ b/examples/java/submit_order_with_attached/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + io.github.longbridge + submit_order_with_attached + 0.0.1 + + + + io.github.longbridge + openapi-sdk + LATEST + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + + + exec + + + + + java + + -classpath + + Main + + + + + + + + 1.11 + 11 + 11 + utf-8 + + diff --git a/examples/java/submit_order_with_attached/src/main/java/main.java b/examples/java/submit_order_with_attached/src/main/java/main.java new file mode 100644 index 0000000000..904dd679f6 --- /dev/null +++ b/examples/java/submit_order_with_attached/src/main/java/main.java @@ -0,0 +1,31 @@ +import com.longbridge.*; +import com.longbridge.trade.*; +import java.math.BigDecimal; + +class Main { + public static void main(String[] args) throws Exception { + String clientId = "your-client-id"; + OAuth oauth = new OAuthBuilder(clientId) + .build(url -> System.out.println("Open to authorize: " + url)) + .get(); + try (oauth; + Config config = Config.fromOAuth(oauth); + TradeContext ctx = TradeContext.create(config)) { + SubmitAttachedParams attached = new SubmitAttachedParams(AttachedOrderType.BRACKET) + .setProfitTakerPrice(new BigDecimal("220")) + .setStopLossPrice(new BigDecimal("180")); + + SubmitOrderOptions opts = new SubmitOrderOptions( + "AAPL.US", + OrderType.LO, + OrderSide.Buy, + new BigDecimal("1"), + TimeInForceType.Day) + .setSubmittedPrice(new BigDecimal("200")) + .setAttachedParams(attached); + + SubmitOrderResponse resp = ctx.submitOrder(opts).get(); + System.out.println(resp); + } + } +} diff --git a/examples/nodejs/submit_order_with_attached.js b/examples/nodejs/submit_order_with_attached.js new file mode 100644 index 0000000000..fd3d3e241f --- /dev/null +++ b/examples/nodejs/submit_order_with_attached.js @@ -0,0 +1,35 @@ +const { + Config, + TradeContext, + Decimal, + OrderSide, + TimeInForceType, + OrderType, + AttachedOrderType, + OAuth, +} = require('longbridge'); + +async function main() { + const oauth = await OAuth.build("your-client-id", (_, url) => { + console.log("Open this URL to authorize: " + url); + }); + let config = Config.fromOAuth(oauth); + let ctx = TradeContext.new(config); + + let resp = await ctx.submitOrder({ + symbol: "AAPL.US", + orderType: OrderType.LO, + side: OrderSide.Buy, + submittedQuantity: new Decimal(1), + timeInForce: TimeInForceType.Day, + submittedPrice: new Decimal(200), + attachedParams: { + attachedOrderType: AttachedOrderType.Bracket, + profitTakerPrice: new Decimal(220), + stopLossPrice: new Decimal(180), + }, + }); + console.log(resp.toString()); +} + +Promise.all([main()]).catch((err) => console.error(err)); diff --git a/examples/python/submit_order_with_attached.py b/examples/python/submit_order_with_attached.py new file mode 100644 index 0000000000..5ca6f377b5 --- /dev/null +++ b/examples/python/submit_order_with_attached.py @@ -0,0 +1,33 @@ +from decimal import Decimal + +from longbridge.openapi import ( + AttachedOrderType, + Config, + OAuthBuilder, + OrderSide, + OrderType, + SubmitAttachedParams, + TimeInForceType, + TradeContext, +) + +oauth = OAuthBuilder("your-client-id").build( + lambda url: print(f"Open this URL to authorize: {url}") +) +config = Config.from_oauth(oauth) +ctx = TradeContext(config) + +resp = ctx.submit_order( + symbol="AAPL.US", + order_type=OrderType.LO, + side=OrderSide.Buy, + submitted_quantity=Decimal(1), + time_in_force=TimeInForceType.Day, + submitted_price=Decimal(200), + attached_params=SubmitAttachedParams( + AttachedOrderType.Bracket, + profit_taker_price=Decimal(220), + stop_loss_price=Decimal(180), + ), +) +print(resp) diff --git a/examples/python/submit_order_with_attached_async.py b/examples/python/submit_order_with_attached_async.py new file mode 100644 index 0000000000..3fc5842c45 --- /dev/null +++ b/examples/python/submit_order_with_attached_async.py @@ -0,0 +1,41 @@ +"""Submit order with attached take-profit/stop-loss (async).""" +import asyncio +from decimal import Decimal + +from longbridge.openapi import ( + AsyncTradeContext, + AttachedOrderType, + Config, + OAuthBuilder, + OrderSide, + OrderType, + SubmitAttachedParams, + TimeInForceType, +) + + +async def main() -> None: + oauth = await OAuthBuilder("your-client-id").build_async( + lambda url: print(f"Open this URL to authorize: {url}") + ) + config = Config.from_oauth(oauth) + ctx = AsyncTradeContext.create(config) + + resp = await ctx.submit_order( + symbol="AAPL.US", + order_type=OrderType.LO, + side=OrderSide.Buy, + submitted_quantity=Decimal(1), + time_in_force=TimeInForceType.Day, + submitted_price=Decimal(200), + attached_params=SubmitAttachedParams( + AttachedOrderType.Bracket, + profit_taker_price=Decimal(220), + stop_loss_price=Decimal(180), + ), + ) + print(resp) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/rust/submit_order_with_attached/Cargo.toml b/examples/rust/submit_order_with_attached/Cargo.toml new file mode 100644 index 0000000000..7f4ff345aa --- /dev/null +++ b/examples/rust/submit_order_with_attached/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "submit_order_with_attached" +version = "0.1.0" +edition.workspace = true + +[dependencies] +longbridge = { path = "../../../rust" } +tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] } +tracing-subscriber = { version = "0.3.18", features = ["fmt", "env-filter"] } diff --git a/examples/rust/submit_order_with_attached/src/main.rs b/examples/rust/submit_order_with_attached/src/main.rs new file mode 100644 index 0000000000..5ed7abded8 --- /dev/null +++ b/examples/rust/submit_order_with_attached/src/main.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +use longbridge::{ + decimal, + oauth::OAuthBuilder, + trade::{ + AttachedOrderType, OrderSide, OrderType, SubmitAttachedParams, SubmitOrderOptions, + TimeInForceType, TradeContext, + }, + Config, +}; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let oauth = OAuthBuilder::new("your-client-id") + .build(|url| println!("Open this URL to authorize: {url}")) + .await?; + let config = Arc::new(Config::from_oauth(oauth)); + let (ctx, _) = TradeContext::new(config); + + let attached = SubmitAttachedParams::new(AttachedOrderType::Bracket) + .profit_taker_price(decimal!(220i32)) + .stop_loss_price(decimal!(180i32)); + + let opts = SubmitOrderOptions::new( + "AAPL.US", + OrderType::LO, + OrderSide::Buy, + decimal!(1i32), + TimeInForceType::Day, + ) + .submitted_price(decimal!(200i32)) + .attached_params(attached); + + let resp = ctx.submit_order(opts).await?; + println!("{resp:?}"); + Ok(()) +} diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 629fef5fdd..5258c1b8b5 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -252,7 +252,7 @@ public static native void tradeContextStockPositions(long context, GetStockPosit public static native void tradeContextOrderDetail(long context, String orderId, AsyncCallback callback); - static native void tradeContextOrderDetailAttached(long context, String orderId, Object callback); + public static native void tradeContextOrderDetailAttached(long context, String orderId, Object callback); public static native void tradeContextEstimateMaxPurchaseQuantity(long context, EstimateMaxPurchaseQuantityOptions opts, diff --git a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java index 82605548a7..b67562bad6 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java @@ -9,7 +9,7 @@ */ public class AttachedOrderDetail { private String orderId; - private int attachedTypeDisplay; + private AttachedOrderType attachedTypeDisplay; private BigDecimal triggerPrice; private BigDecimal quantity; private BigDecimal executedQty; @@ -19,9 +19,9 @@ public class AttachedOrderDetail { private LocalDate gtd; private TimeInForceType timeInForce; private String counterId; - private int triggerStatus; + private TriggerStatus triggerStatus; private BigDecimal executedAmount; - private int tag; + private OrderTag tag; private OffsetDateTime submittedAt; private BigDecimal executedPrice; private OutsideRTH forceOnlyRth; @@ -44,7 +44,7 @@ public String getOrderId() { * * @return attached type display */ - public int getAttachedTypeDisplay() { + public AttachedOrderType getAttachedTypeDisplay() { return attachedTypeDisplay; } @@ -134,7 +134,7 @@ public String getCounterId() { * * @return trigger status */ - public int getTriggerStatus() { + public TriggerStatus getTriggerStatus() { return triggerStatus; } @@ -152,7 +152,7 @@ public BigDecimal getExecutedAmount() { * * @return tag */ - public int getTag() { + public OrderTag getTag() { return tag; } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java index bd1d4cefbf..2a5e150322 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderType.java @@ -2,6 +2,8 @@ /** Attached order type */ public enum AttachedOrderType { + /** Unknown */ + UNKNOWN, /** Take profit */ PROFIT_TAKER, /** Stop loss */ diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 2f10152d62..fefb918ae7 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -448,6 +448,8 @@ impl_java_enum!( "com/longbridge/trade/AttachedOrderType", longbridge::trade::AttachedOrderType, [ + #[java(remote = Unknown)] + UNKNOWN, #[java(remote = ProfitTaker)] PROFIT_TAKER, #[java(remote = StopLoss)] diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index f38419fbf3..c66b0c7bf4 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -80,8 +80,8 @@ export declare class AttachedOrderDetail { toJSON(): any /** Order ID */ get orderId(): string - /** Attached type display */ - get attachedTypeDisplay(): number + /** Attached order type */ + get attachedTypeDisplay(): AttachedOrderType /** Trigger price */ get triggerPrice(): Decimal | null /** Submitted quantity */ @@ -101,15 +101,15 @@ export declare class AttachedOrderDetail { /** Counter ID */ get counterId(): string /** Trigger status */ - get triggerStatus(): number + get triggerStatus(): TriggerStatus | null /** Executed amount */ get executedAmount(): Decimal /** Tag */ - get tag(): number + get tag(): OrderTag /** Submitted time */ get submittedAt(): Date /** Executed price */ - get executedPrice(): Decimal + get executedPrice(): Decimal | null /** Force only RTH */ get forceOnlyRth(): OutsideRTH | null /** Reviewed */ @@ -1214,7 +1214,7 @@ export declare class OrderDetail { /** Order history details */ get history(): Array /** Order charges */ - get chargeDetail(): OrderChargeDetail + get chargeDetail(): OrderChargeDetail | null /** Attached orders */ get attachedOrders(): Array } @@ -3251,12 +3251,14 @@ export declare const enum AssetType { /** Attached order type */ export declare const enum AttachedOrderType { + /** Unknown */ + Unknown = 0, /** Profit taker */ - ProfitTaker = 0, + ProfitTaker = 1, /** Stop loss */ - StopLoss = 1, + StopLoss = 2, /** Bracket */ - Bracket = 2 + Bracket = 3 } export declare const enum BalanceType { diff --git a/nodejs/index.js b/nodejs/index.js index fe5afa2ab6..5a371a504a 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -3,9 +3,6 @@ // @ts-nocheck /* auto-generated by NAPI-RS */ -const { createRequire } = require('node:module') -require = createRequire(__filename) - const { readFileSync } = require('node:fs') let nativeBinding = null const loadErrors = [] @@ -66,7 +63,7 @@ const isMuslFromChildProcess = () => { function requireNative() { if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { try { - nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); + return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); } catch (err) { loadErrors.push(err) } @@ -78,7 +75,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-android-arm64') + const binding = require('longbridge-android-arm64') + const bindingPackageVersion = require('longbridge-android-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -89,7 +91,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-android-arm-eabi') + const binding = require('longbridge-android-arm-eabi') + const bindingPackageVersion = require('longbridge-android-arm-eabi/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -98,16 +105,39 @@ function requireNative() { } } else if (process.platform === 'win32') { if (process.arch === 'x64') { + if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') { + try { + return require('./longbridge.win32-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } try { + const binding = require('longbridge-win32-x64-gnu') + const bindingPackageVersion = require('longbridge-win32-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { return require('./longbridge.win32-x64-msvc.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-win32-x64-msvc') + const binding = require('longbridge-win32-x64-msvc') + const bindingPackageVersion = require('longbridge-win32-x64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } + } } else if (process.arch === 'ia32') { try { return require('./longbridge.win32-ia32-msvc.node') @@ -115,7 +145,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-win32-ia32-msvc') + const binding = require('longbridge-win32-ia32-msvc') + const bindingPackageVersion = require('longbridge-win32-ia32-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -126,7 +161,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-win32-arm64-msvc') + const binding = require('longbridge-win32-arm64-msvc') + const bindingPackageVersion = require('longbridge-win32-arm64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -140,7 +180,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-darwin-universal') + const binding = require('longbridge-darwin-universal') + const bindingPackageVersion = require('longbridge-darwin-universal/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -151,7 +196,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-darwin-x64') + const binding = require('longbridge-darwin-x64') + const bindingPackageVersion = require('longbridge-darwin-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -162,7 +212,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-darwin-arm64') + const binding = require('longbridge-darwin-arm64') + const bindingPackageVersion = require('longbridge-darwin-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -177,7 +232,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-freebsd-x64') + const binding = require('longbridge-freebsd-x64') + const bindingPackageVersion = require('longbridge-freebsd-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -188,7 +248,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-freebsd-arm64') + const binding = require('longbridge-freebsd-arm64') + const bindingPackageVersion = require('longbridge-freebsd-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -204,7 +269,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-x64-musl') + const binding = require('longbridge-linux-x64-musl') + const bindingPackageVersion = require('longbridge-linux-x64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -215,7 +285,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-x64-gnu') + const binding = require('longbridge-linux-x64-gnu') + const bindingPackageVersion = require('longbridge-linux-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -228,7 +303,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm64-musl') + const binding = require('longbridge-linux-arm64-musl') + const bindingPackageVersion = require('longbridge-linux-arm64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -239,7 +319,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm64-gnu') + const binding = require('longbridge-linux-arm64-gnu') + const bindingPackageVersion = require('longbridge-linux-arm64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -252,7 +337,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm-musleabihf') + const binding = require('longbridge-linux-arm-musleabihf') + const bindingPackageVersion = require('longbridge-linux-arm-musleabihf/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -263,7 +353,46 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm-gnueabihf') + const binding = require('longbridge-linux-arm-gnueabihf') + const bindingPackageVersion = require('longbridge-linux-arm-gnueabihf/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'loong64') { + if (isMusl()) { + try { + return require('./longbridge.linux-loong64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('longbridge-linux-loong64-musl') + const bindingPackageVersion = require('longbridge-linux-loong64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./longbridge.linux-loong64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('longbridge-linux-loong64-gnu') + const bindingPackageVersion = require('longbridge-linux-loong64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -276,7 +405,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-riscv64-musl') + const binding = require('longbridge-linux-riscv64-musl') + const bindingPackageVersion = require('longbridge-linux-riscv64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -287,7 +421,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-riscv64-gnu') + const binding = require('longbridge-linux-riscv64-gnu') + const bindingPackageVersion = require('longbridge-linux-riscv64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -299,7 +438,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-ppc64-gnu') + const binding = require('longbridge-linux-ppc64-gnu') + const bindingPackageVersion = require('longbridge-linux-ppc64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -310,7 +454,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-s390x-gnu') + const binding = require('longbridge-linux-s390x-gnu') + const bindingPackageVersion = require('longbridge-linux-s390x-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -320,34 +469,49 @@ function requireNative() { } else if (process.platform === 'openharmony') { if (process.arch === 'arm64') { try { - return require('./longbridge.linux-arm64-ohos.node') + return require('./longbridge.openharmony-arm64.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-linux-arm64-ohos') + const binding = require('longbridge-openharmony-arm64') + const bindingPackageVersion = require('longbridge-openharmony-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } } else if (process.arch === 'x64') { try { - return require('./longbridge.linux-x64-ohos.node') + return require('./longbridge.openharmony-x64.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-linux-x64-ohos') + const binding = require('longbridge-openharmony-x64') + const bindingPackageVersion = require('longbridge-openharmony-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } } else if (process.arch === 'arm') { try { - return require('./longbridge.linux-arm-ohos.node') + return require('./longbridge.openharmony-arm.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-linux-arm-ohos') + const binding = require('longbridge-openharmony-arm') + const bindingPackageVersion = require('longbridge-openharmony-arm/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -362,22 +526,36 @@ function requireNative() { nativeBinding = requireNative() if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + let wasiBinding = null + let wasiBindingError = null try { - nativeBinding = require('./longbridge.wasi.cjs') + wasiBinding = require('./longbridge.wasi.cjs') + nativeBinding = wasiBinding } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { - loadErrors.push(err) + wasiBindingError = err } } - if (!nativeBinding) { + if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { try { - nativeBinding = require('longbridge-wasm32-wasi') + wasiBinding = require('longbridge-wasm32-wasi') + nativeBinding = wasiBinding } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { + if (!wasiBindingError) { + wasiBindingError = err + } else { + wasiBindingError.cause = err + } loadErrors.push(err) } } } + if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { + const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') + error.cause = wasiBindingError + throw error + } } if (!nativeBinding) { @@ -386,7 +564,12 @@ if (!nativeBinding) { `Cannot find native binding. ` + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', - { cause: loadErrors } + { + cause: loadErrors.reduce((err, cur) => { + cur.cause = err + return cur + }), + }, ) } throw new Error(`Failed to load native binding`) diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs index e409becf7a..f355b37766 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -200,6 +200,8 @@ pub struct AllExecutionsResponse { #[derive(Debug, JsEnum, Hash, Eq, PartialEq, Copy, Clone)] #[js(remote = "longbridge::trade::AttachedOrderType")] pub enum AttachedOrderType { + /// Unknown + Unknown, /// Profit taker ProfitTaker, /// Stop loss @@ -215,8 +217,8 @@ pub enum AttachedOrderType { pub struct AttachedOrderDetail { /// Order ID order_id: String, - /// Attached type display - attached_type_display: i32, + /// Attached order type + attached_type_display: AttachedOrderType, /// Trigger price #[js(opt)] trigger_price: Option, @@ -239,16 +241,18 @@ pub struct AttachedOrderDetail { /// Counter ID counter_id: String, /// Trigger status - trigger_status: i32, + #[js(opt)] + trigger_status: Option, /// Executed amount executed_amount: Decimal, /// Tag - tag: i32, + tag: OrderTag, /// Submitted time #[js(datetime)] submitted_at: DateTime, /// Executed price - executed_price: Decimal, + #[js(opt)] + executed_price: Option, /// Force only RTH #[js(opt)] force_only_rth: Option, @@ -564,7 +568,8 @@ pub struct OrderDetail { #[js(array)] history: Vec, /// Order charges - charge_detail: OrderChargeDetail, + #[js(opt)] + charge_detail: Option, /// Attached orders #[js(array)] attached_orders: Vec, diff --git a/python/pyproject.toml b/python/pyproject.toml index 051c2f60aa..1ad076542e 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,5 +1,6 @@ [project] name = "longbridge" +dynamic = ["version"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 474edaa9fa..73f93cd69b 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -5888,6 +5888,11 @@ class AttachedOrderType: Attached order type """ + class Unknown(AttachedOrderType): + """ + Unknown + """ + class ProfitTaker(AttachedOrderType): """ Take profit @@ -5913,7 +5918,7 @@ class AttachedOrderDetail: Order ID """ - attached_type_display: int + attached_type_display: Type[AttachedOrderType] """ Attached type display (1=take-profit, 2=stop-loss) """ @@ -5963,7 +5968,7 @@ class AttachedOrderDetail: Counter ID """ - trigger_status: int + trigger_status: Optional[Type[TriggerStatus]] """ Trigger status """ @@ -5973,7 +5978,7 @@ class AttachedOrderDetail: Executed amount """ - tag: int + tag: Type[OrderTag] """ Tag """ @@ -5983,7 +5988,7 @@ class AttachedOrderDetail: Submitted time """ - executed_price: Decimal + executed_price: Optional[Decimal] """ Executed price """ @@ -6587,7 +6592,7 @@ class OrderDetail: Order history details """ - charge_detail: OrderChargeDetail + charge_detail: Optional[OrderChargeDetail] """ Order charges """ diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index e545710123..d9ade78f24 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -204,6 +204,8 @@ pub(crate) enum OutsideRTH { #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] #[py(remote = "longbridge::trade::AttachedOrderType")] pub(crate) enum AttachedOrderType { + /// Unknown + Unknown, /// Take profit ProfitTaker, /// Stop loss @@ -219,8 +221,8 @@ pub(crate) enum AttachedOrderType { pub(crate) struct AttachedOrderDetail { /// Order ID order_id: String, - /// Attached type display (1=take-profit, 2=stop-loss) - attached_type_display: i32, + /// Attached order type + attached_type_display: AttachedOrderType, /// Trigger price #[py(opt)] trigger_price: Option, @@ -242,15 +244,17 @@ pub(crate) struct AttachedOrderDetail { /// Counter ID counter_id: String, /// Trigger status - trigger_status: i32, + #[py(opt)] + trigger_status: Option, /// Executed amount executed_amount: PyDecimal, /// Tag - tag: i32, + tag: OrderTag, /// Submitted time submitted_at: PyOffsetDateTimeWrapper, /// Executed price - executed_price: PyDecimal, + #[py(opt)] + executed_price: Option, /// Enable or disable outside regular trading hours (force only) #[py(opt)] force_only_rth: Option, @@ -689,7 +693,8 @@ pub(crate) struct OrderDetail { #[py(array)] history: Vec, /// Order charges - charge_detail: OrderChargeDetail, + #[py(opt)] + charge_detail: Option, /// Attached orders #[py(array)] attached_orders: Vec, diff --git a/rust/src/trade/requests/submit_order.rs b/rust/src/trade/requests/submit_order.rs index 6f3f04f889..cc82bd09d9 100644 --- a/rust/src/trade/requests/submit_order.rs +++ b/rust/src/trade/requests/submit_order.rs @@ -25,7 +25,10 @@ pub struct SubmitOrderOptions { trailing_amount: Option, #[serde(skip_serializing_if = "Option::is_none")] trailing_percent: Option, - #[serde(with = "serde_utils::date_opt")] + #[serde( + with = "serde_utils::date_opt", + skip_serializing_if = "Option::is_none" + )] expire_date: Option, #[serde(skip_serializing_if = "Option::is_none")] outside_rth: Option, diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index 30c60800d8..79d3166991 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -236,8 +236,10 @@ pub enum OutsideRTH { } /// Attached order type -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)] pub enum AttachedOrderType { + /// Unknown + Unknown, /// Take profit #[strum(serialize = "PROFIT_TAKER")] ProfitTaker, @@ -254,8 +256,8 @@ pub enum AttachedOrderType { pub struct AttachedOrderDetail { /// Attached order ID pub order_id: String, - /// Display type: 1=take-profit, 2=stop-loss - pub attached_type_display: i32, + /// Attached order type + pub attached_type_display: AttachedOrderType, /// Trigger price #[serde(with = "serde_utils::decimal_opt_empty_is_none")] pub trigger_price: Option, @@ -280,12 +282,13 @@ pub struct AttachedOrderDetail { pub time_in_force: TimeInForceType, /// Counter order ID pub counter_id: String, - /// Trigger status (0=not activated,1=monitoring,2=cancelled,4=triggered) - pub trigger_status: i32, + /// Trigger status + #[serde(with = "serde_utils::trigger_status")] + pub trigger_status: Option, /// Executed amount pub executed_amount: Decimal, /// Tag - pub tag: i32, + pub tag: OrderTag, /// Submitted time (unix timestamp seconds) #[serde( serialize_with = "time::serde::rfc3339::serialize", @@ -293,7 +296,8 @@ pub struct AttachedOrderDetail { )] pub submitted_at: OffsetDateTime, /// Executed price - pub executed_price: Decimal, + #[serde(with = "serde_utils::decimal_opt_empty_is_none")] + pub executed_price: Option, /// Force RTH only #[serde(with = "serde_utils::outside_rth")] pub force_only_rth: Option, @@ -619,7 +623,7 @@ pub struct OrderDetail { /// Order history details pub history: Vec, /// Order charges - pub charge_detail: OrderChargeDetail, + pub charge_detail: Option, /// Attached orders #[serde(default)] pub attached_orders: Vec, @@ -871,6 +875,8 @@ impl_serde_for_enum_string!( DeductionStatus, ChargeCategoryCode ); +impl_serde_for_enum_string!(AttachedOrderType); +impl_default_for_enum_string!(AttachedOrderType); impl_default_for_enum_string!( OrderType, OrderStatus, From cf0c3e28919dda9db77459351f2b2161fd77c232 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 08:20:38 +0800 Subject: [PATCH 3/9] docs(changelog): document attached order support and breaking changes --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec4b6bbd5..fbf1faba55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- **All languages:** attached order (take-profit / stop-loss) support for `submit_order` and `replace_order` + - New types: `AttachedOrderType` (`ProfitTaker` / `StopLoss` / `Bracket`), `AttachedOrderDetail`, `SubmitAttachedParams`, `ReplaceAttachedParams` + - `SubmitOrderOptions` / `ReplaceOrderOptions`: new `attached_params` field + - `GetTodayOrdersOptions`: new `is_attached` flag to filter by attached orders + - `Order` / `OrderDetail`: new `attached_orders: Vec` field + - New method `order_detail_attached(order_id)` — queries detail for an attached order by its own ID + - `order_detail` now accepts `GetOrderDetailOptions` (with optional `is_attached` flag) in addition to a plain order ID string + +### Breaking changes + +- **All languages:** `OrderDetail.charge_detail` is now `Option` (previously non-optional). Attached orders return `null` for this field; callers must handle the absent case. +- **C SDK:** `lb_order_detail_t` gains a new `has_charge_detail: bool` field before `charge_detail`. Existing binaries must be recompiled; code that reads `charge_detail` directly should check `has_charge_detail` first. + ## [4.4.0] - 2026-07-20 ### Added From bb63f6b60f85fab67682a5c61eabe844069e57d1 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:08:24 +0800 Subject: [PATCH 4/9] docs(trade): clarify is_attached semantics in GetTodayOrdersOptions and GetOrderDetailOptions is_attached=true indicates that the provided order_id is an attached sub-order ID, not a regular order ID. It does not filter results to show only attached orders. --- .../com/longbridge/trade/GetHistoryOrdersOptions.java | 1 + .../java/com/longbridge/trade/GetTodayOrdersOptions.java | 8 +++++--- nodejs/index.d.ts | 6 +++++- nodejs/src/trade/requests/get_today_orders.rs | 4 +++- python/pysrc/longbridge/openapi.pyi | 8 ++++---- rust/src/trade/requests/get_order_detail.rs | 6 +++++- rust/src/trade/requests/get_today_orders.rs | 6 +++++- 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java index dd73cd3d1a..99a280d8af 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java @@ -81,4 +81,5 @@ public GetHistoryOrdersOptions setEndAt(OffsetDateTime endAt) { this.endAt = endAt; return this; } + } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java index 37b104bc63..eae75af96c 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java @@ -70,7 +70,9 @@ public GetTodayOrdersOptions setOrderId(String orderId) { } /** - * Filters to include only attached orders. + * Indicate that the provided order ID is an attached order ID. + * When set, the server looks up the order whose attached sub-order matches + * the given order ID, rather than treating it as a regular order ID. * * @return this instance for chaining */ @@ -80,9 +82,9 @@ public GetTodayOrdersOptions setIsAttached() { } /** - * Returns the is-attached filter. + * Returns whether the order ID is treated as an attached order ID. * - * @return is-attached filter + * @return is-attached flag */ public Boolean getIsAttached() { return isAttached; diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index c66b0c7bf4..7bf53968dc 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -4339,7 +4339,11 @@ export interface GetTodayOrdersOptions { market?: Market /** Order id */ orderId?: string - /** Filter attached orders only */ + /** + * Indicate that the provided order_id is an attached order ID. When set, + * the server looks up the order whose attached sub-order matches order_id, + * rather than treating it as a regular order ID. + */ isAttached?: boolean } diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs index b5d5e2b9be..592a89f63d 100644 --- a/nodejs/src/trade/requests/get_today_orders.rs +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -16,7 +16,9 @@ pub struct GetTodayOrdersOptions { pub market: Option, /// Order id pub order_id: Option, - /// Filter attached orders only + /// Indicate that the provided order_id is an attached order ID. When set, + /// the server looks up the order whose attached sub-order matches order_id, + /// rather than treating it as a regular order ID. pub is_attached: Option, } diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 73f93cd69b..776c672c4e 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -7182,7 +7182,7 @@ class TradeContext: side: Filter by order side market: Filter by market type order_id: Filter by order id - is_attached: Include attached orders + is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID Returns: Order list @@ -7500,7 +7500,7 @@ class TradeContext: Args: order_id: Order id - is_attached: Include attached orders + is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID Returns: Order detail @@ -7906,7 +7906,7 @@ class AsyncTradeContext: side: Filter by order side. market: Filter by market type. order_id: Filter by order ID. - is_attached: Include attached orders. + is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID. Examples: :: @@ -8255,7 +8255,7 @@ class AsyncTradeContext: Args: order_id: Order ID. - is_attached: Include attached orders. + is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID. Examples: :: diff --git a/rust/src/trade/requests/get_order_detail.rs b/rust/src/trade/requests/get_order_detail.rs index 23c472df0d..b68329d649 100644 --- a/rust/src/trade/requests/get_order_detail.rs +++ b/rust/src/trade/requests/get_order_detail.rs @@ -16,7 +16,11 @@ impl GetOrderDetailOptions { is_attached: None, } } - /// Query by attached order + /// Indicate that the provided `order_id` is an attached order ID. + /// + /// When set, the server looks up the order detail whose attached sub-order + /// matches the given `order_id`, rather than treating `order_id` as a + /// regular order ID. pub fn is_attached(self) -> Self { Self { is_attached: Some(true), diff --git a/rust/src/trade/requests/get_today_orders.rs b/rust/src/trade/requests/get_today_orders.rs index 5b67cca399..8764101f38 100644 --- a/rust/src/trade/requests/get_today_orders.rs +++ b/rust/src/trade/requests/get_today_orders.rs @@ -79,7 +79,11 @@ impl GetTodayOrdersOptions { } } - /// Filter by attached order + /// Indicate that the provided `order_id` is an attached order ID. + /// + /// When set, the server looks up the order whose attached sub-order matches + /// the given `order_id`, rather than treating `order_id` as a regular order + /// ID. This does **not** filter results to show only attached orders. pub fn is_attached(self) -> Self { Self { is_attached: Some(true), From 18defbdc3b3e5ea0cee73582507699381fad1c81 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:12:32 +0800 Subject: [PATCH 5/9] docs(trade): correct is_attached semantics based on actual API behavior is_attached=true only takes effect when combined with order_id. It tells the server to treat order_id as an attached sub-order ID for lookup. Without order_id it has no effect and does NOT filter results to show only attached orders. --- CHANGELOG.md | 2 +- .../com/longbridge/trade/GetTodayOrdersOptions.java | 7 ++++--- nodejs/src/trade/requests/get_today_orders.rs | 6 +++--- python/pysrc/longbridge/openapi.pyi | 8 ++++---- rust/src/trade/requests/get_order_detail.rs | 7 +++---- rust/src/trade/requests/get_today_orders.rs | 10 +++++----- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbf1faba55..ed235023e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **All languages:** attached order (take-profit / stop-loss) support for `submit_order` and `replace_order` - New types: `AttachedOrderType` (`ProfitTaker` / `StopLoss` / `Bracket`), `AttachedOrderDetail`, `SubmitAttachedParams`, `ReplaceAttachedParams` - `SubmitOrderOptions` / `ReplaceOrderOptions`: new `attached_params` field - - `GetTodayOrdersOptions`: new `is_attached` flag to filter by attached orders + - `GetTodayOrdersOptions`: new `is_attached` flag — when combined with `order_id`, treats `order_id` as an attached sub-order ID for lookup (has no effect without `order_id`) - `Order` / `OrderDetail`: new `attached_orders: Vec` field - New method `order_detail_attached(order_id)` — queries detail for an attached order by its own ID - `order_detail` now accepts `GetOrderDetailOptions` (with optional `is_attached` flag) in addition to a plain order ID string diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java index eae75af96c..40c5ae1e11 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java @@ -70,9 +70,10 @@ public GetTodayOrdersOptions setOrderId(String orderId) { } /** - * Indicate that the provided order ID is an attached order ID. - * When set, the server looks up the order whose attached sub-order matches - * the given order ID, rather than treating it as a regular order ID. + * When set together with order ID, indicates that the order ID is an + * attached sub-order ID. The server looks up using the attached order ID + * instead of treating it as a regular order ID. Has no effect without + * order ID. * * @return this instance for chaining */ diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs index 592a89f63d..88dae3e372 100644 --- a/nodejs/src/trade/requests/get_today_orders.rs +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -16,9 +16,9 @@ pub struct GetTodayOrdersOptions { pub market: Option, /// Order id pub order_id: Option, - /// Indicate that the provided order_id is an attached order ID. When set, - /// the server looks up the order whose attached sub-order matches order_id, - /// rather than treating it as a regular order ID. + /// When set together with order_id, indicates that order_id is an attached + /// sub-order ID. The server looks up using the attached order ID instead of + /// treating it as a regular order ID. Has no effect without order_id. pub is_attached: Option, } diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 776c672c4e..ee71b43420 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -7182,7 +7182,7 @@ class TradeContext: side: Filter by order side market: Filter by market type order_id: Filter by order id - is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID + is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id Returns: Order list @@ -7500,7 +7500,7 @@ class TradeContext: Args: order_id: Order id - is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID + is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id Returns: Order detail @@ -7906,7 +7906,7 @@ class AsyncTradeContext: side: Filter by order side. market: Filter by market type. order_id: Filter by order ID. - is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID. + is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id. Examples: :: @@ -8255,7 +8255,7 @@ class AsyncTradeContext: Args: order_id: Order ID. - is_attached: Indicate that the provided order_id is an attached order ID. When set, the server looks up the order whose attached sub-order matches order_id, rather than treating it as a regular order ID. + is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id. Examples: :: diff --git a/rust/src/trade/requests/get_order_detail.rs b/rust/src/trade/requests/get_order_detail.rs index b68329d649..965fc96dc1 100644 --- a/rust/src/trade/requests/get_order_detail.rs +++ b/rust/src/trade/requests/get_order_detail.rs @@ -16,11 +16,10 @@ impl GetOrderDetailOptions { is_attached: None, } } - /// Indicate that the provided `order_id` is an attached order ID. + /// Indicate that the provided `order_id` is an attached sub-order ID. /// - /// When set, the server looks up the order detail whose attached sub-order - /// matches the given `order_id`, rather than treating `order_id` as a - /// regular order ID. + /// When set, the server looks up the order detail using the attached order + /// ID instead of treating `order_id` as a regular order ID. pub fn is_attached(self) -> Self { Self { is_attached: Some(true), diff --git a/rust/src/trade/requests/get_today_orders.rs b/rust/src/trade/requests/get_today_orders.rs index 8764101f38..62dcd6b6fd 100644 --- a/rust/src/trade/requests/get_today_orders.rs +++ b/rust/src/trade/requests/get_today_orders.rs @@ -79,11 +79,11 @@ impl GetTodayOrdersOptions { } } - /// Indicate that the provided `order_id` is an attached order ID. - /// - /// When set, the server looks up the order whose attached sub-order matches - /// the given `order_id`, rather than treating `order_id` as a regular order - /// ID. This does **not** filter results to show only attached orders. + /// When set together with [`order_id`], indicates that `order_id` is an + /// attached sub-order ID. The server will look up using the attached order + /// ID instead of treating it as a regular order ID. Has no effect without + /// [`order_id`] and does **not** filter results to show only attached + /// orders. pub fn is_attached(self) -> Self { Self { is_attached: Some(true), From 10c4b6b62bd9b5dac26f936d0cd6f12c39a244cf Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:21:05 +0800 Subject: [PATCH 6/9] docs(trade): clarify is_attached returns the sub-order itself, not parent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on actual API testing: today_orders(order_id=attached_id, is_attached=True) returns the attached sub-order as an Order entry with its own order_id, side, order_type etc — not the parent order. --- .../java/com/longbridge/trade/GetTodayOrdersOptions.java | 5 ++--- nodejs/src/trade/requests/get_today_orders.rs | 4 ++-- rust/src/trade/requests/get_today_orders.rs | 7 +++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java index 40c5ae1e11..edeab42ee0 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java @@ -71,9 +71,8 @@ public GetTodayOrdersOptions setOrderId(String orderId) { /** * When set together with order ID, indicates that the order ID is an - * attached sub-order ID. The server looks up using the attached order ID - * instead of treating it as a regular order ID. Has no effect without - * order ID. + * attached sub-order ID. The server returns the attached sub-order itself + * as an Order entry (not the parent order). Has no effect without order ID. * * @return this instance for chaining */ diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs index 88dae3e372..418cf2d96c 100644 --- a/nodejs/src/trade/requests/get_today_orders.rs +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -17,8 +17,8 @@ pub struct GetTodayOrdersOptions { /// Order id pub order_id: Option, /// When set together with order_id, indicates that order_id is an attached - /// sub-order ID. The server looks up using the attached order ID instead of - /// treating it as a regular order ID. Has no effect without order_id. + /// sub-order ID. The server returns the attached sub-order itself as an + /// Order entry (not the parent order). Has no effect without order_id. pub is_attached: Option, } diff --git a/rust/src/trade/requests/get_today_orders.rs b/rust/src/trade/requests/get_today_orders.rs index 62dcd6b6fd..c899c6a9f2 100644 --- a/rust/src/trade/requests/get_today_orders.rs +++ b/rust/src/trade/requests/get_today_orders.rs @@ -80,10 +80,9 @@ impl GetTodayOrdersOptions { } /// When set together with [`order_id`], indicates that `order_id` is an - /// attached sub-order ID. The server will look up using the attached order - /// ID instead of treating it as a regular order ID. Has no effect without - /// [`order_id`] and does **not** filter results to show only attached - /// orders. + /// attached sub-order ID. The server returns the attached sub-order itself + /// as an [`Order`] entry (not the parent order). Has no effect without + /// [`order_id`]. pub fn is_attached(self) -> Self { Self { is_attached: Some(true), From 2e2743e0fdebf109ba8dbebaa9c69f20acae019c Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:33:20 +0800 Subject: [PATCH 7/9] fix(python): add from_py_object to MacroeconomicCountry pyclass --- python/src/fundamental/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/src/fundamental/types.rs b/python/src/fundamental/types.rs index 30d9a495ed..d2bef01856 100644 --- a/python/src/fundamental/types.rs +++ b/python/src/fundamental/types.rs @@ -1988,8 +1988,8 @@ impl From for MultiLanguageText { } /// Country code for filtering macroeconomic indicators -#[pyclass] -#[derive(Debug, Copy, Clone)] +#[pyclass(eq, eq_int, from_py_object)] +#[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum MacroeconomicCountry { HongKong, China, From eac5f478398d831a7c688ea4037e3f650f50fec5 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 21 Jul 2026 09:49:50 +0800 Subject: [PATCH 8/9] chore(build): regenerate nodejs bindings and rustfmt after rebase Rebasing feat/attached-orders onto main required merging attached-order changes with the US market APIs / client_request_id features; the naive git merge left nodejs/index.d.ts with duplicate US-market method declarations (no conflict markers, just misaligned content). Rebuilding with napi and rustfmt brings the generated files back in sync with the Rust source. --- c/src/trade_context/types.rs | 6 +- nodejs/index.d.ts | 1125 ++++++++++++++++++---------------- nodejs/index.js | 253 ++------ 3 files changed, 630 insertions(+), 754 deletions(-) diff --git a/c/src/trade_context/types.rs b/c/src/trade_context/types.rs index 377ca7d564..6bcecd4ce7 100644 --- a/c/src/trade_context/types.rs +++ b/c/src/trade_context/types.rs @@ -3,9 +3,9 @@ use std::os::raw::c_char; use longbridge::{ Market, trade::{ - AccountBalance, AllExecutionsResponse, AttachedOrderDetail, AttachedOrderType, - BalanceType, CashFlow, CashFlowDirection, CashInfo, EstimateMaxPurchaseQuantityResponse, - Execution, FrozenTransactionFee, FundPosition, FundPositionChannel, FundPositionsResponse, + AccountBalance, AllExecutionsResponse, AttachedOrderDetail, AttachedOrderType, BalanceType, + CashFlow, CashFlowDirection, CashInfo, EstimateMaxPurchaseQuantityResponse, Execution, + FrozenTransactionFee, FundPosition, FundPositionChannel, FundPositionsResponse, MarginRatio, Order, OrderChargeDetail, OrderChargeFee, OrderChargeItem, OrderDetail, OrderHistoryDetail, OrderSide, OrderStatus, OrderTag, OrderType, PushOrderChanged, StockPosition, StockPositionChannel, StockPositionsResponse, SubmitOrderResponse, diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index 7bf53968dc..2c9af8f876 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -680,23 +680,23 @@ export declare class FundamentalContext { macroeconomicIndicators(country?: MacroeconomicCountry | undefined | null, keyword?: string | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise /** Get historical data for a macroeconomic indicator */ macroeconomic(indicatorCode: string, startDate?: string | undefined | null, endDate?: string | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise - /** Get US company overview. US token required. counterID format: "ST/US/AAPL" */ + /** Get US company overview. US token required. */ usCompanyOverview(symbol: string): Promise - /** Get US valuation snapshot (PE/PB/PS). US token required. */ + /** Get US valuation overview. US token required. */ usValuationOverview(symbol: string): Promise - /** Get US financial overview (revenue/net income/EPS). Returns JSON string. US token required. */ + /** Get US financial overview. US token required. */ usFinancialOverview(symbol: string, report: string): Promise - /** Get US financial statement. kind: "IS"|"BS"|"CF". report: "q1"|"qf"|"saf"|"3q"|"af". US token required. */ + /** Get US financial statement v3. kind: "IS"/"BS"/"CF". US token required. */ usFinancialStatement(symbol: string, kind: string, report: string): Promise - /** Get US key financial metrics. report: "q1"|"qf"|"saf"|"3q"|"af". US token required. */ + /** Get US key financial metrics. US token required. */ usKeyFinancialMetrics(symbol: string, report: string): Promise - /** Get US analyst consensus estimates. report: "q1"|"qf"|"saf"|"3q"|"af". US token required. */ + /** Get US analyst consensus estimates. US token required. */ usAnalystConsensus(symbol: string, report: string): Promise /** Get US ETF dividend history. US token required. */ usEtfDividendInfo(symbol: string): Promise - /** Get US company historical dividends. US token required. */ + /** Get US company dividends. US token required. */ usCompanyDividends(symbol: string): Promise - /** Get US ETF document list. size=null returns all. US token required. */ + /** Get US ETF document list. size=None returns all. US token required. */ usEtfFiles(symbol: string, size?: number | undefined | null): Promise } @@ -2110,7 +2110,7 @@ export declare class QuoteContext { optionVolume(symbol: string): Promise /** Get daily historical option volume */ optionVolumeDaily(symbol: string, timestamp: number, count: number): Promise - /** Get US cryptocurrency market overview. counterID format: "CY/US/BTC". US token required. */ + /** Get US cryptocurrency market overview. US token required. */ usCryptoOverview(symbol: string): Promise } @@ -2864,6 +2864,17 @@ export declare class TradeContext { * ``` */ orderDetail(orderId: string): Promise + /** + * Query US order list. Returns JSON string. US token required. + * symbol: user-facing symbol e.g. "AAPL.US"; action: 0=all/1=buy/2=sell. + */ + usQueryOrders(symbol: string | undefined | null, action: number, startAt: number, endAt: number, queryType: number, page: number, limit: number): Promise + /** Get US order detail. US token required. */ + usOrderDetail(orderId: string): Promise + /** Get US account asset overview. US token required. */ + usAssetOverview(): Promise + /** Get US realized P&L. US token required. */ + usRealizedPl(currency: string, category?: string | undefined | null): Promise orderDetailAttached(orderId: string): Promise /** * Estimating the maximum purchase quantity for Hong Kong and US stocks, @@ -2885,21 +2896,6 @@ export declare class TradeContext { * ``` */ estimateMaxPurchaseQuantity(opts: EstimateMaxPurchaseQuantityOptions): Promise - /** Query US order list (paginated). Returns JSON string. US token required. */ - /** - * Query US order list. Returns JSON string with shape `{orders: USOrder[], total_count: number}`. - * symbol: user-facing symbol e.g. "AAPL.US" (optional). - * action: 0=all, 1=buy, 2=sell. - * queryType: 0=all (incl. Rejected), 1=pending, 2=history (filled only). - * US token required. - */ - usQueryOrders(symbol?: string | null, action?: number, startAt?: number, endAt?: number, queryType?: number, page?: number, limit?: number): Promise - /** Get US order detail. isAttached=true includes take-profit/stop-loss sub-orders. Returns JSON string. US token required. */ - usOrderDetail(orderId: string): Promise - /** Get US account asset overview (stocks/options/crypto/buy power). US token required. */ - usAssetOverview(): Promise - /** Get US realized P&L. category: "ALL"|"STOCK"|"OPTION"|"CRYPTO". US token required. */ - usRealizedPl(currency: string, category?: string | undefined | null): Promise } /** The information of trading session */ @@ -4088,6 +4084,19 @@ export interface ExtraConfigParams { enablePrintQuotePackages?: boolean /** Set the path of the log files (Default: `no logs`) */ logPath?: string + /** + * Enable paper trading mode (default: `false`). + * + * When `true`, all API calls target the paper trading (simulation) + * environment. The server validates the token: if it belongs to a + * real-money account the server returns an error. + * + * When `false` (the default) the server imposes no restrictions — both + * paper trading and real-money accounts are accepted. + * + * Paper trading users should set this to `true` as a safety guard. + */ + enablePapertrading?: boolean } /** Filter warrant expiry date type */ @@ -4340,9 +4349,9 @@ export interface GetTodayOrdersOptions { /** Order id */ orderId?: string /** - * Indicate that the provided order_id is an attached order ID. When set, - * the server looks up the order whose attached sub-order matches order_id, - * rather than treating it as a regular order ID. + * When set together with order_id, indicates that order_id is an attached + * sub-order ID. The server returns the attached sub-order itself as an + * Order entry (not the parent order). Has no effect without order_id. */ isAttached?: boolean } @@ -4894,19 +4903,7 @@ export declare const enum OrderTag { /** Long term Order */ LongTerm = 2, /** Grey Order */ - Grey = 3, - /** Force Selling */ - MarginCall = 4, - /** OTC */ - Offline = 5, - /** Option Exercise Long */ - Creditor = 6, - /** Option Exercise Short */ - Debtor = 7, - /** Wavier Of Option Exercise */ - NonExercise = 8, - /** Trade Allocation */ - AllocatedSub = 9 + Grey = 3 } export declare const enum OrderType { @@ -6040,275 +6037,308 @@ export interface UpdateWatchlistGroup { mode: SecuritiesUpdateMode } -/** One security's valuation comparison item. */ -export interface ValuationComparisonItem { - /** Symbol (e.g. `"AAPL.US"`) */ +/** AI chat context embedded in USAnalystConsensus. */ +export interface UsaiChatData { + agentId: string + handoffAgentId: string symbol: string - /** Security name */ - name: string - /** Currency */ + text: string + chatType: string + workflowType: string +} + +/** US analyst consensus estimates and AI analysis. */ +export interface UsAnalystConsensus { + aiSummary: string + aichatData: UsaiChatData currency: string - /** Market capitalisation */ - marketValue: string - /** Latest closing price */ - priceClose: string - /** P/E ratio */ - pe: string - /** P/B ratio */ - pb: string - /** P/S ratio */ - ps: string - /** Return on equity */ - roe: string - /** Earnings per share */ - eps: string - /** Book value per share */ - bps: string - /** Dividends per share */ - dps: string - /** Dividend yield */ - divYld: string - /** Total assets */ - assets: string - /** Historical valuation points */ - history: Array + report: string + list: Array + optReports: Array + h5Data: any } -/** Valuation comparison response. */ -export interface ValuationComparisonResponse { - /** Valuation comparison items */ - list: Array +/** US account asset snapshot */ +export interface UsAssetOverview { + accountType: string + assetTimestamp: number + cashBuyPower: string + overnightBuyPower: string + currency: string + cashList: Array + stockList: Array + optionList: Array + cryptoList: Array + multiLeg: any } -/** Valuation metrics response */ -export interface ValuationData { - /** Valuation metrics */ - metrics: ValuationMetricsData +/** One bracket/conditional sub-order attached to a main order. */ +export interface UsAttachedOrder { + attachedTypeDisplay: number + executedQty: string + quantity: string + status: string + triggerPrice: string + orderId: string + gtd: string + timeInForce: number + tag: number + activateOrderType: string + activateRth: number + submitPrice: string + symbol: string + withdrawn: boolean } -/** Distribution statistics for one valuation metric */ -export interface ValuationDist { - /** Minimum value */ - low?: string - /** Maximum value */ - high?: string - /** Median value */ - median?: string - /** Current value */ - value?: string - /** Percentile ranking */ - ranking?: string - /** Ordinal rank index */ - rankIndex: string - /** Total securities in industry */ - rankTotal: string +/** Action-button state for an order. */ +export interface UsButtonControl { + withdraw: number + replace: number + exceptionable: Array } -/** Historical valuation container */ -export interface ValuationHistoryData { - /** Historical metrics */ - metrics: ValuationHistoryMetrics +/** One cash currency entry in USAssetOverview */ +export interface UsCashEntry { + currency: string + frozenBuyCash: string + outstanding: string + settledCash: string + totalAmount: string + totalCash: string } -/** Historical data for one valuation metric */ -export interface ValuationHistoryMetric { - /** Description */ - desc: string - /** High */ - high?: string - /** Low */ - low?: string - /** Median */ - median?: string - /** Data points */ - list: Array +/** Fee breakdown for an order. */ +export interface UsChargeDetail { + currency: string + totalAmount: string + items: Array } -/** Historical metrics container */ -export interface ValuationHistoryMetrics { - /** PE history */ - pe?: ValuationHistoryMetric - /** PB history */ - pb?: ValuationHistoryMetric - /** PS history */ - ps?: ValuationHistoryMetric +/** One fee category within USChargeDetail. */ +export interface UsChargeItem { + code: number + name: string + fees: Array } -/** One historical valuation data point. */ -export interface ValuationHistoryPoint { - /** Date (RFC 3339) */ - date: string - /** P/E ratio */ - pe: string - /** P/B ratio */ - pb: string - /** P/S ratio */ - ps: string +/** US company dividends */ +export interface UsCompanyDividends { + recentDividends: UsRecentDividend + dividendHistory: Array + payoutRatios: Array + dividendPayoutHistory: Array } -/** Historical valuation response */ -export interface ValuationHistoryResponse { - /** Historical valuation data */ - history: ValuationHistoryData +/** US company overview */ +export interface UsCompanyOverview { + intro: string + marketCap: string + ccySymbol: string + topRankTags: Array + detailUrl: string + shareList: Array } -/** Historical time-series for one valuation metric */ -export interface ValuationMetricData { - /** Description */ - desc: string - /** Historical high */ - high?: string - /** Historical low */ - low?: string - /** Historical median */ - median?: string - /** Data points */ - list: Array +/** Actual vs estimated value for one consensus metric. */ +export interface UsConsensusEstimate { + actual: string + estimate: string } -/** Valuation metrics container */ -export interface ValuationMetricsData { - /** PE ratio history */ - pe?: ValuationMetricData - /** PB ratio history */ - pb?: ValuationMetricData - /** PS ratio history */ - ps?: ValuationMetricData - /** Dividend yield history */ - dvdYld?: ValuationMetricData +/** One fiscal-year entry in USAnalystConsensus.list. */ +export interface UsConsensusItem { + ebit: UsConsensusEstimate + eps: UsConsensusEstimate + fiscalYear: number + reportTxt: string + revenue: UsConsensusEstimate } -/** One valuation data point */ -export interface ValuationPoint { - /** Unix timestamp (seconds) */ - timestamp: number - /** Metric value */ - value?: string +/** One cryptocurrency holding in USAssetOverview */ +export interface UsCryptoEntry { + assetType: string + averageCost: string + symbol: string + currency: string + industryName: string } -/** Warrant sort by */ -export declare const enum WarrantSortBy { - /** Last done */ - LastDone = 0, - /** Change rate */ - ChangeRate = 1, - /** Change value */ - ChangeValue = 2, - /** Volume */ - Volume = 3, - /** Turnover */ - Turnover = 4, - /** Expiry date */ - ExpiryDate = 5, - /** Strike price */ - StrikePrice = 6, - /** Upper strike price */ - UpperStrikePrice = 7, - /** Lower strike price */ - LowerStrikePrice = 8, - /** Outstanding quantity */ - OutstandingQuantity = 9, - /** Outstanding ratio */ - OutstandingRatio = 10, - /** Premium */ - Premium = 11, - /** In/out of the bound */ - ItmOtm = 12, - /** Implied volatility */ - ImpliedVolatility = 13, - /** Greek value delta */ - Delta = 14, - /** Call price */ - CallPrice = 15, - /** Price interval from the call price */ - ToCallPrice = 16, - /** Effective leverage */ - EffectiveLeverage = 17, - /** Leverage ratio */ - LeverageRatio = 18, - /** Conversion ratio */ - ConversionRatio = 19, - /** Breakeven point */ - BalancePoint = 20, - /** Status */ - Status = 21 +/** US cryptocurrency market overview */ +export interface UsCryptoOverview { + name: string + ticker: string + currency: string + allTimeHigh: string + allTimeHighDate: string + allTimeLow: string + allTimeLowDate: string + ipoDate: string + issuePrice: string + shares: string + officialWebAddress: string + /** User-facing symbol (e.g. "BTCUSD.BKKT"), converted from counter_id */ + symbol: string + baseAsset: string + logo: string + wikiUrl: string + /** Profile serialized as JSON string */ + profile: string } -/** Warrant status */ -export declare const enum WarrantStatus { - /** Suspend */ - Suspend = 0, - /** Prepare List */ - PrepareList = 1, - /** Normal */ - Normal = 2 +/** One fiscal-year row in dividend_history or payout_ratios. */ +export interface UsDividendHistoryItem { + fiscalYear: string + fiscalYearRange: string + totalShareholderYield: string + dividend: string + dividendYield: string + dividendGrowthRate: string + dividendPayoutRatio: string + dividendToCashflowRatio: string + netBuyback: string + netBuybackYield: string + netBuybackGrowthRate: string + netBuybackPayoutRatio: string + netBuybackToCashflowRatio: string + currency: string } -/** Warrant type */ -export declare const enum WarrantType { - /** Unknown */ - Unknown = 0, - /** Call */ - Call = 1, - /** Put */ - Put = 2, - /** Bull */ - Bull = 3, - /** Bear */ - Bear = 4, - /** Inline */ - Inline = 5 +/** US dividend item */ +export interface UsDividendItem { + dividend: string + dividendType: string + exDate: string + paymentDate: string + recordDate: string } -export interface USOrderHistory { - execType: number - status: string - price: string - qty: string - time: string - msg: string - isManually: boolean - oppPartyId: string - trdMatchId: string - operator: string - opEntrustWay: string - cxlRejResponseTo: number - withdrawalReason: string - oppName: string - execId: string +/** One actual dividend payment event. */ +export interface UsDividendPayoutRecord { + dividend: string + dividendType: string + currency: string + exDate: string + paymentDate: string + recordDate: string + title: string + startTimeUnix: string } -export interface USButtonControl { - withdraw: number - replace: number - exceptionable: Array + +/** US ETF dividend info */ +export interface UsetfDividendInfo { + dividendTtm: string + dividendYieldTtm: string + dividendFrequency: string + currency: string + fiscalYearInfo: Array } -export interface USChargeItem { - code: number + +/** US ETF file */ +export interface UsetfFile { + fileName: string + filePath: string + updateDate: string + code: string + format: string +} + +/** US ETF files response */ +export interface UsetfFilesResponse { + files: Array +} + +/** One balance-sheet entry in USFinancialOverview. */ +export interface UsFinancialBsItem { + debtAssetsRatio: string + totalAssets: string + totalLiabilities: string + report: UsReportPeriod +} + +/** One cash-flow entry in USFinancialOverview. */ +export interface UsFinancialCfItem { + operating: string + investing: string + financing: string + report: UsReportPeriod +} + +/** One income-statement entry in USFinancialOverview. */ +export interface UsFinancialIsItem { + revenue: string + netIncome: string + netMargin: string + report: UsReportPeriod +} + +/** US financial overview — income statement, balance sheet, and cash flow. */ +export interface UsFinancialOverview { + ccySymbol: string + reportType: string + isList: Array + bsList: Array + cfList: Array +} + +/** US financial statement */ +export interface UsFinancialStatement { + currency: string + report: string + list: Array + emptyFields: Array +} + +/** One financial field within a USFinancialStatementPeriod. */ +export interface UsFinancialStatementField { + displayOrder: number + field: string + id: string + level: number name: string - fees: Array + value: string + valueType: string + yoy: string } -export interface USChargeDetail { + +/** One reporting period in USFinancialStatement. */ +export interface UsFinancialStatementPeriod { + ffPeriod: string + ffYear: number + fields: Array + fpEnd: string + reportTxt: string + rptDate: string +} + +/** Per-fiscal-year dividend row for a US ETF. */ +export interface UsFiscalYearDividend { + dividend: string + dividendYield: string + fiscalYear: string currency: string - totalAmount: string - items: Array + fiscalYearRange: string } -export interface USAttachedOrder { - attachedTypeDisplay: number - executedQty: string - quantity: string - status: string - triggerPrice: string - orderId: string - gtd: string - timeInForce: number - tag: number - activateOrderType: string - activateRth: number - submitPrice: string - symbol: string - withdrawn: boolean + +/** US key financial metrics — ratios and indicators per reporting period. */ +export interface UsKeyFinancialMetrics { + currency: string + report: string + emptyFields: Array + list: Array +} + +/** One period entry in USKeyFinancialMetrics. */ +export interface UsKeyMetricItem { + ffPeriod: string + ffYear: number + fpEnd: string + reportTxt: string + rptDate: string + fields: Array } -export interface USOrderDetail { + +/** Full typed order object within USOrderDetailResponse. */ +export interface UsOrderDetail { id: string aaid: string accountChannel: string @@ -6383,93 +6413,40 @@ export interface USOrderDetail { strikePrice: string contractSize: string monitorPrice: string - buttonControl: USButtonControl - chargeDetail: USChargeDetail | null - attachedOrders: Array - orderHistories: Array -} -export interface USOrderDetailResponse { - order: USOrderDetail | null - currentAttachedOrder: USOrderDetail | null - currentMillisecond: string + buttonControl: UsButtonControl + chargeDetail?: UsChargeDetail + attachedOrders: Array + orderHistories: Array } -export interface USReportPeriod { - startDate: string - endDate: string - reportTxt: string -} -export interface USFinancialISItem { - revenue: string - netIncome: string - netMargin: string - report: USReportPeriod -} -export interface USFinancialBSItem { - debtAssetsRatio: string - totalAssets: string - totalLiabilities: string - report: USReportPeriod -} -export interface USFinancialCFItem { - operating: string - investing: string - financing: string - report: USReportPeriod -} -export interface USFinancialOverview { - ccySymbol: string - reportType: string - isList: Array - bsList: Array - cfList: Array -} -export interface USKeyMetricItem { - ffPeriod: string - ffYear: number - fpEnd: string - reportTxt: string - rptDate: string - fields: Array -} -export interface USKeyFinancialMetrics { - currency: string - report: string - emptyFields: Array - list: Array -} -export interface USAIChatData { - agentId: string - handoffAgentId: string - symbol: string - text: string - chatType: string - workflowType: string -} -export interface USConsensusEstimate { - actual: string - estimate: string -} -export interface USConsensusItem { - ebit: USConsensusEstimate - eps: USConsensusEstimate - fiscalYear: number - reportTxt: string - revenue: USConsensusEstimate -} -export interface USAnalystConsensus { - aiSummary: string - aichatData: USAIChatData - currency: string - report: string - list: Array - optReports: Array - h5Data: unknown +/** Response for us_order_detail. */ +export interface UsOrderDetailResponse { + order?: UsOrderDetail + currentAttachedOrder?: UsOrderDetail + currentMillisecond: string } -// ── US-market types ──────────────────────────────────────────────────────── +/** One order state-transition entry within USOrderDetail. */ +export interface UsOrderHistory { + execType: number + status: string + price: string + qty: string + time: string + msg: string + isManually: boolean + oppPartyId: string + trdMatchId: string + operator: string + opEntrustWay: string + cxlRejResponseTo: number + withdrawalReason: string + oppName: string + execId: string +} -export interface USRankTag { +/** Industry rank tag */ +export interface UsRankTag { key: string location: number title: string @@ -6478,178 +6455,49 @@ export interface USRankTag { highlightText: string } -export interface USSharelistItem { - chg: string - id: string - name: string +/** Realized P&L response for a US account */ +export interface UsRealizedPl { + realizedPlList: Array } -export interface USCompanyOverview { - intro: string - marketCap: string - ccySymbol: string - topRankTags: Array - detailUrl: string - shareList: Array +/** One asset-category entry in USRealizedPL */ +export interface UsRealizedPlEntry { + category: number + currency: string + metrics: Array } -export interface USValuationMetric { - circle: string - part: string - metric: string - desc: string - industryMedian: string +/** One time-period metric in USRealizedPLEntry */ +export interface UsRealizedPlMetric { + amount: string + period: number + rate: string } -export interface USValuationOverview { - metrics: Record - indicator: string - range: number - date: string - ccySymbol: string - aichatData: USAIChatData - aiSummary: string +/** TTM dividend summary within USCompanyDividends. */ +export interface UsRecentDividend { + dividendTtm: string + dividendYieldTtm: string + payouts: string + currency: string } -export interface USFinancialStatementField { - displayOrder: number - field: string +/** One reporting-period window shared by IS/BS/CF entries. */ +export interface UsReportPeriod { + startDate: string + endDate: string + reportTxt: string +} + +/** One entry in USCompanyOverview.share_list. */ +export interface UsSharelistItem { + chg: string id: string - level: number name: string - value: string - valueType: string - yoy: string -} -export interface USFinancialStatementPeriod { - ffPeriod: string - ffYear: number - fields: Array - fpEnd: string - reportTxt: string - rptDate: string -} -export interface USFinancialStatement { - currency: string - report: string - list: Array - emptyFields: Array -} - -export interface USFiscalYearDividend { - dividend: string - dividendYield: string - fiscalYear: string - currency: string - fiscalYearRange: string -} - -export interface USETFDividendInfo { - dividendTtm: string - dividendYieldTtm: string - dividendFrequency: string - currency: string - fiscalYearInfo: Array -} - -export interface USDividendItem { - dividend: string - dividendType: string - exDate: string - paymentDate: string - recordDate: string -} - -export interface USRecentDividend { - dividendTtm: string - dividendYieldTtm: string - payouts: string - currency: string -} -export interface USDividendHistoryItem { - fiscalYear: string - fiscalYearRange: string - totalShareholderYield: string - dividend: string - dividendYield: string - dividendGrowthRate: string - dividendPayoutRatio: string - dividendToCashflowRatio: string - netBuyback: string - netBuybackYield: string - netBuybackGrowthRate: string - netBuybackPayoutRatio: string - netBuybackToCashflowRatio: string - currency: string -} -export interface USDividendPayoutRecord { - dividend: string - dividendType: string - currency: string - exDate: string - paymentDate: string - recordDate: string - title: string - startTimeUnix: string -} -export interface USCompanyDividends { - recentDividends: USRecentDividend - dividendHistory: Array - payoutRatios: Array - dividendPayoutHistory: Array -} - -export interface USETFFile { - fileName: string - filePath: string - updateDate: string - code: string - format: string -} - -export interface USETFFilesResponse { - files: Array -} - -export interface USCryptoOverview { - symbol: string - name: string - ticker: string - baseAsset: string - currency: string - allTimeHigh: string - allTimeHighDate: string - allTimeLow: string - allTimeLowDate: string - ipoDate: string - issuePrice: string - shares: string - officialWebAddress: string - logo: string - wikiUrl: string - /** Profile / description as a JSON string */ - profile: string -} - -export interface USCashEntry { - currency: string - frozenBuyCash: string - outstanding: string - settledCash: string - totalAmount: string - totalCash: string -} - -export interface USCryptoEntry { - assetType: string - averageCost: string - /** User-facing symbol, e.g. "BTCUSD.BKKT" */ - symbol: string - currency: string - industryName: string } -export interface USStockEntry { +/** One stock/equity position in USAssetOverview */ +export interface UsStockEntry { symbol: string fullSymbol: string assetType: string @@ -6674,32 +6522,243 @@ export interface USStockEntry { industryCounterId: string industryName: string } -export interface USAssetOverview { - accountType: string - /** Unix timestamp (seconds) */ - assetTimestamp: number - cashBuyPower: string - overnightBuyPower: string - currency: string - cashList: Array - stockList: Array - optionList: Array - cryptoList: Array - multiLeg: unknown + +/** One valuation metric entry in USValuationOverview.metrics */ +export interface UsValuationMetric { + circle: string + part: string + metric: string + desc: string + industryMedian: string } -export interface USRealizedPLMetric { - amount: string - period: number - rate: string +/** US valuation overview */ +export interface UsValuationOverview { + metrics: Record + indicator: string + range: number + date: string + ccySymbol: string + aichatData: USAIChatData + aiSummary: string } -export interface USRealizedPLEntry { - category: number +/** One security's valuation comparison item. */ +export interface ValuationComparisonItem { + /** Symbol (e.g. `"AAPL.US"`) */ + symbol: string + /** Security name */ + name: string + /** Currency */ currency: string - metrics: Array + /** Market capitalisation */ + marketValue: string + /** Latest closing price */ + priceClose: string + /** P/E ratio */ + pe: string + /** P/B ratio */ + pb: string + /** P/S ratio */ + ps: string + /** Return on equity */ + roe: string + /** Earnings per share */ + eps: string + /** Book value per share */ + bps: string + /** Dividends per share */ + dps: string + /** Dividend yield */ + divYld: string + /** Total assets */ + assets: string + /** Historical valuation points */ + history: Array } -export interface USRealizedPL { - realizedPlList: Array +/** Valuation comparison response. */ +export interface ValuationComparisonResponse { + /** Valuation comparison items */ + list: Array +} + +/** Valuation metrics response */ +export interface ValuationData { + /** Valuation metrics */ + metrics: ValuationMetricsData +} + +/** Distribution statistics for one valuation metric */ +export interface ValuationDist { + /** Minimum value */ + low?: string + /** Maximum value */ + high?: string + /** Median value */ + median?: string + /** Current value */ + value?: string + /** Percentile ranking */ + ranking?: string + /** Ordinal rank index */ + rankIndex: string + /** Total securities in industry */ + rankTotal: string +} + +/** Historical valuation container */ +export interface ValuationHistoryData { + /** Historical metrics */ + metrics: ValuationHistoryMetrics +} + +/** Historical data for one valuation metric */ +export interface ValuationHistoryMetric { + /** Description */ + desc: string + /** High */ + high?: string + /** Low */ + low?: string + /** Median */ + median?: string + /** Data points */ + list: Array +} + +/** Historical metrics container */ +export interface ValuationHistoryMetrics { + /** PE history */ + pe?: ValuationHistoryMetric + /** PB history */ + pb?: ValuationHistoryMetric + /** PS history */ + ps?: ValuationHistoryMetric +} + +/** One historical valuation data point. */ +export interface ValuationHistoryPoint { + /** Date (RFC 3339) */ + date: string + /** P/E ratio */ + pe: string + /** P/B ratio */ + pb: string + /** P/S ratio */ + ps: string +} + +/** Historical valuation response */ +export interface ValuationHistoryResponse { + /** Historical valuation data */ + history: ValuationHistoryData +} + +/** Historical time-series for one valuation metric */ +export interface ValuationMetricData { + /** Description */ + desc: string + /** Historical high */ + high?: string + /** Historical low */ + low?: string + /** Historical median */ + median?: string + /** Data points */ + list: Array +} + +/** Valuation metrics container */ +export interface ValuationMetricsData { + /** PE ratio history */ + pe?: ValuationMetricData + /** PB ratio history */ + pb?: ValuationMetricData + /** PS ratio history */ + ps?: ValuationMetricData + /** Dividend yield history */ + dvdYld?: ValuationMetricData +} + +/** One valuation data point */ +export interface ValuationPoint { + /** Unix timestamp (seconds) */ + timestamp: number + /** Metric value */ + value?: string +} + +/** Warrant sort by */ +export declare const enum WarrantSortBy { + /** Last done */ + LastDone = 0, + /** Change rate */ + ChangeRate = 1, + /** Change value */ + ChangeValue = 2, + /** Volume */ + Volume = 3, + /** Turnover */ + Turnover = 4, + /** Expiry date */ + ExpiryDate = 5, + /** Strike price */ + StrikePrice = 6, + /** Upper strike price */ + UpperStrikePrice = 7, + /** Lower strike price */ + LowerStrikePrice = 8, + /** Outstanding quantity */ + OutstandingQuantity = 9, + /** Outstanding ratio */ + OutstandingRatio = 10, + /** Premium */ + Premium = 11, + /** In/out of the bound */ + ItmOtm = 12, + /** Implied volatility */ + ImpliedVolatility = 13, + /** Greek value delta */ + Delta = 14, + /** Call price */ + CallPrice = 15, + /** Price interval from the call price */ + ToCallPrice = 16, + /** Effective leverage */ + EffectiveLeverage = 17, + /** Leverage ratio */ + LeverageRatio = 18, + /** Conversion ratio */ + ConversionRatio = 19, + /** Breakeven point */ + BalancePoint = 20, + /** Status */ + Status = 21 +} + +/** Warrant status */ +export declare const enum WarrantStatus { + /** Suspend */ + Suspend = 0, + /** Prepare List */ + PrepareList = 1, + /** Normal */ + Normal = 2 +} + +/** Warrant type */ +export declare const enum WarrantType { + /** Unknown */ + Unknown = 0, + /** Call */ + Call = 1, + /** Put */ + Put = 2, + /** Bull */ + Bull = 3, + /** Bear */ + Bear = 4, + /** Inline */ + Inline = 5 } diff --git a/nodejs/index.js b/nodejs/index.js index 5a371a504a..fe5afa2ab6 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -3,6 +3,9 @@ // @ts-nocheck /* auto-generated by NAPI-RS */ +const { createRequire } = require('node:module') +require = createRequire(__filename) + const { readFileSync } = require('node:fs') let nativeBinding = null const loadErrors = [] @@ -63,7 +66,7 @@ const isMuslFromChildProcess = () => { function requireNative() { if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { try { - return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); + nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); } catch (err) { loadErrors.push(err) } @@ -75,12 +78,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-android-arm64') - const bindingPackageVersion = require('longbridge-android-arm64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-android-arm64') } catch (e) { loadErrors.push(e) } @@ -91,12 +89,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-android-arm-eabi') - const bindingPackageVersion = require('longbridge-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-android-arm-eabi') } catch (e) { loadErrors.push(e) } @@ -105,39 +98,16 @@ function requireNative() { } } else if (process.platform === 'win32') { if (process.arch === 'x64') { - if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') { - try { - return require('./longbridge.win32-x64-gnu.node') - } catch (e) { - loadErrors.push(e) - } try { - const binding = require('longbridge-win32-x64-gnu') - const bindingPackageVersion = require('longbridge-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { return require('./longbridge.win32-x64-msvc.node') } catch (e) { loadErrors.push(e) } try { - const binding = require('longbridge-win32-x64-msvc') - const bindingPackageVersion = require('longbridge-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-win32-x64-msvc') } catch (e) { loadErrors.push(e) } - } } else if (process.arch === 'ia32') { try { return require('./longbridge.win32-ia32-msvc.node') @@ -145,12 +115,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-win32-ia32-msvc') - const bindingPackageVersion = require('longbridge-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-win32-ia32-msvc') } catch (e) { loadErrors.push(e) } @@ -161,12 +126,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-win32-arm64-msvc') - const bindingPackageVersion = require('longbridge-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-win32-arm64-msvc') } catch (e) { loadErrors.push(e) } @@ -180,12 +140,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-darwin-universal') - const bindingPackageVersion = require('longbridge-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-darwin-universal') } catch (e) { loadErrors.push(e) } @@ -196,12 +151,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-darwin-x64') - const bindingPackageVersion = require('longbridge-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-darwin-x64') } catch (e) { loadErrors.push(e) } @@ -212,12 +162,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-darwin-arm64') - const bindingPackageVersion = require('longbridge-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-darwin-arm64') } catch (e) { loadErrors.push(e) } @@ -232,12 +177,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-freebsd-x64') - const bindingPackageVersion = require('longbridge-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-freebsd-x64') } catch (e) { loadErrors.push(e) } @@ -248,12 +188,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-freebsd-arm64') - const bindingPackageVersion = require('longbridge-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-freebsd-arm64') } catch (e) { loadErrors.push(e) } @@ -269,12 +204,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-x64-musl') - const bindingPackageVersion = require('longbridge-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-x64-musl') } catch (e) { loadErrors.push(e) } @@ -285,12 +215,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-x64-gnu') - const bindingPackageVersion = require('longbridge-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-x64-gnu') } catch (e) { loadErrors.push(e) } @@ -303,12 +228,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-arm64-musl') - const bindingPackageVersion = require('longbridge-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-arm64-musl') } catch (e) { loadErrors.push(e) } @@ -319,12 +239,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-arm64-gnu') - const bindingPackageVersion = require('longbridge-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-arm64-gnu') } catch (e) { loadErrors.push(e) } @@ -337,12 +252,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-arm-musleabihf') - const bindingPackageVersion = require('longbridge-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-arm-musleabihf') } catch (e) { loadErrors.push(e) } @@ -353,46 +263,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-arm-gnueabihf') - const bindingPackageVersion = require('longbridge-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'loong64') { - if (isMusl()) { - try { - return require('./longbridge.linux-loong64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('longbridge-linux-loong64-musl') - const bindingPackageVersion = require('longbridge-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./longbridge.linux-loong64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('longbridge-linux-loong64-gnu') - const bindingPackageVersion = require('longbridge-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-arm-gnueabihf') } catch (e) { loadErrors.push(e) } @@ -405,12 +276,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-riscv64-musl') - const bindingPackageVersion = require('longbridge-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-riscv64-musl') } catch (e) { loadErrors.push(e) } @@ -421,12 +287,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-riscv64-gnu') - const bindingPackageVersion = require('longbridge-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-riscv64-gnu') } catch (e) { loadErrors.push(e) } @@ -438,12 +299,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-ppc64-gnu') - const bindingPackageVersion = require('longbridge-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-ppc64-gnu') } catch (e) { loadErrors.push(e) } @@ -454,12 +310,7 @@ function requireNative() { loadErrors.push(e) } try { - const binding = require('longbridge-linux-s390x-gnu') - const bindingPackageVersion = require('longbridge-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-s390x-gnu') } catch (e) { loadErrors.push(e) } @@ -469,49 +320,34 @@ function requireNative() { } else if (process.platform === 'openharmony') { if (process.arch === 'arm64') { try { - return require('./longbridge.openharmony-arm64.node') + return require('./longbridge.linux-arm64-ohos.node') } catch (e) { loadErrors.push(e) } try { - const binding = require('longbridge-openharmony-arm64') - const bindingPackageVersion = require('longbridge-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-arm64-ohos') } catch (e) { loadErrors.push(e) } } else if (process.arch === 'x64') { try { - return require('./longbridge.openharmony-x64.node') + return require('./longbridge.linux-x64-ohos.node') } catch (e) { loadErrors.push(e) } try { - const binding = require('longbridge-openharmony-x64') - const bindingPackageVersion = require('longbridge-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-x64-ohos') } catch (e) { loadErrors.push(e) } } else if (process.arch === 'arm') { try { - return require('./longbridge.openharmony-arm.node') + return require('./longbridge.linux-arm-ohos.node') } catch (e) { loadErrors.push(e) } try { - const binding = require('longbridge-openharmony-arm') - const bindingPackageVersion = require('longbridge-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding + return require('longbridge-linux-arm-ohos') } catch (e) { loadErrors.push(e) } @@ -526,36 +362,22 @@ function requireNative() { nativeBinding = requireNative() if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { - let wasiBinding = null - let wasiBindingError = null try { - wasiBinding = require('./longbridge.wasi.cjs') - nativeBinding = wasiBinding + nativeBinding = require('./longbridge.wasi.cjs') } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { - wasiBindingError = err + loadErrors.push(err) } } - if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + if (!nativeBinding) { try { - wasiBinding = require('longbridge-wasm32-wasi') - nativeBinding = wasiBinding + nativeBinding = require('longbridge-wasm32-wasi') } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { - if (!wasiBindingError) { - wasiBindingError = err - } else { - wasiBindingError.cause = err - } loadErrors.push(err) } } } - if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { - const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') - error.cause = wasiBindingError - throw error - } } if (!nativeBinding) { @@ -564,12 +386,7 @@ if (!nativeBinding) { `Cannot find native binding. ` + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', - { - cause: loadErrors.reduce((err, cur) => { - cur.cause = err - return cur - }), - }, + { cause: loadErrors } ) } throw new Error(`Failed to load native binding`) From 90a96849d2f284404adca175963b96917294b922 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 21 Jul 2026 10:47:41 +0800 Subject: [PATCH 9/9] feat(trade): add is_attached support to cancel_order across all SDKs The withdraw endpoint gained an optional is_attached flag (per developers PR #1092) so a take-profit/stop-loss sub-order can be cancelled by its own order ID. Wired non-breakingly per language: Rust CancelOrderOptions (impl Into, backward-compatible via From<&str>/From), Python is_attached kwarg, Node.js optional isAttached param, C++ defaulted parameter, Java cancelOrder overload, and a new C lb_trade_context_cancel_order_attached symbol. --- CHANGELOG.md | 1 + c/csrc/include/longbridge.h | 8 ++++ c/src/trade_context/context.rs | 28 ++++++++++--- cpp/include/trade_context.hpp | 3 +- cpp/src/trade_context.cpp | 30 ++++++++------ .../main/java/com/longbridge/SdkNative.java | 3 ++ .../com/longbridge/trade/TradeContext.java | 21 +++++++++- java/src/trade_context.rs | 23 ++++++++++- nodejs/index.d.ts | 2 +- nodejs/src/trade/context.rs | 11 ++--- python/pysrc/longbridge/openapi.pyi | 6 ++- python/src/trade/context.rs | 18 +++++--- python/src/trade/context_async.rs | 24 +++++++---- rust/src/blocking/trade.rs | 25 ++++++----- rust/src/trade/context.rs | 28 +++++-------- rust/src/trade/mod.rs | 10 ++--- rust/src/trade/requests/cancel_order.rs | 41 +++++++++++++++++++ rust/src/trade/requests/mod.rs | 2 + 18 files changed, 209 insertions(+), 75 deletions(-) create mode 100644 rust/src/trade/requests/cancel_order.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ed235023e4..0fd59f3457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Order` / `OrderDetail`: new `attached_orders: Vec` field - New method `order_detail_attached(order_id)` — queries detail for an attached order by its own ID - `order_detail` now accepts `GetOrderDetailOptions` (with optional `is_attached` flag) in addition to a plain order ID string + - `cancel_order` gains an `is_attached` flag to cancel an attached sub-order by its own order ID (Rust: `CancelOrderOptions`; Python: `is_attached` keyword arg; Node.js: optional `isAttached` param; C++/Java: `is_attached` overload/default parameter; C: new `lb_trade_context_cancel_order_attached`) ### Breaking changes diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index f785b9f36e..1499b34e8d 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -10880,6 +10880,14 @@ void lb_trade_context_cancel_order(const struct lb_trade_context_t *ctx, lb_async_callback_t callback, void *userdata); +/** + * Cancel attached order + */ +void lb_trade_context_cancel_order_attached(const struct lb_trade_context_t *ctx, + const char *order_id, + lb_async_callback_t callback, + void *userdata); + /** * Get account balance */ diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs index 9f7ea2fcc1..e29bce27c5 100644 --- a/c/src/trade_context/context.rs +++ b/c/src/trade_context/context.rs @@ -3,11 +3,11 @@ use std::{ffi::c_void, os::raw::c_char, sync::Arc, time::Instant}; use longbridge::{ TradeContext, trade::{ - AttachedOrderType, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, - GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, - GetHistoryOrdersOptions, GetOrderDetailOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, PushEvent, ReplaceAttachedParams, - ReplaceOrderOptions, SubmitAttachedParams, SubmitOrderOptions, + AttachedOrderType, CancelOrderOptions, EstimateMaxPurchaseQuantityOptions, + GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, PushEvent, + ReplaceAttachedParams, ReplaceOrderOptions, SubmitAttachedParams, SubmitOrderOptions, }, }; use parking_lot::Mutex; @@ -593,6 +593,24 @@ pub unsafe extern "C" fn lb_trade_context_cancel_order( }); } +/// Cancel attached order +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_trade_context_cancel_order_attached( + ctx: *const CTradeContext, + order_id: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let order_id = cstr_to_rust(order_id); + execute_async(callback, ctx, userdata, async move { + ctx_inner + .cancel_order(CancelOrderOptions::new(order_id).is_attached()) + .await?; + Ok(()) + }); +} + /// Get account balance #[unsafe(no_mangle)] pub unsafe extern "C" fn lb_trade_context_account_balance( diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp index ef8229582e..90dd9fe8b4 100644 --- a/cpp/include/trade_context.hpp +++ b/cpp/include/trade_context.hpp @@ -79,7 +79,8 @@ class TradeContext /// Cancel order void cancel_order(const std::string& order_id, - AsyncCallback callback) const; + AsyncCallback callback, + bool is_attached = false) const; /// Get account balance with currency void account_balance( diff --git a/cpp/src/trade_context.cpp b/cpp/src/trade_context.cpp index 5857c076f2..34065766ea 100644 --- a/cpp/src/trade_context.cpp +++ b/cpp/src/trade_context.cpp @@ -551,20 +551,24 @@ TradeContext::submit_order( void TradeContext::cancel_order(const std::string& order_id, - AsyncCallback callback) const + AsyncCallback callback, + bool is_attached) const { - lb_trade_context_cancel_order( - ctx_, - order_id.c_str(), - [](auto res) { - auto callback_ptr = - callback::get_async_callback(res->userdata); - (*callback_ptr)(AsyncResult( - TradeContext((const lb_trade_context_t*)res->ctx), - Status(res->error), - nullptr)); - }, - new AsyncCallback(callback)); + auto trampoline = [](auto res) { + auto callback_ptr = + callback::get_async_callback(res->userdata); + (*callback_ptr)(AsyncResult( + TradeContext((const lb_trade_context_t*)res->ctx), + Status(res->error), + nullptr)); + }; + auto* userdata = new AsyncCallback(callback); + if (is_attached) { + lb_trade_context_cancel_order_attached( + ctx_, order_id.c_str(), trampoline, userdata); + } else { + lb_trade_context_cancel_order(ctx_, order_id.c_str(), trampoline, userdata); + } } void diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 5258c1b8b5..5ebd650dd8 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -238,6 +238,9 @@ public static native void tradeContextSubmitOrder(long context, SubmitOrderOptio public static native void tradeContextCancelOrder(long context, String orderId, AsyncCallback callback); + public static native void tradeContextCancelOrderAttached(long context, String orderId, + AsyncCallback callback); + public static native void tradeContextAccountBalance(long context, String currency, AsyncCallback callback); public static native void tradeContextCashFlow(long context, GetCashFlowOptions opts, AsyncCallback callback); diff --git a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java index f6ec64f40b..0df1ec8690 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -353,8 +353,27 @@ public CompletableFuture submitOrder(SubmitOrderOptions opt * @throws OpenApiException If an error occurs */ public CompletableFuture cancelOrder(String orderId) throws OpenApiException { + return cancelOrder(orderId, false); + } + + /** + * Cancel order + * + * @param orderId Order ID + * @param isAttached When set together with orderId, indicates that orderId is + * an attached sub-order ID. The server will look up using + * the attached order ID instead of treating it as a regular + * order ID. + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture cancelOrder(String orderId, boolean isAttached) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { - SdkNative.tradeContextCancelOrder(this.raw, orderId, callback); + if (isAttached) { + SdkNative.tradeContextCancelOrderAttached(this.raw, orderId, callback); + } else { + SdkNative.tradeContextCancelOrder(this.raw, orderId, callback); + } }); } diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index 4003f14626..26ff9863bd 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -9,7 +9,7 @@ use jni::{ use longbridge::{ Config, Decimal, Market, TradeContext, trade::{ - AttachedOrderType, BalanceType, EstimateMaxPurchaseQuantityOptions, + AttachedOrderType, BalanceType, CancelOrderOptions, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, OrderSide, @@ -638,6 +638,27 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextCancelOr }) } +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextCancelOrderAttached( + mut env: JNIEnv, + _class: JClass, + context: i64, + order_id: JString, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let order_id: String = FromJValue::from_jvalue(env, order_id.into())?; + async_util::execute(env, callback, async move { + Ok(context + .ctx + .cancel_order(CancelOrderOptions::new(order_id).is_attached()) + .await?) + })?; + Ok(()) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextAccountBalance( mut env: JNIEnv, diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index 2c9af8f876..a83b1f541a 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -2766,7 +2766,7 @@ export declare class TradeContext { * await ctx.cancelOrder("709043056541253632"); * ``` */ - cancelOrder(orderId: string): Promise + cancelOrder(orderId: string, isAttached?: boolean | undefined | null): Promise /** * Get account balance * diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs index fa73594f7e..540e42f26d 100644 --- a/nodejs/src/trade/context.rs +++ b/nodejs/src/trade/context.rs @@ -366,11 +366,12 @@ impl TradeContext { /// await ctx.cancelOrder("709043056541253632"); /// ``` #[napi] - pub async fn cancel_order(&self, order_id: String) -> Result<()> { - self.ctx - .cancel_order(order_id) - .await - .map_err(ErrorNewType)?; + pub async fn cancel_order(&self, order_id: String, is_attached: Option) -> Result<()> { + let mut opts = longbridge::trade::CancelOrderOptions::new(order_id); + if is_attached.unwrap_or(false) { + opts = opts.is_attached(); + } + self.ctx.cancel_order(opts).await.map_err(ErrorNewType)?; Ok(()) } diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index ee71b43420..962ededb94 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -7327,12 +7327,13 @@ class TradeContext: print(resp) """ - def cancel_order(self, order_id: str) -> None: + def cancel_order(self, order_id: str, is_attached: bool = False) -> None: """ Cancel order Args: order_id: Order ID + is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id Examples: :: @@ -8071,12 +8072,13 @@ class AsyncTradeContext: """ ... - def cancel_order(self, order_id: str) -> Awaitable[None]: + def cancel_order(self, order_id: str, is_attached: bool = False) -> Awaitable[None]: """ Cancel order by order_id. Returns an awaitable. Args: order_id: Order ID. + is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id Examples: :: diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs index 6c1aed94e1..f8e64f0eb7 100644 --- a/python/src/trade/context.rs +++ b/python/src/trade/context.rs @@ -3,10 +3,11 @@ use std::sync::Arc; use longbridge::{ blocking::TradeContextSync, trade::{ - EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, - GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, - GetTodayOrdersOptions, QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, + CancelOrderOptions, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, + GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, + GetHistoryOrdersOptions, GetOrderDetailOptions, GetStockPositionsOptions, + GetTodayExecutionsOptions, GetTodayOrdersOptions, QueryUSOrdersOptions, + ReplaceOrderOptions, SubmitOrderOptions, }, }; use parking_lot::Mutex; @@ -377,8 +378,13 @@ impl TradeContext { } /// Cancel order - fn cancel_order(&self, order_id: String) -> PyResult<()> { - self.ctx.cancel_order(order_id).map_err(ErrorNewType)?; + #[pyo3(signature = (order_id, is_attached = false))] + fn cancel_order(&self, order_id: String, is_attached: bool) -> PyResult<()> { + let mut opts = CancelOrderOptions::new(order_id); + if is_attached { + opts = opts.is_attached(); + } + self.ctx.cancel_order(opts).map_err(ErrorNewType)?; Ok(()) } diff --git a/python/src/trade/context_async.rs b/python/src/trade/context_async.rs index c2c6a1265f..eff1751e91 100644 --- a/python/src/trade/context_async.rs +++ b/python/src/trade/context_async.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use longbridge::trade::{ - EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, - GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, - GetTodayOrdersOptions, QueryUSOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, - TradeContext, + CancelOrderOptions, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, + GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, + GetHistoryOrdersOptions, GetOrderDetailOptions, GetStockPositionsOptions, + GetTodayExecutionsOptions, GetTodayOrdersOptions, QueryUSOrdersOptions, ReplaceOrderOptions, + SubmitOrderOptions, TradeContext, }; use parking_lot::Mutex; use pyo3::{prelude::*, types::PyType}; @@ -424,10 +424,20 @@ impl AsyncTradeContext { } /// Cancel order. Returns awaitable. - fn cancel_order(&self, py: Python<'_>, order_id: String) -> PyResult> { + #[pyo3(signature = (order_id, is_attached = false))] + fn cancel_order( + &self, + py: Python<'_>, + order_id: String, + is_attached: bool, + ) -> PyResult> { let ctx = self.ctx.clone(); + let mut opts = CancelOrderOptions::new(order_id); + if is_attached { + opts = opts.is_attached(); + } pyo3_async_runtimes::tokio::future_into_py(py, async move { - ctx.cancel_order(order_id).await.map_err(ErrorNewType)?; + ctx.cancel_order(opts).await.map_err(ErrorNewType)?; Ok(()) }) .map(|b| b.unbind()) diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs index 4aaada2b40..4cb5a83a7a 100644 --- a/rust/src/blocking/trade.rs +++ b/rust/src/blocking/trade.rs @@ -4,15 +4,15 @@ use crate::{ Config, Result, blocking::runtime::BlockingRuntime, trade::{ - AccountBalance, AllExecutionsResponse, CashFlow, EstimateMaxPurchaseQuantityOptions, - EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, - GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, - GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, OrderDetail, PushEvent, - QueryUSOrdersOptions, QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse, - SubmitOrderOptions, SubmitOrderResponse, TopicType, TradeContext, USAssetOverview, - USOrderDetailResponse, USRealizedPL, + AccountBalance, AllExecutionsResponse, CancelOrderOptions, CashFlow, + EstimateMaxPurchaseQuantityOptions, EstimateMaxPurchaseQuantityResponse, Execution, + FundPositionsResponse, GetAllExecutionsOptions, GetCashFlowOptions, + GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, + OrderDetail, PushEvent, QueryUSOrdersOptions, QueryUSOrdersResponse, ReplaceOrderOptions, + StockPositionsResponse, SubmitOrderOptions, SubmitOrderResponse, TopicType, TradeContext, + USAssetOverview, USOrderDetailResponse, USRealizedPL, }, }; @@ -290,9 +290,12 @@ impl TradeContextSync { /// # Ok(()) /// # } /// ``` - pub fn cancel_order(&self, order_id: impl Into + Send + 'static) -> Result<()> { + pub fn cancel_order( + &self, + options: impl Into + Send + 'static, + ) -> Result<()> { self.rt - .call(move |ctx| async move { ctx.cancel_order(order_id).await }) + .call(move |ctx| async move { ctx.cancel_order(options).await }) } /// Get account balance diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs index 3ff2ab6cce..62dd65c6d0 100644 --- a/rust/src/trade/context.rs +++ b/rust/src/trade/context.rs @@ -10,14 +10,15 @@ use tracing::{Subscriber, dispatcher, instrument::WithSubscriber}; use crate::{ Config, Result, serde_utils, trade::{ - AccountBalance, AllExecutionsResponse, CashFlow, EstimateMaxPurchaseQuantityOptions, - Execution, FundPositionsResponse, GetAllExecutionsOptions, GetCashFlowOptions, - GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, - GetTodayOrdersOptions, GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, - OrderDetail, OrderSide, PushEvent, QueryUSOrdersOptions, QueryUSOrdersResponse, - ReplaceOrderOptions, StockPositionsResponse, SubmitOrderOptions, TopicType, - USAssetOverview, USOrderDetailResponse, USRealizedPL, + AccountBalance, AllExecutionsResponse, CancelOrderOptions, CashFlow, + EstimateMaxPurchaseQuantityOptions, Execution, FundPositionsResponse, + GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, + GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, OrderDetail, OrderSide, + PushEvent, QueryUSOrdersOptions, QueryUSOrdersResponse, ReplaceOrderOptions, + StockPositionsResponse, SubmitOrderOptions, TopicType, USAssetOverview, + USOrderDetailResponse, USRealizedPL, core::{Command, Core}, }, }; @@ -521,20 +522,13 @@ impl TradeContext { /// # Ok::<_, Box>(()) /// # }); /// ``` - pub async fn cancel_order(&self, order_id: impl Into) -> Result<()> { - #[derive(Debug, Serialize)] - struct Request { - order_id: String, - } - + pub async fn cancel_order(&self, options: impl Into) -> Result<()> { Ok(self .0 .http_cli .request(Method::DELETE, "/v1/trade/order") .response::>() - .query_params(Request { - order_id: order_id.into(), - }) + .query_params(options.into()) .send() .with_subscriber(self.0.log_subscriber.clone()) .await diff --git a/rust/src/trade/mod.rs b/rust/src/trade/mod.rs index 3000965e43..67bc2ec407 100644 --- a/rust/src/trade/mod.rs +++ b/rust/src/trade/mod.rs @@ -10,11 +10,11 @@ mod types; pub use context::{EstimateMaxPurchaseQuantityResponse, SubmitOrderResponse, TradeContext}; pub use push_types::{PushEvent, PushOrderChanged, TopicType}; pub use requests::{ - EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions, - GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, - GetTodayOrdersOptions, ReplaceAttachedParams, ReplaceOrderOptions, SubmitAttachedParams, - SubmitOrderOptions, + CancelOrderOptions, EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, + GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, + GetHistoryOrdersOptions, GetOrderDetailOptions, GetStockPositionsOptions, + GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceAttachedParams, ReplaceOrderOptions, + SubmitAttachedParams, SubmitOrderOptions, }; pub use types::{ AccountBalance, diff --git a/rust/src/trade/requests/cancel_order.rs b/rust/src/trade/requests/cancel_order.rs new file mode 100644 index 0000000000..11ec2a8519 --- /dev/null +++ b/rust/src/trade/requests/cancel_order.rs @@ -0,0 +1,41 @@ +use serde::Serialize; + +/// Options for cancel order request +#[derive(Debug, Serialize, Clone)] +pub struct CancelOrderOptions { + order_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + is_attached: Option, +} + +impl CancelOrderOptions { + /// Create new options with order ID + pub fn new(order_id: impl Into) -> Self { + Self { + order_id: order_id.into(), + is_attached: None, + } + } + /// Indicate that the provided `order_id` is an attached sub-order ID. + /// + /// When set, the server looks up the order using the attached order ID + /// instead of treating `order_id` as a regular order ID. + pub fn is_attached(self) -> Self { + Self { + is_attached: Some(true), + ..self + } + } +} + +impl From for CancelOrderOptions { + fn from(order_id: String) -> Self { + Self::new(order_id) + } +} + +impl<'a> From<&'a str> for CancelOrderOptions { + fn from(order_id: &'a str) -> Self { + Self::new(order_id) + } +} diff --git a/rust/src/trade/requests/mod.rs b/rust/src/trade/requests/mod.rs index b4d9613355..0b3400f46e 100644 --- a/rust/src/trade/requests/mod.rs +++ b/rust/src/trade/requests/mod.rs @@ -1,3 +1,4 @@ +mod cancel_order; mod estimate_max_purchase_quantity; mod get_all_executions; mod get_cash_flow; @@ -11,6 +12,7 @@ mod get_today_orders; mod replace_order; mod submit_order; +pub use cancel_order::CancelOrderOptions; pub use estimate_max_purchase_quantity::EstimateMaxPurchaseQuantityOptions; pub use get_all_executions::GetAllExecutionsOptions; pub use get_cash_flow::GetCashFlowOptions;