diff --git a/CHANGELOG.md b/CHANGELOG.md index d806a7b99..8d284c2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- **All SDKs:** attached (take-profit / stop-loss) orders gain bid/ask depth levels (longbridge/developers#1189). `SubmitAttachedParams` and `ReplaceAttachedParams` gain `profit_taker_depth_level` / `stop_loss_depth_level` (int32): the depth level used for the take-profit / stop-loss limit order's submitted price, valid when `activate_order_type` is `LIT`. `AttachedOrderDetail` gains `limit_depth_level`, returned on the `attached_orders` of `today_orders` / `history_orders` / `order_detail` (unset when the server returns `0`, `null`, a non-numeric value, or omits it). Per layer: Rust `profit_taker_depth_level(i32)` / `stop_loss_depth_level(i32)` builders and `Option`; C `const int32_t *` fields appended at the end of `CSubmitAttachedParams` / `CReplaceAttachedParams` / `CAttachedOrderDetail` (the option structs grow, so rebuild against the new header and zero-initialize them); C++ `std::optional`; Java `setProfitTakerDepthLevel(Integer)` / `setStopLossDepthLevel(Integer)` / `getLimitDepthLevel()`; Node.js `profitTakerDepthLevel?` / `stopLossDepthLevel?` / `limitDepthLevel`; Python keyword arguments and `AttachedOrderDetail.limit_depth_level` + ## [5.2.1] - 2026-10-14 ### Added diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index c0ff5ecc3..4cb10fcec 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -4644,6 +4644,16 @@ typedef struct CReplaceAttachedParams { * Market price (can be null) */ const struct lb_decimal_t *market_price; + /** + * Bid/ask depth level for the take-profit limit order submitted price, + * valid when `activate_order_type` is `LIT` (can be null) + */ + const int32_t *profit_taker_depth_level; + /** + * Bid/ask depth level for the stop-loss limit order submitted price, + * valid when `activate_order_type` is `LIT` (can be null) + */ + const int32_t *stop_loss_depth_level; } CReplaceAttachedParams; /** @@ -4751,6 +4761,16 @@ typedef struct CSubmitAttachedParams { * RTH setting for activated order (can be null) */ const enum lb_outside_rth_t *activate_rth; + /** + * Bid/ask depth level for the take-profit limit order submitted price, + * valid when `activate_order_type` is `LIT` (can be null) + */ + const int32_t *profit_taker_depth_level; + /** + * Bid/ask depth level for the stop-loss limit order submitted price, + * valid when `activate_order_type` is `LIT` (can be null) + */ + const int32_t *stop_loss_depth_level; } CSubmitAttachedParams; /** @@ -5831,6 +5851,10 @@ typedef struct CAttachedOrderDetail { * Submit price (maybe null) */ const struct lb_decimal_t *submit_price; + /** + * Bid/ask depth level of the limit order submitted price (maybe null) + */ + const int32_t *limit_depth_level; } CAttachedOrderDetail; /** diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs index 2a222ab46..897eff6e1 100644 --- a/c/src/trade_context/context.rs +++ b/c/src/trade_context/context.rs @@ -524,6 +524,12 @@ pub unsafe extern "C" fn lb_trade_context_replace_order( if !ap.market_price.is_null() { rp = rp.market_price((*ap.market_price).value); } + if !ap.profit_taker_depth_level.is_null() { + rp = rp.profit_taker_depth_level(*ap.profit_taker_depth_level); + } + if !ap.stop_loss_depth_level.is_null() { + rp = rp.stop_loss_depth_level(*ap.stop_loss_depth_level); + } opts2 = opts2.attached_params(rp); } execute_async(callback, ctx, userdata, async move { @@ -623,6 +629,12 @@ pub unsafe extern "C" fn lb_trade_context_submit_order( if !ap.activate_rth.is_null() { sp = sp.activate_rth((*ap.activate_rth).into()); } + if !ap.profit_taker_depth_level.is_null() { + sp = sp.profit_taker_depth_level(*ap.profit_taker_depth_level); + } + if !ap.stop_loss_depth_level.is_null() { + sp = sp.stop_loss_depth_level(*ap.stop_loss_depth_level); + } opts2 = opts2.attached_params(sp); } execute_async(callback, ctx, userdata, async move { diff --git a/c/src/trade_context/types.rs b/c/src/trade_context/types.rs index 92c789fd4..081b2ac77 100644 --- a/c/src/trade_context/types.rs +++ b/c/src/trade_context/types.rs @@ -106,6 +106,8 @@ pub struct CAttachedOrderDetail { pub activate_rth: *const COutsideRTH, /// Submit price (maybe null) pub submit_price: *const CDecimal, + /// Bid/ask depth level of the limit order submitted price (maybe null) + pub limit_depth_level: *const i32, } #[derive(Debug)] @@ -131,6 +133,7 @@ pub(crate) struct CAttachedOrderDetailOwned { activate_order_type: OrderType, activate_rth: Option, submit_price: Option, + limit_depth_level: Option, } impl From for CAttachedOrderDetailOwned { @@ -157,6 +160,7 @@ impl From for CAttachedOrderDetailOwned { activate_order_type, activate_rth, submit_price, + limit_depth_level, } = detail; Self { order_id: order_id.into(), @@ -180,6 +184,7 @@ impl From for CAttachedOrderDetailOwned { activate_order_type, activate_rth: activate_rth.map(Into::into), submit_price: submit_price.map(Into::into), + limit_depth_level, } } } @@ -210,6 +215,7 @@ impl ToFFI for CAttachedOrderDetailOwned { activate_order_type, activate_rth, submit_price, + limit_depth_level, } = self; CAttachedOrderDetail { order_id: order_id.to_ffi_type(), @@ -254,6 +260,10 @@ impl ToFFI for CAttachedOrderDetailOwned { .as_ref() .map(ToFFI::to_ffi_type) .unwrap_or(std::ptr::null()), + limit_depth_level: limit_depth_level + .as_ref() + .map(|value| value as *const i32) + .unwrap_or(std::ptr::null()), } } } @@ -444,6 +454,12 @@ pub struct CSubmitAttachedParams { pub stop_loss_submit_price: *const CDecimal, /// RTH setting for activated order (can be null) pub activate_rth: *const COutsideRTH, + /// Bid/ask depth level for the take-profit limit order submitted price, + /// valid when `activate_order_type` is `LIT` (can be null) + pub profit_taker_depth_level: *const i32, + /// Bid/ask depth level for the stop-loss limit order submitted price, + /// valid when `activate_order_type` is `LIT` (can be null) + pub stop_loss_depth_level: *const i32, } /// Options for replace attached order params @@ -480,6 +496,12 @@ pub struct CReplaceAttachedParams { pub quantity: *const CDecimal, /// Market price (can be null) pub market_price: *const CDecimal, + /// Bid/ask depth level for the take-profit limit order submitted price, + /// valid when `activate_order_type` is `LIT` (can be null) + pub profit_taker_depth_level: *const i32, + /// Bid/ask depth level for the stop-loss limit order submitted price, + /// valid when `activate_order_type` is `LIT` (can be null) + pub stop_loss_depth_level: *const i32, } /// Order changed message diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index 5ecea2cd7..069660d3c 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1655,6 +1655,8 @@ struct AttachedOrderDetail OrderType activate_order_type; std::optional activate_rth; std::optional submit_price; + /// Bid/ask depth level of the limit order submitted price + std::optional limit_depth_level; }; /// Submit attached order params @@ -1669,6 +1671,12 @@ struct SubmitAttachedParams std::optional profit_taker_submit_price; std::optional stop_loss_submit_price; std::optional activate_rth; + /// Bid/ask depth level for the take-profit limit order submitted price, + /// valid when `activate_order_type` is `LIT` + std::optional profit_taker_depth_level; + /// Bid/ask depth level for the stop-loss limit order submitted price, valid + /// when `activate_order_type` is `LIT` + std::optional stop_loss_depth_level; }; /// Replace attached order params @@ -1689,6 +1697,12 @@ struct ReplaceAttachedParams std::optional main_id; std::optional quantity; std::optional market_price; + /// Bid/ask depth level for the take-profit limit order submitted price, + /// valid when `activate_order_type` is `LIT` + std::optional profit_taker_depth_level; + /// Bid/ask depth level for the stop-loss limit order submitted price, valid + /// when `activate_order_type` is `LIT` + std::optional stop_loss_depth_level; }; /// Multi-leg strategy diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index 1ebffb224..7ffdca025 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -1470,6 +1470,8 @@ convert(const CAttachedOrderDetail* detail) : std::nullopt, detail->submit_price ? std::optional{ Decimal(detail->submit_price) } : std::nullopt, + detail->limit_depth_level ? std::optional{ *detail->limit_depth_level } + : std::nullopt, }; } @@ -1514,6 +1516,10 @@ convert_submit_attached(const SubmitAttachedParams& src) s.activate_rth = convert(*src.activate_rth); s.params.activate_rth = &s.activate_rth; } + s.params.profit_taker_depth_level = + src.profit_taker_depth_level ? &*src.profit_taker_depth_level : nullptr; + s.params.stop_loss_depth_level = + src.stop_loss_depth_level ? &*src.stop_loss_depth_level : nullptr; return s; } @@ -1568,6 +1574,10 @@ convert_replace_attached(const ReplaceAttachedParams& src) 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; + s.params.profit_taker_depth_level = + src.profit_taker_depth_level ? &*src.profit_taker_depth_level : nullptr; + s.params.stop_loss_depth_level = + src.stop_loss_depth_level ? &*src.stop_loss_depth_level : nullptr; return s; } 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 b67562bad..7881b538e 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/AttachedOrderDetail.java @@ -29,6 +29,7 @@ public class AttachedOrderDetail { private OrderType activateOrderType; private OutsideRTH activateRth; private BigDecimal submitPrice; + private Integer limitDepthLevel; /** * Returns the order ID. @@ -219,6 +220,15 @@ public BigDecimal getSubmitPrice() { return submitPrice; } + /** + * Returns the bid/ask depth level of the limit order submitted price. + * + * @return depth level, or {@code null} if not set + */ + public Integer getLimitDepthLevel() { + return limitDepthLevel; + } + @Override public String toString() { return "AttachedOrderDetail [orderId=" + orderId + ", attachedTypeDisplay=" + attachedTypeDisplay @@ -228,6 +238,6 @@ public String toString() { + ", executedAmount=" + executedAmount + ", tag=" + tag + ", submittedAt=" + submittedAt + ", executedPrice=" + executedPrice + ", forceOnlyRth=" + forceOnlyRth + ", reviewed=" + reviewed + ", activateOrderType=" + activateOrderType + ", activateRth=" + activateRth + ", submitPrice=" - + submitPrice + "]"; + + submitPrice + ", limitDepthLevel=" + limitDepthLevel + "]"; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java index c1bc9d6ba..88f938c79 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceAttachedParams.java @@ -19,6 +19,8 @@ public class ReplaceAttachedParams { private Long mainId; private BigDecimal quantity; private BigDecimal marketPrice; + private Integer profitTakerDepthLevel; + private Integer stopLossDepthLevel; public ReplaceAttachedParams(AttachedOrderType attachedOrderType) { this.attachedOrderType = attachedOrderType; @@ -81,4 +83,26 @@ public ReplaceAttachedParams(AttachedOrderType attachedOrderType) { public ReplaceAttachedParams setMarketPrice(BigDecimal v) { this.marketPrice = v; return this; } public BigDecimal getMarketPrice() { return marketPrice; } + + /** + * Sets the bid/ask depth level for the take-profit limit order submitted + * price, valid when {@code activateOrderType} is {@code LIT}. + * + * @param v depth level + * @return this instance for chaining + */ + public ReplaceAttachedParams setProfitTakerDepthLevel(Integer v) { this.profitTakerDepthLevel = v; return this; } + + public Integer getProfitTakerDepthLevel() { return profitTakerDepthLevel; } + + /** + * Sets the bid/ask depth level for the stop-loss limit order submitted + * price, valid when {@code activateOrderType} is {@code LIT}. + * + * @param v depth level + * @return this instance for chaining + */ + public ReplaceAttachedParams setStopLossDepthLevel(Integer v) { this.stopLossDepthLevel = v; return this; } + + public Integer getStopLossDepthLevel() { return stopLossDepthLevel; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java b/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java index 83c4b53a8..1d705bdf0 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/SubmitAttachedParams.java @@ -13,6 +13,8 @@ public class SubmitAttachedParams { private BigDecimal profitTakerSubmitPrice; private BigDecimal stopLossSubmitPrice; private OutsideRTH activateRth; + private Integer profitTakerDepthLevel; + private Integer stopLossDepthLevel; public SubmitAttachedParams(AttachedOrderType attachedOrderType) { this.attachedOrderType = attachedOrderType; @@ -51,4 +53,26 @@ public SubmitAttachedParams(AttachedOrderType attachedOrderType) { public SubmitAttachedParams setActivateRth(OutsideRTH v) { this.activateRth = v; return this; } public OutsideRTH getActivateRth() { return activateRth; } + + /** + * Sets the bid/ask depth level for the take-profit limit order submitted + * price, valid when {@code activateOrderType} is {@code LIT}. + * + * @param v depth level + * @return this instance for chaining + */ + public SubmitAttachedParams setProfitTakerDepthLevel(Integer v) { this.profitTakerDepthLevel = v; return this; } + + public Integer getProfitTakerDepthLevel() { return profitTakerDepthLevel; } + + /** + * Sets the bid/ask depth level for the stop-loss limit order submitted + * price, valid when {@code activateOrderType} is {@code LIT}. + * + * @param v depth level + * @return this instance for chaining + */ + public SubmitAttachedParams setStopLossDepthLevel(Integer v) { this.stopLossDepthLevel = v; return this; } + + public Integer getStopLossDepthLevel() { return stopLossDepthLevel; } } diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index ef4be8386..2ab577fba 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -423,6 +423,15 @@ fn read_submit_attached_params( if let Some(v) = activate_rth { params = params.activate_rth(v); } + let profit_taker_depth_level: Option = + get_field(env, obj, "profitTakerDepthLevel")?; + if let Some(v) = profit_taker_depth_level { + params = params.profit_taker_depth_level(v.into()); + } + let stop_loss_depth_level: Option = get_field(env, obj, "stopLossDepthLevel")?; + if let Some(v) = stop_loss_depth_level { + params = params.stop_loss_depth_level(v.into()); + } Ok(params) } @@ -492,6 +501,15 @@ fn read_replace_attached_params( if let Some(v) = market_price { params = params.market_price(v); } + let profit_taker_depth_level: Option = + get_field(env, obj, "profitTakerDepthLevel")?; + if let Some(v) = profit_taker_depth_level { + params = params.profit_taker_depth_level(v.into()); + } + let stop_loss_depth_level: Option = get_field(env, obj, "stopLossDepthLevel")?; + if let Some(v) = stop_loss_depth_level { + params = params.stop_loss_depth_level(v.into()); + } Ok(params) } diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index b9dfe54c3..8b3e5b411 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -617,7 +617,8 @@ impl_java_class!( reviewed, activate_order_type, activate_rth, - submit_price + submit_price, + limit_depth_level ] ); diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index 5464597de..9a4213f07 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -229,6 +229,8 @@ export declare class AttachedOrderDetail { get activateRth(): OutsideRTH | null /** Submit price */ get submitPrice(): Decimal | null + /** Bid/ask depth level of the limit order submitted price */ + get limitDepthLevel(): number | null } /** Brokers */ @@ -8129,6 +8131,16 @@ export interface ReplaceAttachedParams { quantity?: Decimal /** Market price */ marketPrice?: Decimal + /** + * Bid/ask depth level for the take-profit limit order submitted price, + * valid when `activateOrderType` is `LIT` + */ + profitTakerDepthLevel?: number + /** + * Bid/ask depth level for the stop-loss limit order submitted price, + * valid when `activateOrderType` is `LIT` + */ + stopLossDepthLevel?: number } /** Options for replace grid trading order request */ @@ -8624,6 +8636,16 @@ export interface SubmitAttachedParams { stopLossSubmitPrice?: Decimal /** Activate RTH */ activateRth?: OutsideRTH + /** + * Bid/ask depth level for the take-profit limit order submitted price, + * valid when `activateOrderType` is `LIT` + */ + profitTakerDepthLevel?: number + /** + * Bid/ask depth level for the stop-loss limit order submitted price, + * valid when `activateOrderType` is `LIT` + */ + stopLossDepthLevel?: number } /** Options for submitting a fund order. */ diff --git a/nodejs/src/trade/requests/replace_order.rs b/nodejs/src/trade/requests/replace_order.rs index 1f3755afb..2ade98d74 100644 --- a/nodejs/src/trade/requests/replace_order.rs +++ b/nodejs/src/trade/requests/replace_order.rs @@ -40,6 +40,12 @@ pub struct ReplaceAttachedParams<'env> { pub quantity: Option>, /// Market price pub market_price: Option>, + /// Bid/ask depth level for the take-profit limit order submitted price, + /// valid when `activateOrderType` is `LIT` + pub profit_taker_depth_level: Option, + /// Bid/ask depth level for the stop-loss limit order submitted price, + /// valid when `activateOrderType` is `LIT` + pub stop_loss_depth_level: Option, } impl<'env> From> for longbridge::trade::ReplaceAttachedParams { @@ -87,6 +93,12 @@ impl<'env> From> for longbridge::trade::ReplaceAttac if let Some(v) = p.market_price { opts = opts.market_price(v.0); } + if let Some(v) = p.profit_taker_depth_level { + opts = opts.profit_taker_depth_level(v); + } + if let Some(v) = p.stop_loss_depth_level { + opts = opts.stop_loss_depth_level(v); + } opts } } diff --git a/nodejs/src/trade/requests/submit_order.rs b/nodejs/src/trade/requests/submit_order.rs index c40ab568c..30b55b1c9 100644 --- a/nodejs/src/trade/requests/submit_order.rs +++ b/nodejs/src/trade/requests/submit_order.rs @@ -29,6 +29,12 @@ pub struct SubmitAttachedParams<'env> { pub stop_loss_submit_price: Option>, /// Activate RTH pub activate_rth: Option, + /// Bid/ask depth level for the take-profit limit order submitted price, + /// valid when `activateOrderType` is `LIT` + pub profit_taker_depth_level: Option, + /// Bid/ask depth level for the stop-loss limit order submitted price, + /// valid when `activateOrderType` is `LIT` + pub stop_loss_depth_level: Option, } impl<'env> From> for longbridge::trade::SubmitAttachedParams { @@ -58,6 +64,12 @@ impl<'env> From> for longbridge::trade::SubmitAttache if let Some(v) = p.activate_rth { opts = opts.activate_rth(v.into()); } + if let Some(v) = p.profit_taker_depth_level { + opts = opts.profit_taker_depth_level(v); + } + if let Some(v) = p.stop_loss_depth_level { + opts = opts.stop_loss_depth_level(v); + } opts } } diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs index 37d926a46..0766f4c07 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -270,6 +270,9 @@ pub struct AttachedOrderDetail { /// Submit price #[js(opt)] submit_price: Option, + /// Bid/ask depth level of the limit order submitted price + #[js(opt)] + limit_depth_level: Option, } /// Multi-leg strategy diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 722f570f2..c643ec6a6 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -6300,9 +6300,18 @@ class AttachedOrderDetail: Submit price """ + limit_depth_level: Optional[int] + """ + Bid/ask depth level of the limit order submitted price + """ + class SubmitAttachedParams: """ Submit attached order parameters + + ``profit_taker_depth_level`` / ``stop_loss_depth_level`` set the bid/ask + depth level for the take-profit / stop-loss limit order submitted price, + valid when ``activate_order_type`` is ``LIT``. """ def __init__( @@ -6317,11 +6326,17 @@ class SubmitAttachedParams: profit_taker_submit_price: Optional[Decimal] = None, stop_loss_submit_price: Optional[Decimal] = None, activate_rth: Optional[Type[OutsideRTH]] = None, + profit_taker_depth_level: Optional[int] = None, + stop_loss_depth_level: Optional[int] = None, ) -> None: ... class ReplaceAttachedParams: """ Replace attached order parameters + + ``profit_taker_depth_level`` / ``stop_loss_depth_level`` set the bid/ask + depth level for the take-profit / stop-loss limit order submitted price, + valid when ``activate_order_type`` is ``LIT``. """ def __init__( @@ -6342,6 +6357,8 @@ class ReplaceAttachedParams: main_id: Optional[int] = None, quantity: Optional[Decimal] = None, market_price: Optional[Decimal] = None, + profit_taker_depth_level: Optional[int] = None, + stop_loss_depth_level: Optional[int] = None, ) -> None: ... class MultiLegStrategy: diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index 2b80162d0..ab00587a1 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -272,6 +272,9 @@ pub(crate) struct AttachedOrderDetail { /// Submit price #[py(opt)] submit_price: Option, + /// Bid/ask depth level of the limit order submitted price + #[py(opt)] + limit_depth_level: Option, } /// Submit attached order parameters @@ -283,7 +286,7 @@ pub(crate) struct SubmitAttachedParams(pub(crate) longbridge::trade::SubmitAttac 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))] + #[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_depth_level=None, stop_loss_depth_level=None))] fn new( attached_order_type: AttachedOrderType, profit_taker_price: Option, @@ -294,6 +297,8 @@ impl SubmitAttachedParams { profit_taker_submit_price: Option, stop_loss_submit_price: Option, activate_rth: Option, + profit_taker_depth_level: Option, + stop_loss_depth_level: Option, ) -> Self { let mut p = longbridge::trade::SubmitAttachedParams::new(attached_order_type.into()); if let Some(v) = profit_taker_price { @@ -320,6 +325,12 @@ impl SubmitAttachedParams { if let Some(v) = activate_rth { p = p.activate_rth(v.into()); } + if let Some(v) = profit_taker_depth_level { + p = p.profit_taker_depth_level(v); + } + if let Some(v) = stop_loss_depth_level { + p = p.stop_loss_depth_level(v); + } Self(p) } } @@ -332,7 +343,7 @@ pub(crate) struct ReplaceAttachedParams(pub(crate) longbridge::trade::ReplaceAtt #[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))] + #[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, profit_taker_depth_level=None, stop_loss_depth_level=None))] #[allow(clippy::too_many_arguments)] fn new( attached_order_type: AttachedOrderType, @@ -350,6 +361,8 @@ impl ReplaceAttachedParams { main_id: Option, quantity: Option, market_price: Option, + profit_taker_depth_level: Option, + stop_loss_depth_level: Option, ) -> Self { let mut p = longbridge::trade::ReplaceAttachedParams::new(attached_order_type.into()); if let Some(v) = profit_taker_price { @@ -394,6 +407,12 @@ impl ReplaceAttachedParams { if let Some(v) = market_price { p = p.market_price(v.into()); } + if let Some(v) = profit_taker_depth_level { + p = p.profit_taker_depth_level(v); + } + if let Some(v) = stop_loss_depth_level { + p = p.stop_loss_depth_level(v); + } Self(p) } } diff --git a/rust/src/serde_utils.rs b/rust/src/serde_utils.rs index 38443e184..0b66afbc1 100644 --- a/rust/src/serde_utils.rs +++ b/rust/src/serde_utils.rs @@ -509,6 +509,29 @@ where timestamp::deserialize(deserializer) } +/// Lenient variant of `int32_opt_0_is_none` for newly added response fields: +/// accepts an integer or a numeric string; `null`, `0`, or a value that is not +/// a valid `i32` becomes `None` instead of failing the whole response. +pub(crate) mod int32_opt_null_or_0_is_none { + use serde_json::Value; + + use super::*; + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let value = match Option::::deserialize(deserializer)? { + Some(Value::Number(value)) => value.as_i64(), + Some(Value::String(value)) => value.trim().parse::().ok(), + _ => None, + }; + Ok(value + .and_then(|value| i32::try_from(value).ok()) + .filter(|value| *value != 0)) + } +} + pub(crate) mod int32_opt_0_is_none { use super::*; diff --git a/rust/src/trade/requests/replace_order.rs b/rust/src/trade/requests/replace_order.rs index 4261f9a4b..9160f7519 100644 --- a/rust/src/trade/requests/replace_order.rs +++ b/rust/src/trade/requests/replace_order.rs @@ -212,6 +212,10 @@ pub struct ReplaceAttachedParams { quantity: Option, #[serde(skip_serializing_if = "Option::is_none")] market_price: Option, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_depth_level: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_depth_level: Option, } impl ReplaceAttachedParams { @@ -233,6 +237,8 @@ impl ReplaceAttachedParams { main_id: None, quantity: None, market_price: None, + profit_taker_depth_level: None, + stop_loss_depth_level: None, } } /// Set the take-profit trigger price @@ -291,6 +297,24 @@ impl ReplaceAttachedParams { ..self } } + + /// Set the bid/ask depth level for the take-profit limit order submitted + /// price, valid when `activate_order_type` is `LIT` + pub fn profit_taker_depth_level(self, v: i32) -> Self { + Self { + profit_taker_depth_level: Some(v), + ..self + } + } + + /// Set the bid/ask depth level for the stop-loss limit order submitted + /// price, valid when `activate_order_type` is `LIT` + pub fn stop_loss_depth_level(self, v: i32) -> Self { + Self { + stop_loss_depth_level: Some(v), + ..self + } + } /// Set the take-profit order ID (for modifying existing attached order) pub fn profit_taker_id(self, v: i64) -> Self { Self { @@ -377,4 +401,17 @@ mod tests { serde_json::json!(1767312000) ); } + + #[test] + fn attached_params_depth_level() { + let params = ReplaceAttachedParams::new(AttachedOrderType::Bracket); + let value = serde_json::to_value(¶ms).unwrap(); + assert!(value.get("profit_taker_depth_level").is_none()); + assert!(value.get("stop_loss_depth_level").is_none()); + + let params = params.profit_taker_depth_level(2).stop_loss_depth_level(5); + let value = serde_json::to_value(¶ms).unwrap(); + assert_eq!(value["profit_taker_depth_level"], serde_json::json!(2)); + assert_eq!(value["stop_loss_depth_level"], serde_json::json!(5)); + } } diff --git a/rust/src/trade/requests/submit_order.rs b/rust/src/trade/requests/submit_order.rs index 37b8c2252..85baf752e 100644 --- a/rust/src/trade/requests/submit_order.rs +++ b/rust/src/trade/requests/submit_order.rs @@ -239,6 +239,10 @@ pub struct SubmitAttachedParams { stop_loss_submit_price: Option, #[serde(skip_serializing_if = "Option::is_none")] activate_rth: Option, + #[serde(skip_serializing_if = "Option::is_none")] + profit_taker_depth_level: Option, + #[serde(skip_serializing_if = "Option::is_none")] + stop_loss_depth_level: Option, } impl SubmitAttachedParams { @@ -254,6 +258,8 @@ impl SubmitAttachedParams { profit_taker_submit_price: None, stop_loss_submit_price: None, activate_rth: None, + profit_taker_depth_level: None, + stop_loss_depth_level: None, } } /// Set the take-profit trigger price @@ -312,6 +318,24 @@ impl SubmitAttachedParams { ..self } } + + /// Set the bid/ask depth level for the take-profit limit order submitted + /// price, valid when `activate_order_type` is `LIT` + pub fn profit_taker_depth_level(self, v: i32) -> Self { + Self { + profit_taker_depth_level: Some(v), + ..self + } + } + + /// Set the bid/ask depth level for the stop-loss limit order submitted + /// price, valid when `activate_order_type` is `LIT` + pub fn stop_loss_depth_level(self, v: i32) -> Self { + Self { + stop_loss_depth_level: Some(v), + ..self + } + } } #[cfg(test)] @@ -352,4 +376,17 @@ mod tests { "a time that maps to 0 must not be sent as a regular order" ); } + + #[test] + fn attached_params_depth_level() { + let params = SubmitAttachedParams::new(AttachedOrderType::Bracket); + let value = serde_json::to_value(¶ms).unwrap(); + assert!(value.get("profit_taker_depth_level").is_none()); + assert!(value.get("stop_loss_depth_level").is_none()); + + let params = params.profit_taker_depth_level(2).stop_loss_depth_level(5); + let value = serde_json::to_value(¶ms).unwrap(); + assert_eq!(value["profit_taker_depth_level"], serde_json::json!(2)); + assert_eq!(value["stop_loss_depth_level"], serde_json::json!(5)); + } } diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index c6ca8a0a2..7a7302418 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -316,6 +316,13 @@ pub struct AttachedOrderDetail { /// Submit price (limit price) #[serde(with = "serde_utils::decimal_opt_empty_is_none")] pub submit_price: Option, + /// Bid/ask depth level of the limit order submitted price + #[serde( + default, + deserialize_with = "serde_utils::int32_opt_null_or_0_is_none::deserialize", + serialize_with = "serde_utils::int32_opt_0_is_none::serialize" + )] + pub limit_depth_level: Option, } /// Multi-leg strategy @@ -2250,4 +2257,53 @@ mod tests { assert_eq!(detail.delayed_until, expected, "{raw}"); } } + + #[test] + fn attached_order_limit_depth_level() { + let data = r#" + { + "order_id": "706388312699592705", + "attached_type_display": "STOP_LOSS", + "trigger_price": "10.500", + "quantity": "200", + "executed_qty": "0", + "status": "NewStatus", + "updated_at": "1651644898", + "withdrawn": false, + "gtd": "", + "time_in_force": "Day", + "counter_id": "", + "trigger_status": "NOT_USED", + "executed_amount": "0", + "tag": "Normal", + "submitted_at": "1651644897", + "executed_price": "0.000", + "force_only_rth": "RTH_ONLY", + "reviewed": false, + "activate_order_type": "MIT", + "activate_rth": "RTH_ONLY", + "submit_price": "", + "limit_depth_level": 0 + } + "#; + for (raw, expected) in [ + (serde_json::json!(0), None), + (serde_json::json!(3), Some(3)), + (serde_json::json!(-2), Some(-2)), + (serde_json::json!("3"), Some(3)), + (serde_json::json!("abc"), None), + (serde_json::Value::Null, None), + ] { + let mut value: serde_json::Value = serde_json::from_str(data).unwrap(); + value["limit_depth_level"] = raw.clone(); + let detail = serde_json::from_value::(value).unwrap(); + assert_eq!(detail.limit_depth_level, expected, "{raw}"); + } + + // Responses that predate the field still deserialize. + let mut value: serde_json::Value = serde_json::from_str(data).unwrap(); + value.as_object_mut().unwrap().remove("limit_depth_level"); + let detail = serde_json::from_value::(value).unwrap(); + assert!(detail.limit_depth_level.is_none()); + } }