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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

- **Trade: estimate multi-leg option trading info** — new `TradeContext.estimate_multileg_available_quantity` (`POST /v1/trade/estimate/multileg`) estimating a US option combination strategy's maximum tradable quantity and margin impact before submitting. Takes `side`, `order_type`, `submitted_quantity`, `strategy` (`MultiLegStrategy`), the combination `legs` (each an `EstimateMultiLegOrderLeg` — symbol plus a reserved `side`), and an optional `submitted_price`; returns `max_open_qty`, `unit_margin`, `initial_margin_change` and `maintenance_margin_change`. Unlike the single-leg `estimate_max_purchase_quantity` (a `GET` with scalar query params), the multi-leg estimate carries a `legs` array so it is a `POST` with a JSON body, matching the multi-leg submit endpoint. Added across the Rust core (async + blocking) and mirrored to the C, C++, Java, Node.js and Python layers; the separate Go SDK is tracked separately.

## [5.2.0] - 2026-09-30

### Added
Expand Down
4 changes: 4 additions & 0 deletions c/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ cpp_compat = true
"CPushGridOrderChanged" = "lb_push_grid_order_changed_t"
"CEstimateMaxPurchaseQuantityOptions" = "lb_estimate_max_purchase_quantity_options_t"
"CEstimateMaxPurchaseQuantityResponse" = "lb_estimate_max_purchase_quantity_response_t"
"CEstimateMultiLegOrderLeg" = "lb_estimate_multileg_order_leg_t"
"CEstimateMultiLegAvailableQuantityOptions" = "lb_estimate_multileg_available_quantity_options_t"
"CEstimateMultiLegAvailableQuantityResponse" = "lb_estimate_multileg_available_quantity_response_t"
"CCreateWatchlistGroup" = "lb_create_watchlist_group_t"
"CUpdateWatchlistGroup" = "lb_update_watchlist_group_t"
"CSecuritiesUpdateMode" = "lb_securities_update_mode_t"
Expand Down Expand Up @@ -514,6 +517,7 @@ include = [
# TradeContext: grid trading push (stays on trade side)
"CPushGridOrderChanged",
"CEstimateMaxPurchaseQuantityResponse",
"CEstimateMultiLegAvailableQuantityResponse",
"CSecurityCalcIndex",
"CWarrantInfo",
"CQuotePackageDetail",
Expand Down
94 changes: 94 additions & 0 deletions c/csrc/include/longbridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -4958,6 +4958,58 @@ typedef struct lb_estimate_max_purchase_quantity_options_t {
bool fractional_shares;
} lb_estimate_max_purchase_quantity_options_t;

/**
* A leg of a multi-leg combination for the pre-trade estimate
*/
typedef struct lb_estimate_multileg_order_leg_t {
/**
* Option or underlying-stock symbol, in `ticker.region` format
* (e.g. `QQQ260731C764000.US`)
*/
const char *symbol;
/**
* Leg order side (reserved; not in use for now) (can be null)
*/
const enum lb_order_side_t *side;
} lb_estimate_multileg_order_leg_t;

/**
* Options for estimating a multi-leg option combination's tradable quantity
* and margin impact
*/
typedef struct lb_estimate_multileg_available_quantity_options_t {
/**
* Order side of the combination
*/
enum lb_order_side_t side;
/**
* Order type
*/
enum lb_order_type_t order_type;
/**
* Submitted quantity (number of combinations)
*/
const struct lb_decimal_t *submitted_quantity;
/**
* Multi-leg strategy
*/
enum CMultiLegStrategy strategy;
/**
* Legs of the combination
*/
const struct lb_estimate_multileg_order_leg_t *legs;
/**
* Number of legs
*/
uintptr_t num_legs;
/**
* Submitted price (required for limit order types such as `LO`; the
* estimate is currently computed from market prices, so it does not change
* the result) (can be null)
*/
const struct lb_decimal_t *submitted_price;
} lb_estimate_multileg_available_quantity_options_t;

/**
* Active subscription for a security
*/
Expand Down Expand Up @@ -9069,6 +9121,37 @@ typedef struct lb_estimate_max_purchase_quantity_response_t {
const struct lb_decimal_t *margin_max_qty;
} lb_estimate_max_purchase_quantity_response_t;

/**
* Response for estimating a multi-leg option combination's tradable quantity
* and margin impact
*/
typedef struct lb_estimate_multileg_available_quantity_response_t {
/**
* Maximum number of combinations the account can open. The server returns
* `i64::MAX` (9223372036854775807) when the legs do not form the given
* strategy; treat that as no usable estimate
*/
const struct lb_decimal_t *max_open_qty;
/**
* Funds required per combination unit: the net debit at current market
* prices for debit (premium-paying) combinations, or the margin
* requirement for credit / short combinations
*/
const struct lb_decimal_t *unit_margin;
/**
* Change of the initial margin after placing `submitted_quantity`
* combinations (after minus before); `0` for debit combinations, which
* consume cash instead
*/
const struct lb_decimal_t *initial_margin_change;
/**
* Change of the maintenance margin after placing `submitted_quantity`
* combinations (after minus before); `0` for debit combinations, which
* consume cash instead
*/
const struct lb_decimal_t *maintenance_margin_change;
} lb_estimate_multileg_available_quantity_response_t;

/**
* Security calc index response
*/
Expand Down Expand Up @@ -16466,6 +16549,17 @@ void lb_trade_context_estimate_max_purchase_quantity(const struct lb_trade_conte
lb_async_callback_t callback,
void *userdata);

/**
* Estimate a multi-leg option combination's maximum tradable quantity and
* margin impact before submitting the order.
*
* @param[in] opts Options for the multi-leg estimate request
*/
void lb_trade_context_estimate_multileg_available_quantity(const struct lb_trade_context_t *ctx,
const struct lb_estimate_multileg_available_quantity_options_t *opts,
lb_async_callback_t callback,
void *userdata);

/**
* Create a decimal value with a 64 bit `m` representation and corresponding
* `e` scale.
Expand Down
63 changes: 56 additions & 7 deletions c/src/trade_context/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use longbridge::{
TradeContext,
trade::{
AttachedOrderType, CancelOrderOptions, EstimateMaxPurchaseQuantityOptions,
EstimateMultiLegAvailableQuantityOptions, EstimateMultiLegOrderLeg,
GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions,
GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions,
GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, PushEvent,
Expand All @@ -23,13 +24,15 @@ use crate::{
types::{
CAccountBalanceOwned, CAllExecutionsResponseOwned, CCashFlowOwned,
CEstimateMaxPurchaseQuantityOptions, CEstimateMaxPurchaseQuantityResponseOwned,
CExecutionOwned, CFundPositionsResponseOwned, CGetAllExecutionsOptions,
CGetCashFlowOptions, CGetFundPositionsOptions, CGetHistoryExecutionsOptions,
CGetHistoryOrdersOptions, CGetStockPositionsOptions, CGetTodayExecutionsOptions,
CGetTodayOrdersOptions, CMarginRatioOwned, COrderDetailOwned, COrderOwned,
CPushGridOrderChanged, CPushGridOrderChangedOwned, CPushOrderChanged,
CPushOrderChangedOwned, CReplaceOrderOptions, CStockPositionsResponseOwned,
CSubmitMultiLegOrderOptions, CSubmitOrderOptions, CSubmitOrderResponseOwned,
CEstimateMultiLegAvailableQuantityOptions,
CEstimateMultiLegAvailableQuantityResponseOwned, CExecutionOwned,
CFundPositionsResponseOwned, CGetAllExecutionsOptions, CGetCashFlowOptions,
CGetFundPositionsOptions, CGetHistoryExecutionsOptions, CGetHistoryOrdersOptions,
CGetStockPositionsOptions, CGetTodayExecutionsOptions, CGetTodayOrdersOptions,
CMarginRatioOwned, COrderDetailOwned, COrderOwned, CPushGridOrderChanged,
CPushGridOrderChangedOwned, CPushOrderChanged, CPushOrderChangedOwned,
CReplaceOrderOptions, CStockPositionsResponseOwned, CSubmitMultiLegOrderOptions,
CSubmitOrderOptions, CSubmitOrderResponseOwned,
},
},
types::{CCow, CVec, ToFFI, cstr_array_to_rust, cstr_to_rust, slice_from_raw_parts},
Expand Down Expand Up @@ -873,3 +876,49 @@ pub unsafe extern "C" fn lb_trade_context_estimate_max_purchase_quantity(
Ok(resp)
});
}

/// Estimate a multi-leg option combination's maximum tradable quantity and
/// margin impact before submitting the order.
///
/// @param[in] opts Options for the multi-leg estimate request
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lb_trade_context_estimate_multileg_available_quantity(
ctx: *const CTradeContext,
opts: *const CEstimateMultiLegAvailableQuantityOptions,
callback: CAsyncCallback,
userdata: *mut c_void,
) {
let ctx_inner = (*ctx).ctx.clone();
let side = (*opts).side.into();
let order_type = (*opts).order_type.into();
let submitted_quantity = (*(*opts).submitted_quantity).value;
let strategy = (*opts).strategy.into();
let legs = slice_from_raw_parts((*opts).legs, (*opts).num_legs)
.iter()
.map(|leg| {
let mut l = EstimateMultiLegOrderLeg::new(cstr_to_rust(leg.symbol));
if !leg.side.is_null() {
l = l.side((*leg.side).into());
}
l
})
.collect::<Vec<_>>();
let mut opts2 = EstimateMultiLegAvailableQuantityOptions::new(
side,
order_type,
submitted_quantity,
strategy,
legs,
);
if !(*opts).submitted_price.is_null() {
opts2 = opts2.submitted_price((*(*opts).submitted_price).value);
}
execute_async(callback, ctx, userdata, async move {
let resp: CCow<CEstimateMultiLegAvailableQuantityResponseOwned> = CCow::new(
ctx_inner
.estimate_multileg_available_quantity(opts2)
.await?,
);
Ok(resp)
});
}
102 changes: 96 additions & 6 deletions c/src/trade_context/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use longbridge::{
trade::{
AccountBalance, AllExecutionsResponse, AttachedOrderDetail, AttachedOrderType, BalanceType,
CashFlow, CashFlowDirection, CashInfo, ContractDirection,
EstimateMaxPurchaseQuantityResponse, Execution, FrozenTransactionFee, FundPosition,
FundPositionChannel, FundPositionsResponse, MarginRatio, MultiLegInfo, MultiLegOrderLeg,
MultiLegPosition, MultiLegStrategy, Order, OrderChargeDetail, OrderChargeFee,
OrderChargeItem, OrderDetail, OrderHistoryDetail, OrderSide, OrderStatus, OrderTag,
OrderType, PushGridOrderChanged, PushOrderChanged, StockPosition, StockPositionChannel,
StockPositionsResponse, SubmitOrderResponse, TimeInForceType,
EstimateMaxPurchaseQuantityResponse, EstimateMultiLegAvailableQuantityResponse, Execution,
FrozenTransactionFee, FundPosition, FundPositionChannel, FundPositionsResponse,
MarginRatio, MultiLegInfo, MultiLegOrderLeg, MultiLegPosition, MultiLegStrategy, Order,
OrderChargeDetail, OrderChargeFee, OrderChargeItem, OrderDetail, OrderHistoryDetail,
OrderSide, OrderStatus, OrderTag, OrderType, PushGridOrderChanged, PushOrderChanged,
StockPosition, StockPositionChannel, StockPositionsResponse, SubmitOrderResponse,
TimeInForceType,
},
};
use time::OffsetDateTime;
Expand Down Expand Up @@ -2769,6 +2770,95 @@ impl ToFFI for CEstimateMaxPurchaseQuantityResponseOwned {
}
}

/// A leg of a multi-leg combination for the pre-trade estimate
#[repr(C)]
pub struct CEstimateMultiLegOrderLeg {
/// Option or underlying-stock symbol, in `ticker.region` format
/// (e.g. `QQQ260731C764000.US`)
pub symbol: *const c_char,
/// Leg order side (reserved; not in use for now) (can be null)
pub side: *const COrderSide,
}

/// Options for estimating a multi-leg option combination's tradable quantity
/// and margin impact
#[repr(C)]
pub struct CEstimateMultiLegAvailableQuantityOptions {
/// Order side of the combination
pub side: COrderSide,
/// Order type
pub order_type: COrderType,
/// Submitted quantity (number of combinations)
pub submitted_quantity: *const CDecimal,
/// Multi-leg strategy
pub strategy: CMultiLegStrategy,
/// Legs of the combination
pub legs: *const CEstimateMultiLegOrderLeg,
/// Number of legs
pub num_legs: usize,
/// Submitted price (required for limit order types such as `LO`; the
/// estimate is currently computed from market prices, so it does not change
/// the result) (can be null)
pub submitted_price: *const CDecimal,
}

/// Response for estimating a multi-leg option combination's tradable quantity
/// and margin impact
#[repr(C)]
pub struct CEstimateMultiLegAvailableQuantityResponse {
/// Maximum number of combinations the account can open. The server returns
/// `i64::MAX` (9223372036854775807) when the legs do not form the given
/// strategy; treat that as no usable estimate
pub max_open_qty: *const CDecimal,
/// Funds required per combination unit: the net debit at current market
/// prices for debit (premium-paying) combinations, or the margin
/// requirement for credit / short combinations
pub unit_margin: *const CDecimal,
/// Change of the initial margin after placing `submitted_quantity`
/// combinations (after minus before); `0` for debit combinations, which
/// consume cash instead
pub initial_margin_change: *const CDecimal,
/// Change of the maintenance margin after placing `submitted_quantity`
/// combinations (after minus before); `0` for debit combinations, which
/// consume cash instead
pub maintenance_margin_change: *const CDecimal,
}

#[derive(Debug)]
#[repr(C)]
pub struct CEstimateMultiLegAvailableQuantityResponseOwned {
max_open_qty: CDecimal,
unit_margin: CDecimal,
initial_margin_change: CDecimal,
maintenance_margin_change: CDecimal,
}

impl From<EstimateMultiLegAvailableQuantityResponse>
for CEstimateMultiLegAvailableQuantityResponseOwned
{
fn from(value: EstimateMultiLegAvailableQuantityResponse) -> Self {
CEstimateMultiLegAvailableQuantityResponseOwned {
max_open_qty: value.max_open_qty.into(),
unit_margin: value.unit_margin.into(),
initial_margin_change: value.initial_margin_change.into(),
maintenance_margin_change: value.maintenance_margin_change.into(),
}
}
}

impl ToFFI for CEstimateMultiLegAvailableQuantityResponseOwned {
type FFIType = CEstimateMultiLegAvailableQuantityResponse;

fn to_ffi_type(&self) -> Self::FFIType {
CEstimateMultiLegAvailableQuantityResponse {
max_open_qty: self.max_open_qty.to_ffi_type(),
unit_margin: self.unit_margin.to_ffi_type(),
initial_margin_change: self.initial_margin_change.to_ffi_type(),
maintenance_margin_change: self.maintenance_margin_change.to_ffi_type(),
}
}
}

// ── Grid trading push (stays on the trade context)
// ──────────────────────────────────

Expand Down
7 changes: 7 additions & 0 deletions cpp/include/trade_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ class TradeContext
AsyncCallback<TradeContext, EstimateMaxPurchaseQuantityResponse> callback)
const;

/// Estimate a multi-leg option combination's maximum tradable quantity and
/// margin impact before submitting the order
void estimate_multileg_available_quantity(
const EstimateMultiLegAvailableQuantityOptions& opts,
AsyncCallback<TradeContext, EstimateMultiLegAvailableQuantityResponse>
callback) const;

/// Set grid order changed callback, after receiving the grid order changed
/// event, it will call back to this function.
void set_on_grid_order_changed(
Expand Down
Loading
Loading