From d74728891f02f5004881e1697ee26c2a96b92303 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 16 Jun 2026 16:38:11 +0800 Subject: [PATCH 1/7] 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 | 177 +++++++++- java/src/types/classes.rs | 36 +- java/src/types/enum_types.rs | 13 + nodejs/index.d.ts | 131 ++++++- nodejs/index.js | 255 ++------------ 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 | 36 +- python/src/trade/context_async.rs | 43 ++- python/src/trade/mod.rs | 4 + python/src/trade/types.rs | 199 ++++++++++- rust/src/blocking/trade.rs | 10 +- rust/src/trade/context.rs | 21 +- rust/src/trade/mod.rs | 17 +- 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, 2985 insertions(+), 293 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 35ff1563c1..2f80fe074b 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1138,6 +1138,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 */ @@ -2319,8 +2337,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 */ @@ -2369,8 +2457,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 */ @@ -2440,6 +2574,10 @@ typedef struct lb_submit_order_options_t { * Remark (Maximum 64 characters) (can be null) */ const char *remark; + /** + * Attached order parameters (can be null) + */ + const struct CSubmitAttachedParams *attached_params; } lb_submit_order_options_t; /** @@ -3241,6 +3379,96 @@ typedef struct lb_execution_t { const struct lb_decimal_t *price; } lb_execution_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 */ @@ -3361,6 +3589,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; /** @@ -3976,6 +4212,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; /** @@ -10625,6 +10869,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 9013388e6b..fbceb315ee 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, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, PushEvent, ReplaceOrderOptions, - SubmitOrderOptions, + AttachedOrderType, EstimateMaxPurchaseQuantityOptions, GetCashFlowOptions, + GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, PushEvent, ReplaceAttachedParams, ReplaceOrderOptions, + SubmitAttachedParams, SubmitOrderOptions, }, }; use parking_lot::Mutex; @@ -336,6 +337,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(); @@ -384,6 +388,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(()) @@ -446,6 +498,36 @@ pub unsafe extern "C" fn lb_trade_context_submit_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 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) @@ -594,6 +676,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 7e4d7d6e2d..c58e9b377d 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, 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, 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 { @@ -405,6 +690,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)] @@ -438,6 +727,7 @@ pub(crate) struct COrderOwned { trigger_count: Option, monitor_price: Option, remark: CString, + attached_orders: CVec, } impl From for COrderOwned { @@ -472,6 +762,7 @@ impl From for COrderOwned { trigger_count, monitor_price, remark, + attached_orders, } = order; COrderOwned { order_id: order_id.into(), @@ -503,6 +794,7 @@ impl From for COrderOwned { trigger_count, monitor_price: monitor_price.map(Into::into), remark: remark.into(), + attached_orders: attached_orders.into(), } } } @@ -541,6 +833,7 @@ impl ToFFI for COrderOwned { trigger_count, monitor_price, remark, + attached_orders, } = self; COrder { order_id: order_id.to_ffi_type(), @@ -617,6 +910,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(), } } } @@ -657,6 +952,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 @@ -685,6 +982,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 @@ -724,6 +1023,8 @@ pub struct CSubmitOrderOptions { pub monitor_price: *const CDecimal, /// Remark (Maximum 64 characters) (can be null) pub remark: *const c_char, + /// Attached order parameters (can be null) + pub attached_params: *const CSubmitAttachedParams, } /// Response for submit order request @@ -1771,6 +2072,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)] @@ -1815,6 +2120,7 @@ pub(crate) struct COrderDetailOwned { platform_deducted_currency: Option, history: CVec, charge_detail: COrderChargeDetailOwned, + attached_orders: CVec, } impl From for COrderDetailOwned { @@ -1860,6 +2166,7 @@ impl From for COrderDetailOwned { platform_deducted_currency, history, charge_detail, + attached_orders, } = order; COrderDetailOwned { order_id: order_id.into(), @@ -1902,6 +2209,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(), } } } @@ -1951,6 +2259,7 @@ impl ToFFI for COrderDetailOwned { platform_deducted_currency, history, charge_detail, + attached_orders, } = self; COrderDetail { order_id: order_id.to_ffi_type(), @@ -2057,6 +2366,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 ce909e5ed3..a197d41852 100644 --- a/cpp/include/trade_context.hpp +++ b/cpp/include/trade_context.hpp @@ -108,6 +108,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 7c66849b0e..68bbeeb799 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1538,6 +1538,79 @@ enum class OutsideRTH Overnight, }; +/// 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 { @@ -1599,6 +1672,8 @@ struct Order std::optional monitor_price; /// Remark std::string remark; + /// Attached orders + std::vector attached_orders; }; /// Order changed message @@ -1686,6 +1761,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 @@ -1713,6 +1790,8 @@ struct ReplaceOrderOptions std::optional monitor_price; /// Remark std::optional remark; + /// Attached order params + std::optional attached_params; }; /// Options for submit order request @@ -1751,6 +1830,8 @@ struct SubmitOrderOptions std::optional monitor_price; /// Remark (Maximum 64 characters) std::optional remark; + /// Attached order params + std::optional attached_params; }; /// Response for submit order request @@ -2134,6 +2215,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 8789e1a772..a6f4819964 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; @@ -98,9 +100,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; @@ -1276,9 +1280,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), @@ -1320,7 +1493,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, }; } @@ -1701,6 +1875,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), @@ -1764,6 +1944,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 669402e764..3daa1ffe35 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) @@ -294,7 +296,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; @@ -325,6 +327,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( @@ -371,7 +374,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 @@ -394,6 +399,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_, @@ -431,9 +440,11 @@ TradeContext::submit_order( nullptr, nullptr, opts.remark ? opts.remark->c_str() : 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(); @@ -467,6 +478,10 @@ TradeContext::submit_order( outside_rth = convert(*opts.outside_rth); opts2.outside_rth = &outside_rth; } + 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_, @@ -756,6 +771,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 7beea04c53..ba8125bb39 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -247,6 +247,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 b2805be39d..886393de20 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java @@ -24,6 +24,7 @@ public class SubmitOrderOptions { private Integer triggerCount; private BigDecimal monitorPrice; private String remark; + private SubmitAttachedParams attachedParams; /** * Constructs options for submitting an order. @@ -167,4 +168,24 @@ public SubmitOrderOptions setRemark(String remark) { this.remark = remark; 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 e4d9e5630a..7607bb4586 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -574,6 +574,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 2e03effa1f..c2c19b8db2 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::Order, diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index 214812ffdc..14a7fc3c3c 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -9,10 +9,11 @@ use jni::{ use longbridge::{ Config, Decimal, Market, TradeContext, trade::{ - BalanceType, EstimateMaxPurchaseQuantityOptions, GetCashFlowOptions, + AttachedOrderType, BalanceType, EstimateMaxPurchaseQuantityOptions, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, OrderSide, - OrderStatus, OrderType, OutsideRTH, PushEvent, ReplaceOrderOptions, SubmitOrderOptions, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, OrderSide, OrderStatus, OrderType, OutsideRTH, PushEvent, + ReplaceAttachedParams, ReplaceOrderOptions, SubmitAttachedParams, SubmitOrderOptions, TimeInForceType, TopicType, }, }; @@ -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)] @@ -288,6 +289,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 @@ -299,6 +304,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, @@ -348,6 +464,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?) @@ -417,6 +549,22 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextSubmitOr if let Some(remark) = remark { new_opts = new_opts.remark(remark); } + 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?) @@ -625,3 +773,24 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextEstimate 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 285f3611cd..99b30c63cb 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, @@ -657,7 +685,9 @@ impl_java_class!( #[java(set_as_opt = crate::types::JavaInteger)] trigger_count, monitor_price, - remark + remark, + #[java(objarray)] + attached_orders ] ); @@ -934,7 +964,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 51a3b9c7a2..4b7786351c 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -455,6 +455,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 d564d1d5fa..5c581693f3 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -64,6 +64,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 @@ -619,13 +667,9 @@ export declare class FundamentalContext { */ etfAssetAllocation(symbol: string): Promise /** List macroeconomic indicators */ - macroeconomicIndicators(country?: MacroeconomicCountry | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise + 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 - /** List macroeconomic indicators (v2) with optional keyword filter */ - macroeconomicIndicatorsV2(country?: MacroeconomicCountry | undefined | null, keyword?: string | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise - /** Get historical data for a macroeconomic indicator (v2) with sort support */ - macroeconomicV2(indicatorCode: string, startDate?: string | undefined | null, endDate?: string | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null, sort?: string | undefined | null): Promise } /** Fund position */ @@ -1017,6 +1061,8 @@ export declare class Order { get monitorPrice(): Decimal | null /** Remark */ get remark(): string + /** Attached orders */ + get attachedOrders(): Array } /** Order charge detail */ @@ -1141,6 +1187,8 @@ export declare class OrderDetail { get history(): Array /** Order charges */ get chargeDetail(): OrderChargeDetail + /** Attached orders */ + get attachedOrders(): Array } /** Order history detail */ @@ -2784,6 +2832,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 @@ -3153,6 +3202,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, @@ -4217,6 +4276,8 @@ export interface GetTodayOrdersOptions { market?: Market /** Order id */ orderId?: string + /** Filter attached orders only */ + isAttached?: boolean } /** Data granularity */ @@ -5234,6 +5295,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 */ @@ -5258,6 +5353,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. */ @@ -5610,6 +5707,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 */ @@ -5647,6 +5766,8 @@ export interface SubmitOrderOptions { monitorPrice?: Decimal /** Remark (Maximum 64 characters) */ remark?: string + /** Attached order parameters */ + attachedParams?: SubmitAttachedParams } /** Quote type of subscription */ diff --git a/nodejs/index.js b/nodejs/index.js index 25721d2945..e9f7c9e06f 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`) @@ -579,6 +396,7 @@ module.exports = nativeBinding module.exports.AccountBalance = nativeBinding.AccountBalance module.exports.AlertContext = nativeBinding.AlertContext 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 @@ -669,6 +487,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 eca83725b5..5dfb17a368 100644 --- a/nodejs/src/trade/context.rs +++ b/nodejs/src/trade/context.rs @@ -502,6 +502,15 @@ impl TradeContext { .try_into() } + #[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 6919c8160c..d925137cae 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> { @@ -42,6 +96,8 @@ pub struct SubmitOrderOptions<'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::SubmitOrderOptions { @@ -87,6 +143,9 @@ impl<'env> From> for longbridge::trade::SubmitOrderOpti 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/types.rs b/nodejs/src/trade/types.rs index be4e0e257e..10b90ee3f1 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -193,6 +193,75 @@ pub enum OutsideRTH { Overnight, } +/// 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)] @@ -272,6 +341,9 @@ pub struct Order { monitor_price: Option, /// Remark remark: String, + /// Attached orders + #[js(array)] + attached_orders: Vec, } /// Commission-free Status @@ -491,6 +563,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 bef3ba940f..cfb733a187 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -5857,6 +5857,180 @@ class OutsideRTH: Overnight """ +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 @@ -6007,6 +6181,11 @@ class Order: Remark """ + attached_orders: List[AttachedOrderDetail] + """ + Attached orders + """ + class CommissionFreeStatus: """ Commission-free Status @@ -6387,6 +6566,11 @@ class OrderDetail: Order charges """ + attached_orders: List[AttachedOrderDetail] + """ + Attached orders + """ + class SubmitOrderResponse: """ Response for submit order request @@ -6934,6 +7118,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 @@ -6944,6 +7129,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 @@ -6981,6 +7167,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 @@ -7034,6 +7221,7 @@ class TradeContext: trigger_count: Optional[int] = None, monitor_price: Optional[Decimal] = None, remark: Optional[str] = None, + attached_params: Optional[SubmitAttachedParams] = None, ) -> SubmitOrderResponse: """ Submit order @@ -7055,6 +7243,7 @@ class TradeContext: trigger_count: Trigger count monitor_price: Monitor price remark: Remark (Maximum 64 characters) + attached_params: Attached order parameters Returns: Response @@ -7250,12 +7439,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 @@ -7568,6 +7758,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. @@ -7578,6 +7769,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: :: @@ -7622,6 +7814,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. @@ -7638,6 +7831,7 @@ class AsyncTradeContext: trigger_count: Trigger count. monitor_price: Monitor price. remark: Remark (max 64 characters). + attached_params: Attached order parameters. Examples: :: @@ -7680,6 +7874,7 @@ class AsyncTradeContext: trigger_count: Optional[int] = None, monitor_price: Optional[Decimal] = None, remark: 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. @@ -7701,6 +7896,7 @@ class AsyncTradeContext: trigger_count: Trigger count. monitor_price: Monitor price. remark: Remark (max 64 characters). + attached_params: Attached order parameters. Examples: :: @@ -7914,12 +8110,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 2e7ff3a090..46f8a59854 100644 --- a/python/src/trade/context.rs +++ b/python/src/trade/context.rs @@ -4,8 +4,9 @@ use longbridge::{ blocking::TradeContextSync, trade::{ EstimateMaxPurchaseQuantityOptions, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, + ReplaceOrderOptions, SubmitOrderOptions, }, }; use parking_lot::Mutex; @@ -21,8 +22,8 @@ use crate::{ types::{ AccountBalance, BalanceType, CashFlow, EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, MarginRatio, Order, OrderDetail, OrderSide, OrderStatus, - OrderType, OutsideRTH, StockPositionsResponse, SubmitOrderResponse, TimeInForceType, - TopicType, + OrderType, OutsideRTH, ReplaceAttachedParams, StockPositionsResponse, + SubmitAttachedParams, SubmitOrderResponse, TimeInForceType, TopicType, }, }, types::Market, @@ -170,7 +171,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, @@ -178,6 +179,7 @@ impl TradeContext { side: Option, market: Option, order_id: Option, + is_attached: bool, ) -> PyResult> { let mut opts = GetTodayOrdersOptions::new(); @@ -194,6 +196,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)) @@ -204,7 +209,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, @@ -219,6 +224,7 @@ impl TradeContext { trigger_count: Option, monitor_price: Option, remark: Option, + attached_params: Option, ) -> PyResult<()> { let mut opts = ReplaceOrderOptions::new(order_id, quantity.into()); @@ -249,13 +255,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))] + #[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, attached_params = None))] #[allow(clippy::too_many_arguments)] fn submit_order( &self, @@ -275,6 +284,7 @@ impl TradeContext { trigger_count: Option, monitor_price: Option, remark: Option, + attached_params: Option, ) -> PyResult { let mut opts = SubmitOrderOptions::new( symbol, @@ -317,6 +327,9 @@ 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 .submit_order(opts) @@ -402,9 +415,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 a0c02d3fe9..e0c4c8fca6 100644 --- a/python/src/trade/context_async.rs +++ b/python/src/trade/context_async.rs @@ -4,9 +4,9 @@ use std::sync::Arc; use longbridge::trade::{ EstimateMaxPurchaseQuantityOptions, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, - TradeContext, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, + ReplaceOrderOptions, SubmitOrderOptions, TradeContext, }; use parking_lot::Mutex; use pyo3::{prelude::*, types::PyType}; @@ -22,8 +22,8 @@ use crate::{ types::{ AccountBalance, BalanceType, CashFlow, EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, MarginRatio, Order, OrderDetail, OrderSide, OrderStatus, - OrderType, OutsideRTH, StockPositionsResponse, SubmitOrderResponse, TimeInForceType, - TopicType, + OrderType, OutsideRTH, ReplaceAttachedParams, StockPositionsResponse, + SubmitAttachedParams, SubmitOrderResponse, TimeInForceType, TopicType, }, }, types::Market, @@ -202,7 +202,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<'_>, @@ -211,6 +212,7 @@ impl AsyncTradeContext { side: Option, market: Option, order_id: Option, + is_attached: bool, ) -> PyResult> { let ctx = self.ctx.clone(); let mut opts = GetTodayOrdersOptions::new(); @@ -227,6 +229,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() @@ -237,7 +242,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, @@ -253,6 +258,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()); @@ -283,6 +289,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(()) @@ -291,7 +300,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))] + #[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, attached_params = None))] #[allow(clippy::too_many_arguments)] fn submit_order( &self, @@ -312,6 +321,7 @@ impl AsyncTradeContext { trigger_count: Option, monitor_price: Option, remark: Option, + attached_params: Option, ) -> PyResult> { let ctx = self.ctx.clone(); let mut opts = SubmitOrderOptions::new( @@ -354,6 +364,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 { let r: SubmitOrderResponse = ctx .submit_order(opts) @@ -474,11 +487,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 01f249eef2..90dae79c45 100644 --- a/python/src/trade/mod.rs +++ b/python/src/trade/mod.rs @@ -15,6 +15,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 49a4bd9179..96f8de009a 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, @@ -196,6 +196,197 @@ pub(crate) enum OutsideRTH { Overnight, } +/// 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)] @@ -274,6 +465,9 @@ pub(crate) struct Order { monitor_price: Option, /// Remark remark: String, + /// Attached orders + #[py(array)] + attached_orders: Vec, } /// Commission-free Status @@ -493,6 +687,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 3573120353..bcf8b754a5 100644 --- a/rust/src/blocking/trade.rs +++ b/rust/src/blocking/trade.rs @@ -7,9 +7,9 @@ use crate::{ AccountBalance, CashFlow, EstimateMaxPurchaseQuantityOptions, EstimateMaxPurchaseQuantityResponse, Execution, FundPositionsResponse, GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, - GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, MarginRatio, - Order, OrderDetail, PushEvent, ReplaceOrderOptions, StockPositionsResponse, - SubmitOrderOptions, SubmitOrderResponse, TopicType, TradeContext, + GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, MarginRatio, Order, OrderDetail, PushEvent, ReplaceOrderOptions, + StockPositionsResponse, SubmitOrderOptions, SubmitOrderResponse, TopicType, TradeContext, }, }; @@ -441,10 +441,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 4e4c1c0dbe..8c6da31dc7 100644 --- a/rust/src/trade/context.rs +++ b/rust/src/trade/context.rs @@ -12,9 +12,10 @@ use crate::{ trade::{ AccountBalance, CashFlow, EstimateMaxPurchaseQuantityOptions, Execution, FundPositionsResponse, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, MarginRatio, Order, OrderDetail, - PushEvent, ReplaceOrderOptions, StockPositionsResponse, SubmitOrderOptions, TopicType, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, MarginRatio, + Order, OrderDetail, PushEvent, ReplaceOrderOptions, StockPositionsResponse, + SubmitOrderOptions, TopicType, core::{Command, Core}, }, }; @@ -763,20 +764,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 21df810dcc..298e90ed99 100644 --- a/rust/src/trade/mod.rs +++ b/rust/src/trade/mod.rs @@ -11,14 +11,15 @@ pub use context::{EstimateMaxPurchaseQuantityResponse, SubmitOrderResponse, Trad pub use push_types::{PushEvent, PushOrderChanged, TopicType}; pub use requests::{ EstimateMaxPurchaseQuantityOptions, GetCashFlowOptions, GetFundPositionsOptions, - GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions, - GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, + GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions, + GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, + ReplaceAttachedParams, ReplaceOrderOptions, SubmitAttachedParams, SubmitOrderOptions, }; pub use types::{ - AccountBalance, BalanceType, CashFlow, CashFlowDirection, CashInfo, ChargeCategoryCode, - CommissionFreeStatus, DeductionStatus, Execution, FrozenTransactionFee, FundPosition, - FundPositionChannel, FundPositionsResponse, MarginRatio, Order, OrderChargeDetail, - OrderChargeFee, OrderChargeItem, OrderDetail, OrderHistoryDetail, OrderSide, OrderStatus, - OrderTag, OrderType, OutsideRTH, StockPosition, StockPositionChannel, StockPositionsResponse, - TimeInForceType, TriggerPriceType, TriggerStatus, + AccountBalance, AttachedOrderDetail, AttachedOrderType, BalanceType, CashFlow, + CashFlowDirection, CashInfo, ChargeCategoryCode, CommissionFreeStatus, DeductionStatus, + Execution, FrozenTransactionFee, FundPosition, FundPositionChannel, FundPositionsResponse, + MarginRatio, Order, OrderChargeDetail, OrderChargeFee, OrderChargeItem, OrderDetail, + OrderHistoryDetail, OrderSide, OrderStatus, OrderTag, OrderType, OutsideRTH, StockPosition, + StockPositionChannel, StockPositionsResponse, TimeInForceType, TriggerPriceType, TriggerStatus, }; 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 ee4fdbc091..2e4a3e5507 100644 --- a/rust/src/trade/requests/mod.rs +++ b/rust/src/trade/requests/mod.rs @@ -3,6 +3,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; @@ -14,8 +15,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 aa301b1803..cf5a68c830 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 @@ -37,6 +37,8 @@ pub struct SubmitOrderOptions { monitor_price: Option, #[serde(skip_serializing_if = "Option::is_none")] remark: Option, + #[serde(skip_serializing_if = "Option::is_none")] + attached_params: Option, } impl SubmitOrderOptions { @@ -66,6 +68,7 @@ impl SubmitOrderOptions { trigger_count: None, monitor_price: None, remark: None, + attached_params: None, } } @@ -172,4 +175,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 d78082f73b..93529286e1 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -234,6 +234,80 @@ pub enum OutsideRTH { Overnight, } +/// 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 { @@ -320,6 +394,9 @@ pub struct Order { pub monitor_price: Option, /// Remark pub remark: String, + /// Attached orders + #[serde(default)] + pub attached_orders: Vec, } /// Commission-free Status @@ -542,6 +619,9 @@ pub struct OrderDetail { pub history: Vec, /// Order charges pub charge_detail: OrderChargeDetail, + /// Attached orders + #[serde(default)] + pub attached_orders: Vec, } /// Cash info From 9be932fba5851cafb030ae9c99254a4e74eb3173 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 17 Jun 2026 19:46:58 +0800 Subject: [PATCH 2/7] 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 2f80fe074b..74e79cec2c 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1142,18 +1142,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; /** @@ -3388,9 +3392,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) */ @@ -3428,9 +3432,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 */ @@ -3438,7 +3442,7 @@ typedef struct CAttachedOrderDetail { /** * Tag */ - int32_t tag; + enum lb_order_tag_t tag; /** * Submitted time (unix timestamp) */ @@ -4209,7 +4213,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 c58e9b377d..b42070f21c 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) @@ -1942,6 +1952,7 @@ impl ToFFI for COrderChargeItemOwned { } /// Order charge detail +#[derive(Clone)] #[repr(C)] pub struct COrderChargeDetail { /// Total charges amount @@ -1954,6 +1965,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 @@ -2070,7 +2092,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, @@ -2119,7 +2143,7 @@ pub(crate) struct COrderDetailOwned { platform_deducted_amount: Option, platform_deducted_currency: Option, history: CVec, - charge_detail: COrderChargeDetailOwned, + charge_detail: Option, attached_orders: CVec, } @@ -2208,7 +2232,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(), } } @@ -2365,7 +2389,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 68bbeeb799..bab581ff76 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1555,7 +1555,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; @@ -1565,11 +1565,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; @@ -2213,8 +2213,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 a6f4819964..220a478ec7 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -1281,7 +1281,7 @@ convert(OutsideRTH status) } inline AttachedOrderType -convert(lb_attached_order_type_t ty) +convert(CAttachedOrderType ty) { switch (ty) { case AttachedOrderTypeUnknown: @@ -1297,7 +1297,7 @@ convert(lb_attached_order_type_t ty) } } -inline lb_attached_order_type_t +inline CAttachedOrderType convert(AttachedOrderType ty) { switch (ty) { @@ -1315,11 +1315,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), @@ -1330,11 +1330,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, @@ -1351,7 +1351,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 @@ -1395,7 +1395,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 @@ -1943,7 +1943,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 ba8125bb39..75741c5c36 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -247,7 +247,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 4b7786351c..2c501398f8 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -459,6 +459,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 5c581693f3..07c746ae82 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -70,8 +70,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 */ @@ -91,15 +91,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 */ @@ -1186,7 +1186,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 } @@ -3204,12 +3204,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 e9f7c9e06f..4f570aeb89 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 10b90ee3f1..ff891228af 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -198,6 +198,8 @@ pub enum OutsideRTH { #[derive(Debug, JsEnum, Hash, Eq, PartialEq, Copy, Clone)] #[js(remote = "longbridge::trade::AttachedOrderType")] pub enum AttachedOrderType { + /// Unknown + Unknown, /// Profit taker ProfitTaker, /// Stop loss @@ -213,8 +215,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, @@ -237,16 +239,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, @@ -562,7 +566,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 cfb733a187..c458e32a03 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -5862,6 +5862,11 @@ class AttachedOrderType: Attached order type """ + class Unknown(AttachedOrderType): + """ + Unknown + """ + class ProfitTaker(AttachedOrderType): """ Take profit @@ -5887,7 +5892,7 @@ class AttachedOrderDetail: Order ID """ - attached_type_display: int + attached_type_display: Type[AttachedOrderType] """ Attached type display (1=take-profit, 2=stop-loss) """ @@ -5937,7 +5942,7 @@ class AttachedOrderDetail: Counter ID """ - trigger_status: int + trigger_status: Optional[Type[TriggerStatus]] """ Trigger status """ @@ -5947,7 +5952,7 @@ class AttachedOrderDetail: Executed amount """ - tag: int + tag: Type[OrderTag] """ Tag """ @@ -5957,7 +5962,7 @@ class AttachedOrderDetail: Submitted time """ - executed_price: Decimal + executed_price: Optional[Decimal] """ Executed price """ @@ -6561,7 +6566,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 96f8de009a..c66baf3f7b 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -201,6 +201,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 @@ -216,8 +218,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, @@ -239,15 +241,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, @@ -686,7 +690,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 cf5a68c830..e4481c8b83 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 93529286e1..4575a301b5 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -235,8 +235,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, @@ -253,8 +255,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, @@ -279,12 +281,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", @@ -292,7 +295,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, @@ -618,7 +622,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, @@ -870,6 +874,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 4ba86ab8f12dfa3d2cd7b4fe26840a6b707c6ff0 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 08:20:38 +0800 Subject: [PATCH 3/7] docs(changelog): document attached order support and breaking changes --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 498b0dbdc2..9548913963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Rust:** `market::TradeStatus` models `/v1/quote/market-status` trade status codes, including engine-compatible normalization and display helpers. +- **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.3.2] - 2026-06-13 ### Added From 2e44c177916f6b524f216d813fc8bdf555be7e72 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:08:24 +0800 Subject: [PATCH 4/7] 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 4b070a3cbf..1efbefcc73 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -4278,7 +4278,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 205f052043..f9591793b2 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -7134,7 +7134,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 @@ -7450,7 +7450,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 @@ -7774,7 +7774,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: :: @@ -8121,7 +8121,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 4dfac939bb8226602e1e8f1da8c8041c6e52c6d9 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:12:32 +0800 Subject: [PATCH 5/7] 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 bd9d02e07d..5479b4a0df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,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 f9591793b2..43f1c8c598 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -7134,7 +7134,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 @@ -7450,7 +7450,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 @@ -7774,7 +7774,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: :: @@ -8121,7 +8121,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 1caf2d08d98b6c4e130314a6ff4afb76f16be363 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:21:05 +0800 Subject: [PATCH 6/7] 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 081c306b2df87b4e5973b323b37bf1daee870335 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 18 Jun 2026 09:33:20 +0800 Subject: [PATCH 7/7] 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 ea842a54b0..6d31ffe5a5 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,