From fd8254382a9862c0cadfbebe5516c26cc75493fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Thu, 22 Oct 2026 16:19:44 +0800 Subject: [PATCH 01/16] fix(trade): restrict fund_positions and margin_ratio to AP accounts US data-center accounts have no fund holdings or margin-ratio data, so these calls now fail fast with a region error instead of an opaque server error. Document the restriction in the Rust, C, C++ and Java method docs. --- c/csrc/include/longbridge.h | 4 ++++ c/src/trade_context/context.rs | 4 ++++ cpp/include/trade_context.hpp | 4 ++++ .../src/main/java/com/longbridge/trade/TradeContext.java | 4 ++++ rust/src/trade/context.rs | 8 ++++++++ 5 files changed, 24 insertions(+) diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index 4cb10fcec..c8456ae57 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -16506,6 +16506,8 @@ void lb_trade_context_cash_flow(const struct lb_trade_context_t *ctx, /** * Get fund positions * + * AP accounts only: US data-center accounts have no fund holdings. + * * @param[in] opts Options for get fund positions request */ void lb_trade_context_fund_positions(const struct lb_trade_context_t *ctx, @@ -16525,6 +16527,8 @@ void lb_trade_context_stock_positions(const struct lb_trade_context_t *ctx, /** * Get margin ratio + * + * AP accounts only: US data-center accounts have no margin-ratio data. */ void lb_trade_context_margin_ratio(const struct lb_trade_context_t *ctx, const char *symbol, diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs index 897eff6e1..f61d82601 100644 --- a/c/src/trade_context/context.rs +++ b/c/src/trade_context/context.rs @@ -777,6 +777,8 @@ pub unsafe extern "C" fn lb_trade_context_cash_flow( /// Get fund positions /// +/// AP accounts only: US data-center accounts have no fund holdings. +/// /// @param[in] opts Options for get fund positions request #[unsafe(no_mangle)] pub unsafe extern "C" fn lb_trade_context_fund_positions( @@ -820,6 +822,8 @@ pub unsafe extern "C" fn lb_trade_context_stock_positions( } /// Get margin ratio +/// +/// AP accounts only: US data-center accounts have no margin-ratio data. #[unsafe(no_mangle)] pub unsafe extern "C" fn lb_trade_context_margin_ratio( ctx: *const CTradeContext, diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp index 32e85f08c..00938b455 100644 --- a/cpp/include/trade_context.hpp +++ b/cpp/include/trade_context.hpp @@ -104,6 +104,8 @@ class TradeContext AsyncCallback> callback) const; /// Get fund positions + /// + /// AP accounts only: US data-center accounts have no fund holdings. void fund_positions( const std::optional& opts, AsyncCallback callback) const; @@ -114,6 +116,8 @@ class TradeContext AsyncCallback callback) const; /// Get margin ratio + /// + /// AP accounts only: US data-center accounts have no margin-ratio data. void margin_ratio(const std::string& symbol, AsyncCallback callback) const; 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 71b46c4c8..1493d9a4f 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -537,6 +537,8 @@ public synchronized CompletableFuture getCashFlow(GetCashFlowOptions /** * Get fund positions + *

+ * AP accounts only: US data-center accounts have no fund holdings. * *

      * {@code
@@ -601,6 +603,8 @@ public synchronized CompletableFuture getStockPositions(
 
     /**
      * Get margin ratio
+     * 

+ * AP accounts only: US data-center accounts have no margin-ratio data. * *

      * {@code
diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs
index aa4402bfd..759afbbe4 100644
--- a/rust/src/trade/context.rs
+++ b/rust/src/trade/context.rs
@@ -729,6 +729,9 @@ impl TradeContext {
     ///
     /// Reference: 
     ///
+    /// AP accounts only: US data-center accounts have no fund holdings, and
+    /// the call returns an error for them.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -756,6 +759,7 @@ impl TradeContext {
             .0
             .http_cli
             .request(Method::GET, "/v1/asset/fund")
+            .dc_restrict(DcRegion::Ap)
             .query_params(opts.into().unwrap_or_default())
             .response::>()
             .send()
@@ -807,6 +811,9 @@ impl TradeContext {
     ///
     /// Reference: 
     ///
+    /// AP accounts only: US data-center accounts have no margin-ratio data,
+    /// and the call returns an error for them.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -836,6 +843,7 @@ impl TradeContext {
             .0
             .http_cli
             .request(Method::GET, "/v1/risk/margin-ratio")
+            .dc_restrict(DcRegion::Ap)
             .query_params(Request {
                 symbol: symbol.into(),
             })

From 1da5510a4e3421ef822569d2b9e8d503afe7faae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Thu, 22 Oct 2026 16:19:52 +0800
Subject: [PATCH 02/16] feat(trade): add US cash, holding, transaction and
 exchange-rate APIs

Add four US-only TradeContext methods (async + blocking), all behind
dc_restrict(DcRegion::Us):

- us_cash_and_buy_power: GET /v1/us/assets/cash-and-buy-power
- us_available_holding: GET /v1/us/assets/available-holding
- us_transaction_history: POST /v1/us/assets/transaction-history
- us_exchange_rates: GET /v1/us/assets/exchange-rates

The exchange-rate response is a two-level map upstream; flatten it into
a sorted list that keeps both map keys on each entry.
---
 rust/src/blocking/trade.rs |  39 +++-
 rust/src/lib.rs            |   9 +-
 rust/src/trade/context.rs  | 143 +++++++++++++-
 rust/src/trade/mod.rs      |  11 ++
 rust/src/trade/types.rs    | 391 +++++++++++++++++++++++++++++++++++++
 5 files changed, 584 insertions(+), 9 deletions(-)

diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs
index e19498d6e..74e9a9472 100644
--- a/rust/src/blocking/trade.rs
+++ b/rust/src/blocking/trade.rs
@@ -9,10 +9,12 @@ use crate::{
         FundPositionsResponse, GetAllExecutionsOptions, GetCashFlowOptions,
         GetFundPositionsOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions,
         GetOrderDetailOptions, GetStockPositionsOptions, GetTodayExecutionsOptions,
-        GetTodayOrdersOptions, GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order,
-        OrderDetail, PushEvent, QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse,
+        GetTodayOrdersOptions, GetUSAvailableHoldingOptions, GetUSHistoryOrders,
+        GetUSRealizedPLOptions, GetUSTransactionHistoryOptions, MarginRatio, Order, OrderDetail,
+        PushEvent, QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse,
         SubmitMultiLegOrderOptions, SubmitOrderOptions, SubmitOrderResponse, TopicType,
-        TradeContext, USAssetOverview, USOrderDetailResponse, USRealizedPL,
+        TradeContext, USAssetOverview, USAvailableHolding, USCashAndBuyPower, USExchangeRates,
+        USOrderDetailResponse, USRealizedPL, USTransactionHistory,
     },
 };
 
@@ -512,4 +514,35 @@ impl TradeContextSync {
         self.rt
             .call(move |ctx| async move { ctx.us_realized_pl(opts).await })
     }
+
+    /// Get cash, buying power and margin status for the US account (blocking)
+    pub fn us_cash_and_buy_power(&self, currency: Option<&str>) -> Result {
+        let currency = currency.map(ToString::to_string);
+        self.rt
+            .call(move |ctx| async move { ctx.us_cash_and_buy_power(currency.as_deref()).await })
+    }
+
+    /// Get the quantity of a US holding that is available to trade (blocking)
+    pub fn us_available_holding(
+        &self,
+        opts: GetUSAvailableHoldingOptions,
+    ) -> Result {
+        self.rt
+            .call(move |ctx| async move { ctx.us_available_holding(opts).await })
+    }
+
+    /// Get the US account transaction history (blocking)
+    pub fn us_transaction_history(
+        &self,
+        opts: GetUSTransactionHistoryOptions,
+    ) -> Result {
+        self.rt
+            .call(move |ctx| async move { ctx.us_transaction_history(opts).await })
+    }
+
+    /// Get the exchange rates used by the US account (blocking)
+    pub fn us_exchange_rates(&self) -> Result {
+        self.rt
+            .call(move |ctx| async move { ctx.us_exchange_rates().await })
+    }
 }
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index e8f47ad6c..3e46352cb 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -65,8 +65,11 @@ pub use screener::ScreenerContext;
 pub use sharelist::SharelistContext;
 pub use signal::SignalContext;
 pub use trade::{
-    GetUSHistoryOrders, GetUSRealizedPLOptions, QueryUSOrdersOptions, QueryUSOrdersResponse,
-    TradeContext, USAssetOverview, USCashEntry, USCryptoEntry, USOrderDetailResponse,
-    USOrderHistory, USRealizedPL, USRealizedPLEntry, USRealizedPLMetric,
+    GetUSAvailableHoldingOptions, GetUSHistoryOrders, GetUSRealizedPLOptions,
+    GetUSTransactionHistoryOptions, QueryUSOrdersOptions, QueryUSOrdersResponse, TradeContext,
+    USAssetOverview, USAvailableHolding, USBuyLimitInfo, USCashAndBuyPower, USCashDetail,
+    USCashEntry, USCryptoEntry, USExchangeRate, USExchangeRates, USMarginStatus,
+    USOrderDetailResponse, USOrderHistory, USRealizedPL, USRealizedPLEntry, USRealizedPLMetric,
+    USTransaction, USTransactionHistory,
 };
 pub use types::Market;
diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs
index 759afbbe4..83a690407 100644
--- a/rust/src/trade/context.rs
+++ b/rust/src/trade/context.rs
@@ -15,10 +15,12 @@ use crate::{
         GetAllExecutionsOptions, GetCashFlowOptions, GetFundPositionsOptions,
         GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetOrderDetailOptions,
         GetStockPositionsOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions,
-        GetUSHistoryOrders, GetUSRealizedPLOptions, MarginRatio, Order, OrderDetail, OrderSide,
-        PushEvent, QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse,
+        GetUSAvailableHoldingOptions, GetUSHistoryOrders, GetUSRealizedPLOptions,
+        GetUSTransactionHistoryOptions, MarginRatio, Order, OrderDetail, OrderSide, PushEvent,
+        QueryUSOrdersResponse, ReplaceOrderOptions, StockPositionsResponse,
         SubmitMultiLegOrderOptions, SubmitOrderOptions, TopicType, USAssetOverview,
-        USOrderDetailResponse, USRealizedPL,
+        USAvailableHolding, USCashAndBuyPower, USExchangeRates, USOrderDetailResponse,
+        USRealizedPL, USTransactionHistory,
         core::{Command, Core},
     },
 };
@@ -1101,4 +1103,139 @@ impl TradeContext {
             .await?
             .0)
     }
+
+    /// Get cash, buying power and margin status for the US account.
+    ///
+    /// `currency`: optional, e.g. `"USD"`.
+    ///
+    /// Path: `GET /v1/us/assets/cash-and-buy-power`
+    ///
+    /// US token required.
+    pub async fn us_cash_and_buy_power(&self, currency: Option<&str>) -> Result {
+        #[derive(Serialize)]
+        struct Query<'a> {
+            #[serde(skip_serializing_if = "Option::is_none")]
+            currency: Option<&'a str>,
+        }
+
+        Ok(self
+            .0
+            .http_cli
+            .request(Method::GET, "/v1/us/assets/cash-and-buy-power")
+            .dc_restrict(DcRegion::Us)
+            .query_params(Query { currency })
+            .response::>()
+            .send()
+            .with_subscriber(self.0.log_subscriber.clone())
+            .await?
+            .0)
+    }
+
+    /// Get the quantity of a US holding that is available to trade.
+    ///
+    /// Path: `GET /v1/us/assets/available-holding`
+    ///
+    /// US token required.
+    pub async fn us_available_holding(
+        &self,
+        opts: GetUSAvailableHoldingOptions,
+    ) -> Result {
+        #[derive(Serialize)]
+        struct Query {
+            symbol: String,
+            equity_type: String,
+            position_side: String,
+        }
+
+        let equity_type = if opts.equity_type.is_empty() {
+            "STOCK".to_string()
+        } else {
+            opts.equity_type
+        };
+        let position_side = if opts.position_side.is_empty() {
+            "LONG".to_string()
+        } else {
+            opts.position_side
+        };
+
+        Ok(self
+            .0
+            .http_cli
+            .request(Method::GET, "/v1/us/assets/available-holding")
+            .dc_restrict(DcRegion::Us)
+            .query_params(Query {
+                symbol: opts.symbol,
+                equity_type,
+                position_side,
+            })
+            .response::>()
+            .send()
+            .with_subscriber(self.0.log_subscriber.clone())
+            .await?
+            .0)
+    }
+
+    /// Get the US account transaction history (deposits, withdrawals, trades,
+    /// corporate actions, transfers and so on), one page at a time.
+    ///
+    /// Path: `POST /v1/us/assets/transaction-history`
+    ///
+    /// US token required.
+    pub async fn us_transaction_history(
+        &self,
+        opts: GetUSTransactionHistoryOptions,
+    ) -> Result {
+        #[derive(Debug, Serialize)]
+        struct Body {
+            start: i64,
+            end: i64,
+            page: i32,
+            size: i32,
+            #[serde(skip_serializing_if = "Vec::is_empty")]
+            types: Vec,
+            #[serde(skip_serializing_if = "String::is_empty")]
+            currency: String,
+        }
+
+        Ok(self
+            .0
+            .http_cli
+            .request(Method::POST, "/v1/us/assets/transaction-history")
+            .dc_restrict(DcRegion::Us)
+            .body(Json(Body {
+                start: opts.start_at.unix_timestamp(),
+                end: opts.end_at.unix_timestamp(),
+                page: if opts.page > 0 { opts.page } else { 1 },
+                size: if opts.size > 0 { opts.size } else { 20 },
+                types: opts.types,
+                currency: opts.currency,
+            }))
+            .response::>()
+            .send()
+            .with_subscriber(self.0.log_subscriber.clone())
+            .await?
+            .0)
+    }
+
+    /// Get the exchange rates used by the US account.
+    ///
+    /// The upstream two-level map is flattened into
+    /// [`USExchangeRates::exchanges`]; each entry keeps the two upstream map
+    /// keys as `universal_key` and `spec_key`.
+    ///
+    /// Path: `GET /v1/us/assets/exchange-rates`
+    ///
+    /// US token required.
+    pub async fn us_exchange_rates(&self) -> Result {
+        Ok(self
+            .0
+            .http_cli
+            .request(Method::GET, "/v1/us/assets/exchange-rates")
+            .dc_restrict(DcRegion::Us)
+            .response::>()
+            .send()
+            .with_subscriber(self.0.log_subscriber.clone())
+            .await?
+            .0)
+    }
 }
diff --git a/rust/src/trade/mod.rs b/rust/src/trade/mod.rs
index 31ecdedcd..e35c7979e 100644
--- a/rust/src/trade/mod.rs
+++ b/rust/src/trade/mod.rs
@@ -35,8 +35,10 @@ pub use types::{
     FundPositionChannel,
     FundPositionsResponse,
     // US-market types
+    GetUSAvailableHoldingOptions,
     GetUSHistoryOrders,
     GetUSRealizedPLOptions,
+    GetUSTransactionHistoryOptions,
     MarginRatio,
     MultiLegInfo,
     MultiLegOrderLeg,
@@ -63,11 +65,18 @@ pub use types::{
     TriggerStatus,
     USAssetOverview,
     USAttachedOrder,
+    USAvailableHolding,
     USButtonControl,
+    USBuyLimitInfo,
+    USCashAndBuyPower,
+    USCashDetail,
     USCashEntry,
     USChargeDetail,
     USChargeItem,
     USCryptoEntry,
+    USExchangeRate,
+    USExchangeRates,
+    USMarginStatus,
     USOrderDetail,
     USOrderDetailResponse,
     USOrderHistory,
@@ -75,4 +84,6 @@ pub use types::{
     USRealizedPLEntry,
     USRealizedPLMetric,
     USStockEntry,
+    USTransaction,
+    USTransactionHistory,
 };
diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs
index 7a7302418..be995674c 100644
--- a/rust/src/trade/types.rs
+++ b/rust/src/trade/types.rs
@@ -1,3 +1,5 @@
+use std::collections::BTreeMap;
+
 use num_enum::{FromPrimitive, IntoPrimitive};
 use rust_decimal::Decimal;
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -1691,12 +1693,401 @@ pub struct USRealizedPL {
     pub realized_pl_list: Vec,
 }
 
+/// Request for [`crate::TradeContext::us_available_holding`].
+#[derive(Debug, Clone, Default)]
+pub struct GetUSAvailableHoldingOptions {
+    /// Security symbol, e.g. `"AAPL.US"`, an option symbol such as
+    /// `"AAPL261120C345000.US"`, or a crypto pair such as `"BTCUSD.BKKT"`.
+    pub symbol: String,
+    /// Holding type: `"STOCK"`, `"OPTION"` or `"CRYPTO"`. Defaults to
+    /// `"STOCK"` if empty.
+    pub equity_type: String,
+    /// Position side: `"LONG"` or `"SHORT"`. Defaults to `"LONG"` if empty.
+    pub position_side: String,
+}
+
+/// Response for [`crate::TradeContext::us_available_holding`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USAvailableHolding {
+    /// Quantity available to sell (long) or buy to cover (short); negative for
+    /// a short position
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub available_qty: String,
+}
+
+/// Buying power in [`USCashAndBuyPower`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USBuyLimitInfo {
+    /// Currency
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub currency: String,
+    /// Cash buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub cash_buy_power: String,
+    /// Overnight buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub overnight_buy_power: String,
+    /// Day-trade buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub day_trade_buy_power: String,
+    /// Instant buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub instant_buy_power: String,
+    /// Intraday margin buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub intraday_margin_buy_power: String,
+    /// Option buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub option_buy_power: String,
+    /// Crypto buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub crypto_buy_power: String,
+    /// Margin maintenance excess used for buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub mme: String,
+}
+
+/// One cash currency entry in [`USCashAndBuyPower`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USCashDetail {
+    /// Currency
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub currency: String,
+    /// Total cash
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub total_amount: String,
+    /// Available cash
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub available_cash: String,
+    /// Withdrawable cash
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub withdraw_cash: String,
+    /// Cash frozen for pending orders
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub frozen_cash: String,
+    /// Settled cash
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub settled_cash: String,
+    /// Outstanding (unsettled) amount
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub outstanding: String,
+    /// Amount held for external use (e.g. IPO or fund subscriptions)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub external_amount: String,
+}
+
+/// Margin account status in [`USCashAndBuyPower`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USMarginStatus {
+    /// Currency
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub currency: String,
+    /// Net liquidation value
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub nlv: String,
+    /// Margin equity
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub margin_equity: String,
+    /// Margin maintenance excess
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub margin_maintenance_excess: String,
+    /// Maintenance margin requirement
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub mmr: String,
+    /// Regulation T excess
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub reg_t_excess: String,
+    /// Special memorandum account
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub sma: String,
+}
+
+/// Response for [`crate::TradeContext::us_cash_and_buy_power`].
+/// Field names match `GET /v1/us/assets/cash-and-buy-power`.
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USCashAndBuyPower {
+    /// Buying power
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub buy_limit_info: USBuyLimitInfo,
+    /// Cash per currency
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub cash_list: Vec,
+    /// Margin account status
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub margin_status: USMarginStatus,
+}
+
+/// Request for [`crate::TradeContext::us_transaction_history`].
+#[derive(Debug, Clone)]
+pub struct GetUSTransactionHistoryOptions {
+    /// Start time (inclusive)
+    pub start_at: OffsetDateTime,
+    /// End time (inclusive)
+    pub end_at: OffsetDateTime,
+    /// Page number, starting at 1. Defaults to 1 if 0.
+    pub page: i32,
+    /// Page size. Defaults to 20 if 0.
+    pub size: i32,
+    /// Transaction types to include; empty means all. Values:
+    /// `"TT_DEPOSIT_AND_WITHDRAWAL"`, `"TT_STOCKS_AND_ETFS_TRADING"`,
+    /// `"TT_OPTIONS_TRADING"`, `"TT_CRYPTO_TRADING"`, `"TT_CORPORATE_ACTION"`,
+    /// `"TT_TRANSFER_IN_AND_OUT"`, `"TT_CRYPTO_TRANSFER"`,
+    /// `"TT_MARGIN_FINANCING"`, `"TT_OTHER"`.
+    pub types: Vec,
+    /// Currency filter, e.g. `"USD"`; empty means all.
+    pub currency: String,
+}
+
+impl GetUSTransactionHistoryOptions {
+    /// Create a request for transactions between `start_at` and `end_at`.
+    pub fn new(start_at: OffsetDateTime, end_at: OffsetDateTime) -> Self {
+        Self {
+            start_at,
+            end_at,
+            page: 1,
+            size: 20,
+            types: Vec::new(),
+            currency: String::new(),
+        }
+    }
+}
+
+/// One record in [`USTransactionHistory`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USTransaction {
+    /// Display title
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub title: String,
+    /// Business code
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub biz_code: String,
+    /// Business code name
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub biz_code_name: String,
+    /// Amount; negative for outflows
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub amount: String,
+    /// Currency
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub currency: String,
+    /// Security symbol, if the record relates to one (e.g. `"AAPL.US"`)
+    #[serde(
+        default,
+        alias = "counter_id",
+        deserialize_with = "crate::serde_utils::null_as_default"
+    )]
+    pub symbol: String,
+    /// Market code
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub market: String,
+    /// Holding type
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub equity_type: String,
+    /// Position side
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub position_side: String,
+    /// Quantity
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub quantity: String,
+    /// Quantity description
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub quantity_description: String,
+    /// Reference number
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub ref_no: String,
+    /// Remark
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub remark: String,
+    /// Creation time
+    #[serde(
+        default,
+        deserialize_with = "crate::serde_utils::timestamp_opt_lenient::deserialize",
+        serialize_with = "crate::serde_utils::rfc3339_opt::serialize"
+    )]
+    pub create_at: Option,
+    /// Completion time
+    #[serde(
+        default,
+        deserialize_with = "crate::serde_utils::timestamp_opt_lenient::deserialize",
+        serialize_with = "crate::serde_utils::rfc3339_opt::serialize"
+    )]
+    pub done_at: Option,
+}
+
+/// Response for [`crate::TradeContext::us_transaction_history`].
+/// Field names match `POST /v1/us/assets/transaction-history`.
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USTransactionHistory {
+    /// Current page number
+    #[serde(default, with = "crate::serde_utils::int64_str")]
+    pub current_page: i64,
+    /// Records on this page. The API returns no total count: a page shorter
+    /// than the requested size is the last one.
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub items: Vec,
+}
+
+/// One exchange rate in [`USExchangeRates`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct USExchangeRate {
+    /// Outer upstream map key (`universal_exchanges` key)
+    pub universal_key: String,
+    /// Inner upstream map key (`spec_exchanges` key)
+    pub spec_key: String,
+    /// Base currency
+    pub base_currency: String,
+    /// Other (quote) currency
+    pub other_currency: String,
+    /// Bid rate
+    pub bid_rate: String,
+    /// Offer rate
+    pub offer_rate: String,
+    /// Average rate
+    pub average_rate: String,
+}
+
+/// Response for [`crate::TradeContext::us_exchange_rates`].
+///
+/// The upstream response is a two-level map
+/// (`universal_exchanges..spec_exchanges..exchanges[]`); it is
+/// flattened into a single list, sorted by the two upstream map keys, with
+/// the keys kept on each entry as [`USExchangeRate::universal_key`] and
+/// [`USExchangeRate::spec_key`].
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+#[serde(from = "RawUSExchangeRates")]
+pub struct USExchangeRates {
+    /// Exchange rates
+    pub exchanges: Vec,
+}
+
+#[derive(Deserialize, Default)]
+struct RawUSExchangeRate {
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    base_currency: String,
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    other_currency: String,
+    #[serde(default, with = "crate::serde_utils::value_as_opt_string")]
+    bid_rate: Option,
+    #[serde(default, with = "crate::serde_utils::value_as_opt_string")]
+    offer_rate: Option,
+    #[serde(default, with = "crate::serde_utils::value_as_opt_string")]
+    average_rate: Option,
+}
+
+#[derive(Deserialize, Default)]
+struct RawUSSpecExchanges {
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    exchanges: Vec,
+}
+
+#[derive(Deserialize, Default)]
+struct RawUSUniversalExchanges {
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    spec_exchanges: BTreeMap,
+}
+
+#[derive(Deserialize, Default)]
+struct RawUSExchangeRates {
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    universal_exchanges: BTreeMap,
+    /// Already-flattened form, so that the serialized [`USExchangeRates`]
+    /// deserializes back.
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    exchanges: Vec,
+}
+
+impl From for USExchangeRates {
+    fn from(raw: RawUSExchangeRates) -> Self {
+        let mut exchanges = raw.exchanges;
+        for (universal_key, universal) in raw.universal_exchanges {
+            for (spec_key, spec) in universal.spec_exchanges {
+                exchanges.extend(spec.exchanges.into_iter().map(|rate| USExchangeRate {
+                    universal_key: universal_key.clone(),
+                    spec_key: spec_key.clone(),
+                    base_currency: rate.base_currency,
+                    other_currency: rate.other_currency,
+                    bid_rate: rate.bid_rate.unwrap_or_default(),
+                    offer_rate: rate.offer_rate.unwrap_or_default(),
+                    average_rate: rate.average_rate.unwrap_or_default(),
+                }));
+            }
+        }
+        Self { exchanges }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use time::macros::datetime;
 
     use super::*;
 
+    #[test]
+    fn us_exchange_rates_response() {
+        let data = r#"{
+            "universal_exchanges": {
+                "2": {"spec_exchanges": {"USD": {"exchanges": [
+                    {"base_currency": "USD", "other_currency": "HKD", "bid_rate": 7.8, "offer_rate": "7.85", "average_rate": null}
+                ]}}},
+                "1": {"spec_exchanges": {
+                    "b": {"exchanges": [{"base_currency": "USD", "other_currency": "CNH", "bid_rate": "7.1"}]},
+                    "a": {"exchanges": null}
+                }}
+            }
+        }"#;
+        let resp: USExchangeRates = serde_json::from_str(data).unwrap();
+        assert_eq!(resp.exchanges.len(), 2);
+        assert_eq!(resp.exchanges[0].universal_key, "1");
+        assert_eq!(resp.exchanges[0].spec_key, "b");
+        assert_eq!(resp.exchanges[0].other_currency, "CNH");
+        assert_eq!(resp.exchanges[0].offer_rate, "");
+        assert_eq!(resp.exchanges[1].universal_key, "2");
+        assert_eq!(resp.exchanges[1].spec_key, "USD");
+        assert_eq!(resp.exchanges[1].bid_rate, "7.8");
+        assert_eq!(resp.exchanges[1].offer_rate, "7.85");
+        assert_eq!(resp.exchanges[1].average_rate, "");
+
+        let json = serde_json::to_string(&resp).unwrap();
+        let back: USExchangeRates = serde_json::from_str(&json).unwrap();
+        assert_eq!(back.exchanges.len(), 2);
+        assert_eq!(back.exchanges[1].bid_rate, "7.8");
+    }
+
+    #[test]
+    fn us_cash_and_buy_power_response() {
+        let data = r#"{
+            "buy_limit_info": {"currency": "USD", "cash_buy_power": "100.5", "overnight_buy_power": "90"},
+            "cash_list": [{"currency": "USD", "total_amount": "120", "available_cash": "100.5", "withdraw_cash": "80", "frozen_cash": null}],
+            "margin_status": {"currency": "USD", "nlv": "1200"}
+        }"#;
+        let resp: USCashAndBuyPower = serde_json::from_str(data).unwrap();
+        assert_eq!(resp.buy_limit_info.cash_buy_power, "100.5");
+        assert_eq!(resp.cash_list[0].withdraw_cash, "80");
+        assert_eq!(resp.cash_list[0].frozen_cash, "");
+        assert_eq!(resp.margin_status.nlv, "1200");
+    }
+
+    #[test]
+    fn us_transaction_history_response() {
+        let data = r#"{
+            "current_page": "2",
+            "items": [
+                {"title": "Buy", "amount": "-100", "currency": "USD", "symbol": "AAPL.US", "create_at": "1792600000", "done_at": 1792600060},
+                {"title": "Deposit", "amount": "500", "currency": "USD", "counter_id": "ST/US/AAPL", "done_at": "0"}
+            ]
+        }"#;
+        let resp: USTransactionHistory = serde_json::from_str(data).unwrap();
+        assert_eq!(resp.current_page, 2);
+        assert_eq!(resp.items[0].symbol, "AAPL.US");
+        assert_eq!(
+            resp.items[0].create_at.unwrap().unix_timestamp(),
+            1792600000
+        );
+        assert_eq!(resp.items[0].done_at.unwrap().unix_timestamp(), 1792600060);
+        assert_eq!(resp.items[1].symbol, "ST/US/AAPL");
+        assert!(resp.items[1].done_at.is_none());
+    }
+
     #[test]
     fn fund_position_response() {
         let data = r#"

From 8b4049a760cc2925acde665511d3c89a1dcb375a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Thu, 22 Oct 2026 16:19:52 +0800
Subject: [PATCH 03/16] feat(bindings): expose US asset APIs in Python, Node.js
 and Java

- Python: sync and async methods, result classes, and openapi.pyi stubs
- Node.js: napi methods, result types and GetUSTransactionHistoryOptions;
  regenerate index.d.ts
- Java: JNI entry points returning JSON, like the existing US stubs
- Note the AP-only restriction on fund_positions and margin_ratio in the
  Python and Node.js docs
---
 java/src/trade_context.rs                     | 107 ++++++-
 nodejs/index.d.ts                             | 137 ++++++++
 nodejs/src/trade/context.rs                   |  86 ++++-
 .../requests/get_us_transaction_history.rs    |  43 +++
 nodejs/src/trade/requests/mod.rs              |   2 +
 nodejs/src/trade/types.rs                     | 225 ++++++++++++-
 python/pysrc/longbridge/openapi.pyi           | 295 ++++++++++++++++++
 python/src/trade/context.rs                   |  66 ++++
 python/src/trade/context_async.rs             |  93 ++++++
 python/src/trade/mod.rs                       |   9 +
 python/src/trade/types.rs                     | 223 +++++++++++++
 11 files changed, 1282 insertions(+), 4 deletions(-)
 create mode 100644 nodejs/src/trade/requests/get_us_transaction_history.rs

diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs
index 2ab577fba..75c233a2f 100644
--- a/java/src/trade_context.rs
+++ b/java/src/trade_context.rs
@@ -23,7 +23,7 @@ use time::{Date, OffsetDateTime};
 
 use crate::{
     async_util,
-    error::jni_result,
+    error::{JniError, jni_result},
     types::{FromJValue, IntoJValue, JavaInteger, JavaLong, ObjectArray, get_field},
 };
 
@@ -1091,6 +1091,111 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextUsRealiz
     })
 }
 
+#[unsafe(no_mangle)]
+pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextUsCashAndBuyPower(
+    mut env: JNIEnv,
+    _class: JClass,
+    context: i64,
+    currency: JObject,
+    callback: JObject,
+) {
+    jni_result(&mut env, (), |env| {
+        let context = &*(context as *const ContextObj);
+        let __owned_ctx = context.ctx.clone();
+        let currency: Option = FromJValue::from_jvalue(env, currency.into())?;
+        async_util::execute(env, callback, async move {
+            let resp = __owned_ctx
+                .us_cash_and_buy_power(currency.as_deref())
+                .await?;
+            Ok(serde_json::to_string(&resp).unwrap_or_default())
+        })?;
+        Ok(())
+    })
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextUsAvailableHolding(
+    mut env: JNIEnv,
+    _class: JClass,
+    context: i64,
+    symbol: JObject,
+    equity_type: JObject,
+    position_side: JObject,
+    callback: JObject,
+) {
+    jni_result(&mut env, (), |env| {
+        let context = &*(context as *const ContextObj);
+        let __owned_ctx = context.ctx.clone();
+        let symbol: String = FromJValue::from_jvalue(env, symbol.into())?;
+        let equity_type: Option = FromJValue::from_jvalue(env, equity_type.into())?;
+        let position_side: Option = FromJValue::from_jvalue(env, position_side.into())?;
+        async_util::execute(env, callback, async move {
+            let resp = __owned_ctx
+                .us_available_holding(longbridge::trade::GetUSAvailableHoldingOptions {
+                    symbol,
+                    equity_type: equity_type.unwrap_or_default(),
+                    position_side: position_side.unwrap_or_default(),
+                })
+                .await?;
+            Ok(serde_json::to_string(&resp).unwrap_or_default())
+        })?;
+        Ok(())
+    })
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextUsTransactionHistory(
+    mut env: JNIEnv,
+    _class: JClass,
+    context: i64,
+    start_at: i64,
+    end_at: i64,
+    page: i64,
+    size: i64,
+    types: JObject,
+    currency: JObject,
+    callback: JObject,
+) {
+    jni_result(&mut env, (), |env| {
+        let context = &*(context as *const ContextObj);
+        let __owned_ctx = context.ctx.clone();
+        let types: ObjectArray = FromJValue::from_jvalue(env, types.into())?;
+        let currency: Option = FromJValue::from_jvalue(env, currency.into())?;
+        let start_at = OffsetDateTime::from_unix_timestamp(start_at)
+            .map_err(|err| JniError::Other(format!("invalid start_at: {err}")))?;
+        let end_at = OffsetDateTime::from_unix_timestamp(end_at)
+            .map_err(|err| JniError::Other(format!("invalid end_at: {err}")))?;
+        let mut opts = longbridge::trade::GetUSTransactionHistoryOptions::new(start_at, end_at);
+        opts.page = page as i32;
+        opts.size = size as i32;
+        opts.types = types.0;
+        opts.currency = currency.unwrap_or_default();
+        async_util::execute(env, callback, async move {
+            let resp = __owned_ctx.us_transaction_history(opts).await?;
+            Ok(serde_json::to_string(&resp).unwrap_or_default())
+        })?;
+        Ok(())
+    })
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextUsExchangeRates(
+    mut env: JNIEnv,
+    _class: JClass,
+    context: i64,
+    callback: JObject,
+) {
+    jni_result(&mut env, (), |env| {
+        let context = &*(context as *const ContextObj);
+        let __owned_ctx = context.ctx.clone();
+        async_util::execute(env, callback, async move {
+            let resp = __owned_ctx.us_exchange_rates().await?;
+            Ok(serde_json::to_string(&resp).unwrap_or_default())
+        })?;
+        Ok(())
+    })
+}
+
 #[unsafe(no_mangle)]
 pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextOrderDetailAttached(
     mut env: JNIEnv,
diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts
index 9a4213f07..5f9d8237d 100644
--- a/nodejs/index.d.ts
+++ b/nodejs/index.d.ts
@@ -4544,6 +4544,8 @@ export declare class TradeContext {
   /**
    * Get fund positions
    *
+   * AP accounts only: US data-center accounts have no fund holdings.
+   *
    * #### Example
    *
    * ```javascript
@@ -4574,6 +4576,8 @@ export declare class TradeContext {
   /**
    * Get margin ratio
    *
+   * AP accounts only: US data-center accounts have no margin-ratio data.
+   *
    * #### Example
    *
    * ```javascript
@@ -4612,6 +4616,28 @@ export declare class TradeContext {
   usAssetOverview(): Promise
   /** Get US realized P&L. US token required. */
   usRealizedPl(currency: string, category?: string | undefined | null): Promise
+  /**
+   * Get cash, buying power and margin status for the US account. US token
+   * required.
+   *
+   * `currency`: optional, e.g. `"USD"`.
+   */
+  usCashAndBuyPower(currency?: string | undefined | null): Promise
+  /**
+   * Get the quantity of a US holding that is available to trade. US token
+   * required.
+   *
+   * `equityType`: `"STOCK"` (default), `"OPTION"` or `"CRYPTO"`;
+   * `positionSide`: `"LONG"` (default) or `"SHORT"`.
+   */
+  usAvailableHolding(symbol: string, equityType?: string | undefined | null, positionSide?: string | undefined | null): Promise
+  /**
+   * Get the US account transaction history, one page at a time. US token
+   * required.
+   */
+  usTransactionHistory(opts: GetUSTransactionHistoryOptions): Promise
+  /** Get the exchange rates used by the US account. US token required. */
+  usExchangeRates(): Promise
   orderDetailAttached(orderId: string): Promise
   /**
    * Estimating the maximum purchase quantity for Hong Kong and US stocks,
@@ -6718,6 +6744,28 @@ export interface GetTodayOrdersOptions {
   isAttached?: boolean
 }
 
+/** Options for the US transaction history request */
+export interface GetUSTransactionHistoryOptions {
+  /** Start time (inclusive) */
+  startAt: Date
+  /** End time (inclusive) */
+  endAt: Date
+  /** Page number, starting at 1 (default 1) */
+  page?: number
+  /** Page size (default 20) */
+  size?: number
+  /**
+   * Transaction types to include; empty means all. Values:
+   * `TT_DEPOSIT_AND_WITHDRAWAL`, `TT_STOCKS_AND_ETFS_TRADING`,
+   * `TT_OPTIONS_TRADING`, `TT_CRYPTO_TRADING`, `TT_CORPORATE_ACTION`,
+   * `TT_TRANSFER_IN_AND_OUT`, `TT_CRYPTO_TRANSFER`, `TT_MARGIN_FINANCING`,
+   * `TT_OTHER`
+   */
+  types?: Array
+  /** Currency filter, e.g. `USD`; empty means all */
+  currency?: string
+}
+
 /** Data granularity */
 export declare const enum Granularity {
   /** Unknown */
@@ -9065,6 +9113,11 @@ export interface USAttachedOrder {
   withdrawn: boolean
 }
 
+/** Quantity of a US holding that is available to trade */
+export interface USAvailableHolding {
+  availableQty: string
+}
+
 /** Action-button state for an order. */
 export interface USButtonControl {
   withdraw: number
@@ -9072,6 +9125,38 @@ export interface USButtonControl {
   exceptionable: Array
 }
 
+/** Buying power in USCashAndBuyPower */
+export interface USBuyLimitInfo {
+  currency: string
+  cashBuyPower: string
+  overnightBuyPower: string
+  dayTradeBuyPower: string
+  instantBuyPower: string
+  intradayMarginBuyPower: string
+  optionBuyPower: string
+  cryptoBuyPower: string
+  mme: string
+}
+
+/** Cash, buying power and margin status of a US account */
+export interface USCashAndBuyPower {
+  buyLimitInfo: USBuyLimitInfo
+  cashList: Array
+  marginStatus: USMarginStatus
+}
+
+/** One cash currency entry in USCashAndBuyPower */
+export interface USCashDetail {
+  currency: string
+  totalAmount: string
+  availableCash: string
+  withdrawCash: string
+  frozenCash: string
+  settledCash: string
+  outstanding: string
+  externalAmount: string
+}
+
 /** One cash currency entry in USAssetOverview */
 export interface USCashEntry {
   currency: string
@@ -9222,6 +9307,22 @@ export interface USETFFilesResponse {
   files: Array
 }
 
+/** One exchange rate in USExchangeRates */
+export interface USExchangeRate {
+  universalKey: string
+  specKey: string
+  baseCurrency: string
+  otherCurrency: string
+  bidRate: string
+  offerRate: string
+  averageRate: string
+}
+
+/** Exchange rates of a US account */
+export interface USExchangeRates {
+  exchanges: Array
+}
+
 /** One balance-sheet entry in USFinancialOverview. */
 export interface USFinancialBSItem {
   debtAssetsRatio: string
@@ -9312,6 +9413,17 @@ export interface USKeyMetricItem {
   fields: Array
 }
 
+/** Margin account status in USCashAndBuyPower */
+export interface USMarginStatus {
+  currency: string
+  nlv: string
+  marginEquity: string
+  marginMaintenanceExcess: string
+  mmr: string
+  regTExcess: string
+  sma: string
+}
+
 /** Full typed order object within USOrderDetailResponse. */
 export interface USOrderDetail {
   id: string
@@ -9498,6 +9610,31 @@ export interface USStockEntry {
   industryName: string
 }
 
+/** One record in USTransactionHistory */
+export interface USTransaction {
+  title: string
+  bizCode: string
+  bizCodeName: string
+  amount: string
+  currency: string
+  symbol: string
+  market: string
+  equityType: string
+  positionSide: string
+  quantity: string
+  quantityDescription: string
+  refNo: string
+  remark: string
+  createAt?: Date
+  doneAt?: Date
+}
+
+/** One page of US account transaction history */
+export interface USTransactionHistory {
+  currentPage: number
+  items: Array
+}
+
 /** One valuation metric entry in USValuationOverview.metrics */
 export interface USValuationMetric {
   circle: string
diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs
index 2b3bb169c..09c675923 100644
--- a/nodejs/src/trade/context.rs
+++ b/nodejs/src/trade/context.rs
@@ -11,8 +11,8 @@ use crate::{
         requests::{
             EstimateMaxPurchaseQuantityOptions, GetAllExecutionsOptions, GetCashFlowOptions,
             GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetTodayExecutionsOptions,
-            GetTodayOrdersOptions, ReplaceOrderOptions, SubmitMultiLegOrderOptions,
-            SubmitOrderOptions,
+            GetTodayOrdersOptions, GetUSTransactionHistoryOptions, ReplaceOrderOptions,
+            SubmitMultiLegOrderOptions, SubmitOrderOptions,
         },
         types::{
             AccountBalance, AllExecutionsResponse, CashFlow, EstimateMaxPurchaseQuantityResponse,
@@ -510,6 +510,8 @@ impl TradeContext {
 
     /// Get fund positions
     ///
+    /// AP accounts only: US data-center accounts have no fund holdings.
+    ///
     /// #### Example
     ///
     /// ```javascript
@@ -558,6 +560,8 @@ impl TradeContext {
 
     /// Get margin ratio
     ///
+    /// AP accounts only: US data-center accounts have no margin-ratio data.
+    ///
     /// #### Example
     ///
     /// ```javascript
@@ -684,6 +688,84 @@ impl TradeContext {
         })
     }
 
+    /// Get cash, buying power and margin status for the US account. US token
+    /// required.
+    ///
+    /// `currency`: optional, e.g. `"USD"`.
+    #[napi]
+    pub fn us_cash_and_buy_power<'env>(
+        &self,
+        env: &'env Env,
+        currency: Option,
+    ) -> Result> {
+        let ctx = self.ctx.clone();
+        env.spawn_future(async move {
+            Ok(ctx
+                .us_cash_and_buy_power(currency.as_deref())
+                .await
+                .map_err(ErrorNewType)?
+                .into())
+        })
+    }
+
+    /// Get the quantity of a US holding that is available to trade. US token
+    /// required.
+    ///
+    /// `equityType`: `"STOCK"` (default), `"OPTION"` or `"CRYPTO"`;
+    /// `positionSide`: `"LONG"` (default) or `"SHORT"`.
+    #[napi]
+    pub fn us_available_holding<'env>(
+        &self,
+        env: &'env Env,
+        symbol: String,
+        equity_type: Option,
+        position_side: Option,
+    ) -> Result> {
+        let ctx = self.ctx.clone();
+        env.spawn_future(async move {
+            Ok(ctx
+                .us_available_holding(longbridge::trade::GetUSAvailableHoldingOptions {
+                    symbol,
+                    equity_type: equity_type.unwrap_or_default(),
+                    position_side: position_side.unwrap_or_default(),
+                })
+                .await
+                .map_err(ErrorNewType)?
+                .into())
+        })
+    }
+
+    /// Get the US account transaction history, one page at a time. US token
+    /// required.
+    #[napi]
+    pub fn us_transaction_history<'env>(
+        &self,
+        env: &'env Env,
+        opts: GetUSTransactionHistoryOptions,
+    ) -> Result> {
+        let ctx = self.ctx.clone();
+        let opts = opts.into();
+        env.spawn_future(async move {
+            Ok(ctx
+                .us_transaction_history(opts)
+                .await
+                .map_err(ErrorNewType)?
+                .into())
+        })
+    }
+
+    /// Get the exchange rates used by the US account. US token required.
+    #[napi]
+    pub fn us_exchange_rates<'env>(
+        &self,
+        env: &'env Env,
+    ) -> Result> {
+        let ctx = self.ctx.clone();
+        env.spawn_future(
+            async move { Ok(ctx.us_exchange_rates().await.map_err(ErrorNewType)?.into()) },
+        )
+    }
+
     #[napi]
     pub async fn order_detail_attached(&self, order_id: String) -> Result {
         self.ctx
diff --git a/nodejs/src/trade/requests/get_us_transaction_history.rs b/nodejs/src/trade/requests/get_us_transaction_history.rs
new file mode 100644
index 000000000..c77c09bf1
--- /dev/null
+++ b/nodejs/src/trade/requests/get_us_transaction_history.rs
@@ -0,0 +1,43 @@
+use chrono::{DateTime, Utc};
+
+use crate::utils::from_datetime;
+
+/// Options for the US transaction history request
+#[napi_derive::napi(object, js_name = "GetUSTransactionHistoryOptions")]
+pub struct GetUSTransactionHistoryOptions {
+    /// Start time (inclusive)
+    pub start_at: DateTime,
+    /// End time (inclusive)
+    pub end_at: DateTime,
+    /// Page number, starting at 1 (default 1)
+    pub page: Option,
+    /// Page size (default 20)
+    pub size: Option,
+    /// Transaction types to include; empty means all. Values:
+    /// `TT_DEPOSIT_AND_WITHDRAWAL`, `TT_STOCKS_AND_ETFS_TRADING`,
+    /// `TT_OPTIONS_TRADING`, `TT_CRYPTO_TRADING`, `TT_CORPORATE_ACTION`,
+    /// `TT_TRANSFER_IN_AND_OUT`, `TT_CRYPTO_TRANSFER`, `TT_MARGIN_FINANCING`,
+    /// `TT_OTHER`
+    pub types: Option>,
+    /// Currency filter, e.g. `USD`; empty means all
+    pub currency: Option,
+}
+
+impl From for longbridge::trade::GetUSTransactionHistoryOptions {
+    #[inline]
+    fn from(opts: GetUSTransactionHistoryOptions) -> Self {
+        let mut opts2 = longbridge::trade::GetUSTransactionHistoryOptions::new(
+            from_datetime(opts.start_at),
+            from_datetime(opts.end_at),
+        );
+        if let Some(page) = opts.page {
+            opts2.page = page;
+        }
+        if let Some(size) = opts.size {
+            opts2.size = size;
+        }
+        opts2.types = opts.types.unwrap_or_default();
+        opts2.currency = opts.currency.unwrap_or_default();
+        opts2
+    }
+}
diff --git a/nodejs/src/trade/requests/mod.rs b/nodejs/src/trade/requests/mod.rs
index e31531851..649ec90f6 100644
--- a/nodejs/src/trade/requests/mod.rs
+++ b/nodejs/src/trade/requests/mod.rs
@@ -5,6 +5,7 @@ mod get_history_executions;
 mod get_history_orders;
 mod get_today_executions;
 mod get_today_orders;
+mod get_us_transaction_history;
 mod replace_order;
 mod submit_multileg;
 mod submit_order;
@@ -16,6 +17,7 @@ pub use get_history_executions::GetHistoryExecutionsOptions;
 pub use get_history_orders::GetHistoryOrdersOptions;
 pub use get_today_executions::GetTodayExecutionsOptions;
 pub use get_today_orders::GetTodayOrdersOptions;
+pub use get_us_transaction_history::GetUSTransactionHistoryOptions;
 pub use replace_order::ReplaceOrderOptions;
 pub use submit_multileg::SubmitMultiLegOrderOptions;
 pub use submit_order::SubmitOrderOptions;
diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs
index 0766f4c07..d45f999d2 100644
--- a/nodejs/src/trade/types.rs
+++ b/nodejs/src/trade/types.rs
@@ -1,7 +1,7 @@
 use chrono::{DateTime, Utc};
 use longbridge_nodejs_macros::{JsEnum, JsObject};
 
-use crate::{decimal::Decimal, time::NaiveDate, types::Market};
+use crate::{decimal::Decimal, time::NaiveDate, types::Market, utils::to_datetime};
 
 /// Topic type
 #[napi_derive::napi]
@@ -1197,6 +1197,229 @@ impl From for USRealizedPL {
     }
 }
 
+/// Quantity of a US holding that is available to trade
+#[napi_derive::napi(object, js_name = "USAvailableHolding")]
+#[derive(Debug, Clone, Default)]
+pub struct USAvailableHolding {
+    pub available_qty: String,
+}
+
+impl From for USAvailableHolding {
+    fn from(v: longbridge::trade::USAvailableHolding) -> Self {
+        Self {
+            available_qty: v.available_qty,
+        }
+    }
+}
+
+/// Buying power in USCashAndBuyPower
+#[napi_derive::napi(object, js_name = "USBuyLimitInfo")]
+#[derive(Debug, Clone, Default)]
+pub struct USBuyLimitInfo {
+    pub currency: String,
+    pub cash_buy_power: String,
+    pub overnight_buy_power: String,
+    pub day_trade_buy_power: String,
+    pub instant_buy_power: String,
+    pub intraday_margin_buy_power: String,
+    pub option_buy_power: String,
+    pub crypto_buy_power: String,
+    pub mme: String,
+}
+
+impl From for USBuyLimitInfo {
+    fn from(v: longbridge::trade::USBuyLimitInfo) -> Self {
+        Self {
+            currency: v.currency,
+            cash_buy_power: v.cash_buy_power,
+            overnight_buy_power: v.overnight_buy_power,
+            day_trade_buy_power: v.day_trade_buy_power,
+            instant_buy_power: v.instant_buy_power,
+            intraday_margin_buy_power: v.intraday_margin_buy_power,
+            option_buy_power: v.option_buy_power,
+            crypto_buy_power: v.crypto_buy_power,
+            mme: v.mme,
+        }
+    }
+}
+
+/// One cash currency entry in USCashAndBuyPower
+#[napi_derive::napi(object, js_name = "USCashDetail")]
+#[derive(Debug, Clone, Default)]
+pub struct USCashDetail {
+    pub currency: String,
+    pub total_amount: String,
+    pub available_cash: String,
+    pub withdraw_cash: String,
+    pub frozen_cash: String,
+    pub settled_cash: String,
+    pub outstanding: String,
+    pub external_amount: String,
+}
+
+impl From for USCashDetail {
+    fn from(v: longbridge::trade::USCashDetail) -> Self {
+        Self {
+            currency: v.currency,
+            total_amount: v.total_amount,
+            available_cash: v.available_cash,
+            withdraw_cash: v.withdraw_cash,
+            frozen_cash: v.frozen_cash,
+            settled_cash: v.settled_cash,
+            outstanding: v.outstanding,
+            external_amount: v.external_amount,
+        }
+    }
+}
+
+/// Margin account status in USCashAndBuyPower
+#[napi_derive::napi(object, js_name = "USMarginStatus")]
+#[derive(Debug, Clone, Default)]
+pub struct USMarginStatus {
+    pub currency: String,
+    pub nlv: String,
+    pub margin_equity: String,
+    pub margin_maintenance_excess: String,
+    pub mmr: String,
+    pub reg_t_excess: String,
+    pub sma: String,
+}
+
+impl From for USMarginStatus {
+    fn from(v: longbridge::trade::USMarginStatus) -> Self {
+        Self {
+            currency: v.currency,
+            nlv: v.nlv,
+            margin_equity: v.margin_equity,
+            margin_maintenance_excess: v.margin_maintenance_excess,
+            mmr: v.mmr,
+            reg_t_excess: v.reg_t_excess,
+            sma: v.sma,
+        }
+    }
+}
+
+/// Cash, buying power and margin status of a US account
+#[napi_derive::napi(object, js_name = "USCashAndBuyPower")]
+#[derive(Debug, Clone, Default)]
+pub struct USCashAndBuyPower {
+    pub buy_limit_info: USBuyLimitInfo,
+    pub cash_list: Vec,
+    pub margin_status: USMarginStatus,
+}
+
+impl From for USCashAndBuyPower {
+    fn from(v: longbridge::trade::USCashAndBuyPower) -> Self {
+        Self {
+            buy_limit_info: v.buy_limit_info.into(),
+            cash_list: v.cash_list.into_iter().map(Into::into).collect(),
+            margin_status: v.margin_status.into(),
+        }
+    }
+}
+
+/// One record in USTransactionHistory
+#[napi_derive::napi(object, js_name = "USTransaction")]
+#[derive(Debug, Clone, Default)]
+pub struct USTransaction {
+    pub title: String,
+    pub biz_code: String,
+    pub biz_code_name: String,
+    pub amount: String,
+    pub currency: String,
+    pub symbol: String,
+    pub market: String,
+    pub equity_type: String,
+    pub position_side: String,
+    pub quantity: String,
+    pub quantity_description: String,
+    pub ref_no: String,
+    pub remark: String,
+    pub create_at: Option>,
+    pub done_at: Option>,
+}
+
+impl From for USTransaction {
+    fn from(v: longbridge::trade::USTransaction) -> Self {
+        Self {
+            title: v.title,
+            biz_code: v.biz_code,
+            biz_code_name: v.biz_code_name,
+            amount: v.amount,
+            currency: v.currency,
+            symbol: v.symbol,
+            market: v.market,
+            equity_type: v.equity_type,
+            position_side: v.position_side,
+            quantity: v.quantity,
+            quantity_description: v.quantity_description,
+            ref_no: v.ref_no,
+            remark: v.remark,
+            create_at: v.create_at.map(to_datetime),
+            done_at: v.done_at.map(to_datetime),
+        }
+    }
+}
+
+/// One page of US account transaction history
+#[napi_derive::napi(object, js_name = "USTransactionHistory")]
+#[derive(Debug, Clone, Default)]
+pub struct USTransactionHistory {
+    pub current_page: i64,
+    pub items: Vec,
+}
+
+impl From for USTransactionHistory {
+    fn from(v: longbridge::trade::USTransactionHistory) -> Self {
+        Self {
+            current_page: v.current_page,
+            items: v.items.into_iter().map(Into::into).collect(),
+        }
+    }
+}
+
+/// One exchange rate in USExchangeRates
+#[napi_derive::napi(object, js_name = "USExchangeRate")]
+#[derive(Debug, Clone, Default)]
+pub struct USExchangeRate {
+    pub universal_key: String,
+    pub spec_key: String,
+    pub base_currency: String,
+    pub other_currency: String,
+    pub bid_rate: String,
+    pub offer_rate: String,
+    pub average_rate: String,
+}
+
+impl From for USExchangeRate {
+    fn from(v: longbridge::trade::USExchangeRate) -> Self {
+        Self {
+            universal_key: v.universal_key,
+            spec_key: v.spec_key,
+            base_currency: v.base_currency,
+            other_currency: v.other_currency,
+            bid_rate: v.bid_rate,
+            offer_rate: v.offer_rate,
+            average_rate: v.average_rate,
+        }
+    }
+}
+
+/// Exchange rates of a US account
+#[napi_derive::napi(object, js_name = "USExchangeRates")]
+#[derive(Debug, Clone, Default)]
+pub struct USExchangeRates {
+    pub exchanges: Vec,
+}
+
+impl From for USExchangeRates {
+    fn from(v: longbridge::trade::USExchangeRates) -> Self {
+        Self {
+            exchanges: v.exchanges.into_iter().map(Into::into).collect(),
+        }
+    }
+}
+
 /// One order state-transition entry within USOrderDetail.
 #[napi_derive::napi(object, js_name = "USOrderHistory")]
 #[derive(Debug, Clone, Default)]
diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index c643ec6a6..f7c49f09e 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -7973,6 +7973,8 @@ class TradeContext:
         """
         Get fund positions
 
+        AP accounts only: US data-center accounts have no fund holdings.
+
         Args:
             symbols: Filter by fund codes
 
@@ -8025,6 +8027,8 @@ class TradeContext:
         """
         Get margin ratio
 
+        AP accounts only: US data-center accounts have no margin-ratio data.
+
         Args:
             symbol: Security symbol
 
@@ -8175,6 +8179,72 @@ class TradeContext:
         """
         ...
 
+    def us_cash_and_buy_power(self, currency: Optional[str] = None) -> "USCashAndBuyPower":
+        """Get cash, buying power and margin status for the US account. US token required.
+
+        Args:
+            currency: Currency (e.g. ``"USD"``); ``None`` for the server default
+
+        Returns:
+            :class:`USCashAndBuyPower`
+        """
+        ...
+
+    def us_available_holding(
+        self,
+        symbol: str,
+        equity_type: Optional[str] = None,
+        position_side: Optional[str] = None,
+    ) -> "USAvailableHolding":
+        """Get the quantity of a US holding that is available to trade. US token required.
+
+        Args:
+            symbol: Security symbol, e.g. ``"AAPL.US"``, an option symbol such as
+                ``"AAPL261120C345000.US"``, or a crypto pair such as ``"BTCUSD.BKKT"``
+            equity_type: ``"STOCK"`` (default), ``"OPTION"`` or ``"CRYPTO"``
+            position_side: ``"LONG"`` (default) or ``"SHORT"``
+
+        Returns:
+            :class:`USAvailableHolding`
+        """
+        ...
+
+    def us_transaction_history(
+        self,
+        start_at: datetime,
+        end_at: datetime,
+        page: int = 1,
+        size: int = 20,
+        types: Optional[List[str]] = None,
+        currency: Optional[str] = None,
+    ) -> "USTransactionHistory":
+        """Get the US account transaction history, one page at a time. US token required.
+
+        Args:
+            start_at: Start time (inclusive)
+            end_at: End time (inclusive)
+            page: Page number, starting at 1
+            size: Page size
+            types: Transaction types to include; ``None`` for all. Values:
+                ``"TT_DEPOSIT_AND_WITHDRAWAL"``, ``"TT_STOCKS_AND_ETFS_TRADING"``,
+                ``"TT_OPTIONS_TRADING"``, ``"TT_CRYPTO_TRADING"``,
+                ``"TT_CORPORATE_ACTION"``, ``"TT_TRANSFER_IN_AND_OUT"``,
+                ``"TT_CRYPTO_TRANSFER"``, ``"TT_MARGIN_FINANCING"``, ``"TT_OTHER"``
+            currency: Currency filter (e.g. ``"USD"``); ``None`` for all
+
+        Returns:
+            :class:`USTransactionHistory`
+        """
+        ...
+
+    def us_exchange_rates(self) -> "USExchangeRates":
+        """Get the exchange rates used by the US account. US token required.
+
+        Returns:
+            :class:`USExchangeRates`
+        """
+        ...
+
     def set_on_grid_order_changed(self, callback: Callable[["PushGridOrderChanged"], None]) -> None:
         """Set the grid-order-changed push callback."""
         ...
@@ -11904,6 +11974,8 @@ class AsyncTradeContext:
         """
         Get fund positions. Optional filter: symbols. Returns an awaitable that resolves to fund positions response.
 
+        AP accounts only: US data-center accounts have no fund holdings.
+
         Args:
             symbols: Filter by fund codes.
 
@@ -11958,6 +12030,8 @@ class AsyncTradeContext:
         """
         Get margin ratio for symbol. Returns an awaitable that resolves to margin ratio.
 
+        AP accounts only: US data-center accounts have no margin-ratio data.
+
         Args:
             symbol: Security symbol.
 
@@ -12110,6 +12184,72 @@ class AsyncTradeContext:
         """
         ...
 
+    def us_cash_and_buy_power(self, currency: Optional[str] = None) -> "Awaitable[USCashAndBuyPower]":
+        """Get cash, buying power and margin status for the US account. US token required. Returns awaitable.
+
+        Args:
+            currency: Currency (e.g. ``"USD"``); ``None`` for the server default
+
+        Returns:
+            Awaitable resolving to :class:`USCashAndBuyPower`
+        """
+        ...
+
+    def us_available_holding(
+        self,
+        symbol: str,
+        equity_type: Optional[str] = None,
+        position_side: Optional[str] = None,
+    ) -> "Awaitable[USAvailableHolding]":
+        """Get the quantity of a US holding that is available to trade. US token required. Returns awaitable.
+
+        Args:
+            symbol: Security symbol, e.g. ``"AAPL.US"``, an option symbol such as
+                ``"AAPL261120C345000.US"``, or a crypto pair such as ``"BTCUSD.BKKT"``
+            equity_type: ``"STOCK"`` (default), ``"OPTION"`` or ``"CRYPTO"``
+            position_side: ``"LONG"`` (default) or ``"SHORT"``
+
+        Returns:
+            Awaitable resolving to :class:`USAvailableHolding`
+        """
+        ...
+
+    def us_transaction_history(
+        self,
+        start_at: datetime,
+        end_at: datetime,
+        page: int = 1,
+        size: int = 20,
+        types: Optional[List[str]] = None,
+        currency: Optional[str] = None,
+    ) -> "Awaitable[USTransactionHistory]":
+        """Get the US account transaction history, one page at a time. US token required. Returns awaitable.
+
+        Args:
+            start_at: Start time (inclusive)
+            end_at: End time (inclusive)
+            page: Page number, starting at 1
+            size: Page size
+            types: Transaction types to include; ``None`` for all. Values:
+                ``"TT_DEPOSIT_AND_WITHDRAWAL"``, ``"TT_STOCKS_AND_ETFS_TRADING"``,
+                ``"TT_OPTIONS_TRADING"``, ``"TT_CRYPTO_TRADING"``,
+                ``"TT_CORPORATE_ACTION"``, ``"TT_TRANSFER_IN_AND_OUT"``,
+                ``"TT_CRYPTO_TRANSFER"``, ``"TT_MARGIN_FINANCING"``, ``"TT_OTHER"``
+            currency: Currency filter (e.g. ``"USD"``); ``None`` for all
+
+        Returns:
+            Awaitable resolving to :class:`USTransactionHistory`
+        """
+        ...
+
+    def us_exchange_rates(self) -> "Awaitable[USExchangeRates]":
+        """Get the exchange rates used by the US account. US token required. Returns awaitable.
+
+        Returns:
+            Awaitable resolving to :class:`USExchangeRates`
+        """
+        ...
+
     async def set_on_grid_order_changed(self, callback: Callable[["PushGridOrderChanged"], None]) -> None:
         """Set the grid-order-changed push callback."""
         ...
@@ -17503,6 +17643,161 @@ class USRealizedPL:
     """Per-category realized P&L entries"""
 
 
+class USAvailableHolding:
+    """Quantity of a US holding that is available to trade. Returned by TradeContext.us_available_holding."""
+
+    available_qty: str
+    """Quantity available to sell (long) or buy to cover (short); negative for a short position"""
+
+
+class USBuyLimitInfo:
+    """Buying power in USCashAndBuyPower."""
+
+    currency: str
+    """Currency"""
+    cash_buy_power: str
+    """Cash buying power"""
+    overnight_buy_power: str
+    """Overnight buying power"""
+    day_trade_buy_power: str
+    """Day-trade buying power"""
+    instant_buy_power: str
+    """Instant buying power"""
+    intraday_margin_buy_power: str
+    """Intraday margin buying power"""
+    option_buy_power: str
+    """Option buying power"""
+    crypto_buy_power: str
+    """Crypto buying power"""
+    mme: str
+    """Margin maintenance excess used for buying power"""
+
+
+class USCashDetail:
+    """One cash currency entry in USCashAndBuyPower."""
+
+    currency: str
+    """Currency"""
+    total_amount: str
+    """Total cash"""
+    available_cash: str
+    """Available cash"""
+    withdraw_cash: str
+    """Withdrawable cash"""
+    frozen_cash: str
+    """Cash frozen for pending orders"""
+    settled_cash: str
+    """Settled cash"""
+    outstanding: str
+    """Outstanding (unsettled) amount"""
+    external_amount: str
+    """Amount held for external use (e.g. IPO or fund subscriptions)"""
+
+
+class USMarginStatus:
+    """Margin account status in USCashAndBuyPower."""
+
+    currency: str
+    """Currency"""
+    nlv: str
+    """Net liquidation value"""
+    margin_equity: str
+    """Margin equity"""
+    margin_maintenance_excess: str
+    """Margin maintenance excess"""
+    mmr: str
+    """Maintenance margin requirement"""
+    reg_t_excess: str
+    """Regulation T excess"""
+    sma: str
+    """Special memorandum account"""
+
+
+class USCashAndBuyPower:
+    """Cash, buying power and margin status of a US account. Returned by TradeContext.us_cash_and_buy_power."""
+
+    buy_limit_info: USBuyLimitInfo
+    """Buying power"""
+    cash_list: List[USCashDetail]
+    """Cash per currency"""
+    margin_status: USMarginStatus
+    """Margin account status"""
+
+
+class USTransaction:
+    """One record in USTransactionHistory."""
+
+    title: str
+    """Display title"""
+    biz_code: str
+    """Business code"""
+    biz_code_name: str
+    """Business code name"""
+    amount: str
+    """Amount; negative for outflows"""
+    currency: str
+    """Currency"""
+    symbol: str
+    """Security symbol, if the record relates to one (e.g. ``"AAPL.US"``)"""
+    market: str
+    """Market code"""
+    equity_type: str
+    """Holding type"""
+    position_side: str
+    """Position side"""
+    quantity: str
+    """Quantity"""
+    quantity_description: str
+    """Quantity description"""
+    ref_no: str
+    """Reference number"""
+    remark: str
+    """Remark"""
+    create_at: Optional[datetime]
+    """Creation time"""
+    done_at: Optional[datetime]
+    """Completion time"""
+
+
+class USTransactionHistory:
+    """One page of US account transaction history. Returned by TradeContext.us_transaction_history."""
+
+    current_page: int
+    """Current page number"""
+    items: List[USTransaction]
+    """Records on this page. There is no total count: a page shorter than the requested size is the last one."""
+
+
+class USExchangeRate:
+    """One exchange rate in USExchangeRates."""
+
+    universal_key: str
+    """Outer upstream map key (``universal_exchanges`` key)"""
+    spec_key: str
+    """Inner upstream map key (``spec_exchanges`` key)"""
+    base_currency: str
+    """Base currency"""
+    other_currency: str
+    """Other (quote) currency"""
+    bid_rate: str
+    """Bid rate; empty if not provided"""
+    offer_rate: str
+    """Offer rate; empty if not provided"""
+    average_rate: str
+    """Average rate; empty if not provided"""
+
+
+class USExchangeRates:
+    """Exchange rates of a US account. Returned by TradeContext.us_exchange_rates.
+
+    The upstream two-level map is flattened into ``exchanges``, sorted by the two
+    upstream map keys, which are kept on each entry as ``universal_key`` and ``spec_key``.
+    """
+
+    exchanges: List[USExchangeRate]
+    """Exchange rates"""
+
+
 class USOrderHistory:
     """One order state-transition entry within USOrderDetail."""
     exec_type: int
diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs
index 9a93f2b4d..781a477cb 100644
--- a/python/src/trade/context.rs
+++ b/python/src/trade/context.rs
@@ -511,6 +511,8 @@ impl TradeContext {
     }
 
     /// Get fund positions
+    ///
+    /// AP accounts only: US data-center accounts have no fund holdings.
     #[pyo3(signature = (symbols = None))]
     fn fund_positions(&self, symbols: Option>) -> PyResult {
         self.ctx
@@ -529,6 +531,8 @@ impl TradeContext {
     }
 
     /// Get margin ratio
+    ///
+    /// AP accounts only: US data-center accounts have no margin-ratio data.
     fn margin_ratio(&self, symbol: String) -> PyResult {
         self.ctx
             .margin_ratio(symbol)
@@ -614,6 +618,68 @@ impl TradeContext {
             .into())
     }
 
+    /// Get cash, buying power and margin status for the US account. US token
+    /// required.
+    #[pyo3(signature = (currency = None))]
+    fn us_cash_and_buy_power(
+        &self,
+        currency: Option,
+    ) -> PyResult {
+        Ok(self
+            .ctx
+            .us_cash_and_buy_power(currency.as_deref())
+            .map_err(ErrorNewType)?
+            .into())
+    }
+
+    /// Get the quantity of a US holding that is available to trade. US token
+    /// required.
+    #[pyo3(signature = (symbol, equity_type = None, position_side = None))]
+    fn us_available_holding(
+        &self,
+        symbol: String,
+        equity_type: Option,
+        position_side: Option,
+    ) -> PyResult {
+        Ok(self
+            .ctx
+            .us_available_holding(longbridge::trade::GetUSAvailableHoldingOptions {
+                symbol,
+                equity_type: equity_type.unwrap_or_default(),
+                position_side: position_side.unwrap_or_default(),
+            })
+            .map_err(ErrorNewType)?
+            .into())
+    }
+
+    /// Get the US account transaction history. US token required.
+    #[pyo3(signature = (start_at, end_at, page = 1, size = 20, types = None, currency = None))]
+    fn us_transaction_history(
+        &self,
+        start_at: PyOffsetDateTimeWrapper,
+        end_at: PyOffsetDateTimeWrapper,
+        page: i32,
+        size: i32,
+        types: Option>,
+        currency: Option,
+    ) -> PyResult {
+        let mut opts = longbridge::trade::GetUSTransactionHistoryOptions::new(start_at.0, end_at.0);
+        opts.page = page;
+        opts.size = size;
+        opts.types = types.unwrap_or_default();
+        opts.currency = currency.unwrap_or_default();
+        Ok(self
+            .ctx
+            .us_transaction_history(opts)
+            .map_err(ErrorNewType)?
+            .into())
+    }
+
+    /// Get the exchange rates used by the US account. US token required.
+    fn us_exchange_rates(&self) -> PyResult {
+        Ok(self.ctx.us_exchange_rates().map_err(ErrorNewType)?.into())
+    }
+
     /// Estimating the maximum purchase quantity for Hong Kong and US stocks,
     /// warrants, and options
     #[allow(clippy::too_many_arguments)]
diff --git a/python/src/trade/context_async.rs b/python/src/trade/context_async.rs
index 50504b465..e9f2ee8a8 100644
--- a/python/src/trade/context_async.rs
+++ b/python/src/trade/context_async.rs
@@ -576,6 +576,8 @@ impl AsyncTradeContext {
     }
 
     /// Get fund positions. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts have no fund holdings.
     #[pyo3(signature = (symbols = None))]
     fn fund_positions(&self, py: Python<'_>, symbols: Option>) -> PyResult> {
         let ctx = self.ctx.clone();
@@ -608,6 +610,8 @@ impl AsyncTradeContext {
     }
 
     /// Get margin ratio. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts have no margin-ratio data.
     fn margin_ratio(&self, py: Python<'_>, symbol: String) -> PyResult> {
         let ctx = self.ctx.clone();
         pyo3_async_runtimes::tokio::future_into_py(py, async move {
@@ -731,6 +735,95 @@ impl AsyncTradeContext {
         .map(|b| b.unbind())
     }
 
+    /// Get cash, buying power and margin status for the US account. US token
+    /// required. Returns awaitable.
+    #[pyo3(signature = (currency = None))]
+    fn us_cash_and_buy_power(
+        &self,
+        py: Python<'_>,
+        currency: Option,
+    ) -> PyResult> {
+        let ctx = self.ctx.clone();
+        pyo3_async_runtimes::tokio::future_into_py(py, async move {
+            let r: crate::trade::types::USCashAndBuyPower = ctx
+                .us_cash_and_buy_power(currency.as_deref())
+                .await
+                .map_err(ErrorNewType)?
+                .into();
+            Ok(r)
+        })
+        .map(|b| b.unbind())
+    }
+
+    /// Get the quantity of a US holding that is available to trade. US token
+    /// required. Returns awaitable.
+    #[pyo3(signature = (symbol, equity_type = None, position_side = None))]
+    fn us_available_holding(
+        &self,
+        py: Python<'_>,
+        symbol: String,
+        equity_type: Option,
+        position_side: Option,
+    ) -> PyResult> {
+        let ctx = self.ctx.clone();
+        pyo3_async_runtimes::tokio::future_into_py(py, async move {
+            let r: crate::trade::types::USAvailableHolding = ctx
+                .us_available_holding(longbridge::trade::GetUSAvailableHoldingOptions {
+                    symbol,
+                    equity_type: equity_type.unwrap_or_default(),
+                    position_side: position_side.unwrap_or_default(),
+                })
+                .await
+                .map_err(ErrorNewType)?
+                .into();
+            Ok(r)
+        })
+        .map(|b| b.unbind())
+    }
+
+    /// Get the US account transaction history. US token required. Returns
+    /// awaitable.
+    #[allow(clippy::too_many_arguments)]
+    #[pyo3(signature = (start_at, end_at, page = 1, size = 20, types = None, currency = None))]
+    fn us_transaction_history(
+        &self,
+        py: Python<'_>,
+        start_at: PyOffsetDateTimeWrapper,
+        end_at: PyOffsetDateTimeWrapper,
+        page: i32,
+        size: i32,
+        types: Option>,
+        currency: Option,
+    ) -> PyResult> {
+        let ctx = self.ctx.clone();
+        let mut opts = longbridge::trade::GetUSTransactionHistoryOptions::new(start_at.0, end_at.0);
+        opts.page = page;
+        opts.size = size;
+        opts.types = types.unwrap_or_default();
+        opts.currency = currency.unwrap_or_default();
+        pyo3_async_runtimes::tokio::future_into_py(py, async move {
+            let r: crate::trade::types::USTransactionHistory = ctx
+                .us_transaction_history(opts)
+                .await
+                .map_err(ErrorNewType)?
+                .into();
+            Ok(r)
+        })
+        .map(|b| b.unbind())
+    }
+
+    /// Get the exchange rates used by the US account. US token required.
+    /// Returns awaitable.
+    fn us_exchange_rates(&self, py: Python<'_>) -> PyResult> {
+        let ctx = self.ctx.clone();
+        pyo3_async_runtimes::tokio::future_into_py(py, async move {
+            let r: crate::trade::types::USExchangeRates =
+                ctx.us_exchange_rates().await.map_err(ErrorNewType)?.into();
+            Ok(r)
+        })
+        .map(|b| b.unbind())
+    }
+
     /// Estimate max purchase quantity. Returns awaitable.
     #[allow(clippy::too_many_arguments)]
     #[pyo3(signature = (symbol, order_type, side, price = None, currency = None, order_id = None, fractional_shares = false))]
diff --git a/python/src/trade/mod.rs b/python/src/trade/mod.rs
index 6cbe3570c..8afe61922 100644
--- a/python/src/trade/mod.rs
+++ b/python/src/trade/mod.rs
@@ -58,6 +58,15 @@ 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::()?;
+    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 ab00587a1..c460d8400 100644
--- a/python/src/trade/types.rs
+++ b/python/src/trade/types.rs
@@ -1330,6 +1330,229 @@ impl From for USRealizedPL {
     }
 }
 
+/// Quantity of a US holding that is available to trade
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USAvailableHolding {
+    pub available_qty: String,
+}
+
+impl From for USAvailableHolding {
+    fn from(v: longbridge::trade::USAvailableHolding) -> Self {
+        Self {
+            available_qty: v.available_qty,
+        }
+    }
+}
+
+/// Buying power in USCashAndBuyPower
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USBuyLimitInfo {
+    pub currency: String,
+    pub cash_buy_power: String,
+    pub overnight_buy_power: String,
+    pub day_trade_buy_power: String,
+    pub instant_buy_power: String,
+    pub intraday_margin_buy_power: String,
+    pub option_buy_power: String,
+    pub crypto_buy_power: String,
+    pub mme: String,
+}
+
+impl From for USBuyLimitInfo {
+    fn from(v: longbridge::trade::USBuyLimitInfo) -> Self {
+        Self {
+            currency: v.currency,
+            cash_buy_power: v.cash_buy_power,
+            overnight_buy_power: v.overnight_buy_power,
+            day_trade_buy_power: v.day_trade_buy_power,
+            instant_buy_power: v.instant_buy_power,
+            intraday_margin_buy_power: v.intraday_margin_buy_power,
+            option_buy_power: v.option_buy_power,
+            crypto_buy_power: v.crypto_buy_power,
+            mme: v.mme,
+        }
+    }
+}
+
+/// One cash currency entry in USCashAndBuyPower
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USCashDetail {
+    pub currency: String,
+    pub total_amount: String,
+    pub available_cash: String,
+    pub withdraw_cash: String,
+    pub frozen_cash: String,
+    pub settled_cash: String,
+    pub outstanding: String,
+    pub external_amount: String,
+}
+
+impl From for USCashDetail {
+    fn from(v: longbridge::trade::USCashDetail) -> Self {
+        Self {
+            currency: v.currency,
+            total_amount: v.total_amount,
+            available_cash: v.available_cash,
+            withdraw_cash: v.withdraw_cash,
+            frozen_cash: v.frozen_cash,
+            settled_cash: v.settled_cash,
+            outstanding: v.outstanding,
+            external_amount: v.external_amount,
+        }
+    }
+}
+
+/// Margin account status in USCashAndBuyPower
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USMarginStatus {
+    pub currency: String,
+    pub nlv: String,
+    pub margin_equity: String,
+    pub margin_maintenance_excess: String,
+    pub mmr: String,
+    pub reg_t_excess: String,
+    pub sma: String,
+}
+
+impl From for USMarginStatus {
+    fn from(v: longbridge::trade::USMarginStatus) -> Self {
+        Self {
+            currency: v.currency,
+            nlv: v.nlv,
+            margin_equity: v.margin_equity,
+            margin_maintenance_excess: v.margin_maintenance_excess,
+            mmr: v.mmr,
+            reg_t_excess: v.reg_t_excess,
+            sma: v.sma,
+        }
+    }
+}
+
+/// Cash, buying power and margin status of a US account
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USCashAndBuyPower {
+    pub buy_limit_info: USBuyLimitInfo,
+    pub cash_list: Vec,
+    pub margin_status: USMarginStatus,
+}
+
+impl From for USCashAndBuyPower {
+    fn from(v: longbridge::trade::USCashAndBuyPower) -> Self {
+        Self {
+            buy_limit_info: v.buy_limit_info.into(),
+            cash_list: v.cash_list.into_iter().map(Into::into).collect(),
+            margin_status: v.margin_status.into(),
+        }
+    }
+}
+
+/// One record in USTransactionHistory
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USTransaction {
+    pub title: String,
+    pub biz_code: String,
+    pub biz_code_name: String,
+    pub amount: String,
+    pub currency: String,
+    pub symbol: String,
+    pub market: String,
+    pub equity_type: String,
+    pub position_side: String,
+    pub quantity: String,
+    pub quantity_description: String,
+    pub ref_no: String,
+    pub remark: String,
+    pub create_at: Option,
+    pub done_at: Option,
+}
+
+impl From for USTransaction {
+    fn from(v: longbridge::trade::USTransaction) -> Self {
+        Self {
+            title: v.title,
+            biz_code: v.biz_code,
+            biz_code_name: v.biz_code_name,
+            amount: v.amount,
+            currency: v.currency,
+            symbol: v.symbol,
+            market: v.market,
+            equity_type: v.equity_type,
+            position_side: v.position_side,
+            quantity: v.quantity,
+            quantity_description: v.quantity_description,
+            ref_no: v.ref_no,
+            remark: v.remark,
+            create_at: v.create_at.map(Into::into),
+            done_at: v.done_at.map(Into::into),
+        }
+    }
+}
+
+/// One page of US account transaction history
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USTransactionHistory {
+    pub current_page: i64,
+    pub items: Vec,
+}
+
+impl From for USTransactionHistory {
+    fn from(v: longbridge::trade::USTransactionHistory) -> Self {
+        Self {
+            current_page: v.current_page,
+            items: v.items.into_iter().map(Into::into).collect(),
+        }
+    }
+}
+
+/// One exchange rate in USExchangeRates
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USExchangeRate {
+    pub universal_key: String,
+    pub spec_key: String,
+    pub base_currency: String,
+    pub other_currency: String,
+    pub bid_rate: String,
+    pub offer_rate: String,
+    pub average_rate: String,
+}
+
+impl From for USExchangeRate {
+    fn from(v: longbridge::trade::USExchangeRate) -> Self {
+        Self {
+            universal_key: v.universal_key,
+            spec_key: v.spec_key,
+            base_currency: v.base_currency,
+            other_currency: v.other_currency,
+            bid_rate: v.bid_rate,
+            offer_rate: v.offer_rate,
+            average_rate: v.average_rate,
+        }
+    }
+}
+
+/// Exchange rates of a US account
+#[pyclass(get_all, skip_from_py_object)]
+#[derive(Debug, Clone, Default)]
+pub(crate) struct USExchangeRates {
+    pub exchanges: Vec,
+}
+
+impl From for USExchangeRates {
+    fn from(v: longbridge::trade::USExchangeRates) -> Self {
+        Self {
+            exchanges: v.exchanges.into_iter().map(Into::into).collect(),
+        }
+    }
+}
+
 /// One order state-transition entry within USOrderDetail.
 #[pyclass(get_all, skip_from_py_object)]
 #[derive(Debug, Clone, Default)]

From 1ff34ab2fbdeea27b3a737b0d2fb74e2828d0bf5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Thu, 22 Oct 2026 16:19:52 +0800
Subject: [PATCH 04/16] docs(changelog): add US asset APIs and AP-only
 fund/margin calls

---
 CHANGELOG.md | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70c51f1c8..bd7803d3a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,22 @@ 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
+
+- **Rust core (async + blocking), Node.js, Python:** four US-account asset methods on `TradeContext`, all restricted to US data-center tokens (`dc_restrict(DcRegion::Us)`, so a non-US token gets `HttpClientError::DcRegionRestricted` without a request):
+  - `us_cash_and_buy_power(currency)` — `GET /v1/us/assets/cash-and-buy-power`: buying power (`USBuyLimitInfo`), cash per currency (`USCashDetail`) and margin status (`USMarginStatus`) as `USCashAndBuyPower`.
+  - `us_available_holding(opts)` — `GET /v1/us/assets/available-holding`: quantity of a stock / option / crypto holding available to trade (`USAvailableHolding`); `equity_type` defaults to `STOCK`, `position_side` to `LONG`.
+  - `us_transaction_history(opts)` — `POST /v1/us/assets/transaction-history`: one page of account transactions (`USTransactionHistory` / `USTransaction`) between two times, with optional transaction-type and currency filters (`page` defaults to 1, `size` to 20).
+  - `us_exchange_rates()` — `GET /v1/us/assets/exchange-rates`: the upstream two-level map is flattened into `USExchangeRates.exchanges` (`USExchangeRate`), sorted by the two upstream map keys, which are kept on each entry as `universal_key` / `spec_key`; rates accept a JSON number or string and read as an empty string when absent.
+  - Node.js takes the transaction-history request as a `GetUSTransactionHistoryOptions` object; Python as `start_at` / `end_at` `datetime` plus keyword arguments; the `openapi.pyi` stub is updated.
+- **Java:** JNI entry points for the four methods (`tradeContextUsCashAndBuyPower`, `tradeContextUsAvailableHolding`, `tradeContextUsTransactionHistory`, `tradeContextUsExchangeRates`), returning the response as JSON like the existing US JNI stubs; no Java-side `TradeContext` wrappers yet, matching the other US methods.
+
+### Changed
+
+- **All SDKs:** `fund_positions` and `margin_ratio` are AP-only (`dc_restrict(DcRegion::Ap)`): US data-center accounts have no fund holdings or margin-ratio data, so they now get a region error (`HttpClientError::DcRegionRestricted`) instead of an opaque server error. The method docs in every layer say so.
+
 ## [5.2.2] - 2026-10-16
 
 ### Added

From f8877d2ab83e8ebbf7c91772ae3c1182272a829d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Thu, 22 Oct 2026 16:45:43 +0800
Subject: [PATCH 05/16] fix(trade): parse US transaction history with both
 symbol and counter_id

The gateway's counterID2Symbol transform adds symbol and keeps
counter_id, so the counter_id alias on USTransaction.symbol made serde
fail with a duplicate-field error on real responses. Also document
what the two exchange-rate map keys are (account channel and rate
type), confirmed against US prod responses.
---
 python/pysrc/longbridge/openapi.pyi |  4 ++--
 rust/src/trade/types.rs             | 15 ++++++---------
 2 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index f7c49f09e..5dfdbb68d 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -17772,9 +17772,9 @@ class USExchangeRate:
     """One exchange rate in USExchangeRates."""
 
     universal_key: str
-    """Outer upstream map key (``universal_exchanges`` key)"""
+    """Account channel the rates apply to, e.g. ``us_lb_apex`` (outer ``universal_exchanges`` key)"""
     spec_key: str
-    """Inner upstream map key (``spec_exchanges`` key)"""
+    """Rate type, e.g. ``am`` or ``reference`` (inner ``spec_exchanges`` key)"""
     base_currency: str
     """Base currency"""
     other_currency: str
diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs
index be995674c..df8b90e13 100644
--- a/rust/src/trade/types.rs
+++ b/rust/src/trade/types.rs
@@ -1871,11 +1871,7 @@ pub struct USTransaction {
     #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
     pub currency: String,
     /// Security symbol, if the record relates to one (e.g. `"AAPL.US"`)
-    #[serde(
-        default,
-        alias = "counter_id",
-        deserialize_with = "crate::serde_utils::null_as_default"
-    )]
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
     pub symbol: String,
     /// Market code
     #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
@@ -1930,9 +1926,10 @@ pub struct USTransactionHistory {
 /// One exchange rate in [`USExchangeRates`].
 #[derive(Debug, Clone, Serialize, Deserialize, Default)]
 pub struct USExchangeRate {
-    /// Outer upstream map key (`universal_exchanges` key)
+    /// Account channel the rates apply to, e.g. `"us_lb_apex"` (outer
+    /// `universal_exchanges` key)
     pub universal_key: String,
-    /// Inner upstream map key (`spec_exchanges` key)
+    /// Rate type, e.g. `"am"` or `"reference"` (inner `spec_exchanges` key)
     pub spec_key: String,
     /// Base currency
     pub base_currency: String,
@@ -2073,7 +2070,7 @@ mod tests {
             "current_page": "2",
             "items": [
                 {"title": "Buy", "amount": "-100", "currency": "USD", "symbol": "AAPL.US", "create_at": "1792600000", "done_at": 1792600060},
-                {"title": "Deposit", "amount": "500", "currency": "USD", "counter_id": "ST/US/AAPL", "done_at": "0"}
+                {"title": "Buy", "amount": "-10", "currency": "USD", "symbol": "BTCUSD.BKKT", "counter_id": "VA/BKKT/BTCUSD", "done_at": "0"}
             ]
         }"#;
         let resp: USTransactionHistory = serde_json::from_str(data).unwrap();
@@ -2084,7 +2081,7 @@ mod tests {
             1792600000
         );
         assert_eq!(resp.items[0].done_at.unwrap().unix_timestamp(), 1792600060);
-        assert_eq!(resp.items[1].symbol, "ST/US/AAPL");
+        assert_eq!(resp.items[1].symbol, "BTCUSD.BKKT");
         assert!(resp.items[1].done_at.is_none());
     }
 

From cab7903909694d1f706b878db5936b5bb6721dd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Thu, 22 Oct 2026 17:41:57 +0800
Subject: [PATCH 06/16] fix(trade): keep quantity and prices on US crypto
 holdings

GET /v1/us/assets/overview returns crypto_list entries with the same
fields as stock_list (quantity, market, name, prices, today_pl, ...),
but USCryptoEntry only kept five of them, so a crypto holding had no
quantity. Mirror the stock entry's fields in Rust, Python and Node.js,
correct the symbol doc (bare pair code such as BTCUSD; the user-facing
symbol is .), and add a test using the response shapes
returned by US prod.
---
 CHANGELOG.md                        |   1 +
 nodejs/index.d.ts                   |  19 ++++-
 nodejs/src/trade/types.rs           |  38 ++++++++-
 python/pysrc/longbridge/openapi.pyi |  26 ++++++-
 python/src/trade/types.rs           |  38 ++++++++-
 rust/src/trade/types.rs             | 115 +++++++++++++++++++++++++++-
 6 files changed, 225 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9caa784c5..8945df659 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+- **Rust core, Node.js, Python:** `USAssetOverview.crypto_list` entries (`USCryptoEntry`) now carry the holding `quantity`, `market`, `name`, prices (`last_done`, `market_price`, `prev_close`, ...), `today_pl`, `position_side` and the other fields `GET /v1/us/assets/overview` returns for crypto, which were previously dropped, so a crypto holding could not be sized. `symbol` is the bare pair code (e.g. `BTCUSD`); the user-facing symbol is `.` (e.g. `BTCUSD.BKKT`).
 - **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged
 
 ## [5.2.2] - 2026-10-16
diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts
index 5f9d8237d..5d526c236 100644
--- a/nodejs/index.d.ts
+++ b/nodejs/index.d.ts
@@ -9217,9 +9217,26 @@ export interface USConsensusItem {
 /** One cryptocurrency holding in USAssetOverview */
 export interface USCryptoEntry {
   assetType: string
-  averageCost: string
   symbol: string
+  quantity: string
   currency: string
+  averageCost: string
+  market: string
+  tradeStatus: string
+  prevClose: string
+  lastDone: string
+  marketPrice: string
+  pretradeClose: string
+  stockInvestOfToday: string
+  todayPl: string
+  pretradeStockInvestOfToday: string
+  pretradeTodayPl: string
+  nightLastDone: string
+  nightPrevClose: string
+  positionSide: string
+  openPositionTime: string
+  name: string
+  industryCounterId: string
   industryName: string
 }
 
diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs
index d45f999d2..02b6247e8 100644
--- a/nodejs/src/trade/types.rs
+++ b/nodejs/src/trade/types.rs
@@ -1031,9 +1031,26 @@ impl From for USCashEntry {
 #[derive(Debug, Clone, Default)]
 pub struct USCryptoEntry {
     pub asset_type: String,
-    pub average_cost: String,
     pub symbol: String,
+    pub quantity: String,
     pub currency: String,
+    pub average_cost: String,
+    pub market: String,
+    pub trade_status: String,
+    pub prev_close: String,
+    pub last_done: String,
+    pub market_price: String,
+    pub pretrade_close: String,
+    pub stock_invest_of_today: String,
+    pub today_pl: String,
+    pub pretrade_stock_invest_of_today: String,
+    pub pretrade_today_pl: String,
+    pub night_last_done: String,
+    pub night_prev_close: String,
+    pub position_side: String,
+    pub open_position_time: String,
+    pub name: String,
+    pub industry_counter_id: String,
     pub industry_name: String,
 }
 
@@ -1041,9 +1058,26 @@ impl From for USCryptoEntry {
     fn from(v: longbridge::trade::USCryptoEntry) -> Self {
         Self {
             asset_type: v.asset_type,
-            average_cost: v.average_cost,
             symbol: v.symbol,
+            quantity: v.quantity,
             currency: v.currency,
+            average_cost: v.average_cost,
+            market: v.market,
+            trade_status: v.trade_status,
+            prev_close: v.prev_close,
+            last_done: v.last_done,
+            market_price: v.market_price,
+            pretrade_close: v.pretrade_close,
+            stock_invest_of_today: v.stock_invest_of_today,
+            today_pl: v.today_pl,
+            pretrade_stock_invest_of_today: v.pretrade_stock_invest_of_today,
+            pretrade_today_pl: v.pretrade_today_pl,
+            night_last_done: v.night_last_done,
+            night_prev_close: v.night_prev_close,
+            position_side: v.position_side,
+            open_position_time: v.open_position_time,
+            name: v.name,
+            industry_counter_id: v.industry_counter_id,
             industry_name: v.industry_name,
         }
     }
diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index 5dfdbb68d..f2d80411c 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -17549,12 +17549,32 @@ class USCryptoEntry:
 
     asset_type: str
     """Asset type, e.g. ``"CRYPTO"``"""
-    average_cost: str
-    """Average cost price"""
     symbol: str
-    """Symbol"""
+    """Trading-pair code, e.g. ``"BTCUSD"``; the user-facing symbol is ``"."``, e.g. ``"BTCUSD.BKKT"``"""
+    quantity: str
+    """Holding quantity"""
     currency: str
     """Settlement currency"""
+    average_cost: str
+    """Average cost price; empty when the server has none"""
+    market: str
+    """Market code, e.g. ``"BKKT"``"""
+    trade_status: str
+    prev_close: str
+    last_done: str
+    market_price: str
+    pretrade_close: str
+    stock_invest_of_today: str
+    today_pl: str
+    pretrade_stock_invest_of_today: str
+    pretrade_today_pl: str
+    night_last_done: str
+    night_prev_close: str
+    position_side: str
+    open_position_time: str
+    """Position open time (Unix-second string; ``"0"`` when unknown)"""
+    name: str
+    """Asset name, e.g. ``"Bitcoin"``"""
     industry_counter_id: str
     """Industry counter_id"""
     industry_name: str
diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs
index c460d8400..1115cd19e 100644
--- a/python/src/trade/types.rs
+++ b/python/src/trade/types.rs
@@ -1166,9 +1166,26 @@ impl From for USCashEntry {
 #[derive(Debug, Clone, Default)]
 pub(crate) struct USCryptoEntry {
     pub asset_type: String,
-    pub average_cost: String,
     pub symbol: String,
+    pub quantity: String,
     pub currency: String,
+    pub average_cost: String,
+    pub market: String,
+    pub trade_status: String,
+    pub prev_close: String,
+    pub last_done: String,
+    pub market_price: String,
+    pub pretrade_close: String,
+    pub stock_invest_of_today: String,
+    pub today_pl: String,
+    pub pretrade_stock_invest_of_today: String,
+    pub pretrade_today_pl: String,
+    pub night_last_done: String,
+    pub night_prev_close: String,
+    pub position_side: String,
+    pub open_position_time: String,
+    pub name: String,
+    pub industry_counter_id: String,
     pub industry_name: String,
 }
 
@@ -1176,9 +1193,26 @@ impl From for USCryptoEntry {
     fn from(v: longbridge::trade::USCryptoEntry) -> Self {
         Self {
             asset_type: v.asset_type,
-            average_cost: v.average_cost,
             symbol: v.symbol,
+            quantity: v.quantity,
             currency: v.currency,
+            average_cost: v.average_cost,
+            market: v.market,
+            trade_status: v.trade_status,
+            prev_close: v.prev_close,
+            last_done: v.last_done,
+            market_price: v.market_price,
+            pretrade_close: v.pretrade_close,
+            stock_invest_of_today: v.stock_invest_of_today,
+            today_pl: v.today_pl,
+            pretrade_stock_invest_of_today: v.pretrade_stock_invest_of_today,
+            pretrade_today_pl: v.pretrade_today_pl,
+            night_last_done: v.night_last_done,
+            night_prev_close: v.night_prev_close,
+            position_side: v.position_side,
+            open_position_time: v.open_position_time,
+            name: v.name,
+            industry_counter_id: v.industry_counter_id,
             industry_name: v.industry_name,
         }
     }
diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs
index df8b90e13..0ff901125 100644
--- a/rust/src/trade/types.rs
+++ b/rust/src/trade/types.rs
@@ -1519,15 +1519,67 @@ pub struct USCryptoEntry {
     /// Asset type (e.g. `"CRYPTO"`)
     #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
     pub asset_type: String,
-    /// Average cost price
-    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
-    pub average_cost: String,
-    /// User-facing trading-pair symbol (e.g. `"BTCUSD.BKKT"`)
+    /// Trading-pair code (e.g. `"BTCUSD"`); the user-facing symbol is
+    /// `"."` (e.g. `"BTCUSD.BKKT"`)
     #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
     pub symbol: String,
+    /// Holding quantity
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub quantity: String,
     /// Settlement currency
     #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
     pub currency: String,
+    /// Average cost price; empty when the server has none
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub average_cost: String,
+    /// Market code (e.g. `"BKKT"`)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub market: String,
+    /// Trade status
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub trade_status: String,
+    /// Previous close price
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub prev_close: String,
+    /// Latest price
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub last_done: String,
+    /// Market price
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub market_price: String,
+    /// Pre-market close price
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub pretrade_close: String,
+    /// Today's investment value
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub stock_invest_of_today: String,
+    /// Today's profit/loss
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub today_pl: String,
+    /// Today's investment value (pre-market)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub pretrade_stock_invest_of_today: String,
+    /// Today's profit/loss (pre-market)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub pretrade_today_pl: String,
+    /// Overnight-session latest price
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub night_last_done: String,
+    /// Overnight-session previous close
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub night_prev_close: String,
+    /// Position side (long/short)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub position_side: String,
+    /// Position open time (Unix-second string; `"0"` when unknown)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub open_position_time: String,
+    /// Asset name (e.g. `"Bitcoin"`)
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub name: String,
+    /// Industry classification ID
+    #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
+    pub industry_counter_id: String,
     /// Industry name
     #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
     pub industry_name: String,
@@ -2085,6 +2137,61 @@ mod tests {
         assert!(resp.items[1].done_at.is_none());
     }
 
+    /// Shapes (field names, JSON types, map keys) as returned by US prod.
+    #[test]
+    fn us_asset_responses_as_returned_by_prod() {
+        let data = r#"{
+            "current_page": 1,
+            "items": [{
+                "amount": "0.5", "biz_code": "LVAVNSCR", "biz_code_name": "Cryptocurrency Deposit",
+                "counter_id": "VA/BKKT/BTCUSD", "create_at": "1791478565", "currency": "",
+                "done_at": "1791478565", "equity_type": "CRYPTO", "market": "BKKT",
+                "position_side": "LONG", "quantity": "0.5", "quantity_description": "0.5",
+                "ref_no": "R1", "remark": "", "symbol": "BTCUSD.BKKT",
+                "title": "Cryptocurrency Deposit: BTCUSD"
+            }]
+        }"#;
+        let resp: USTransactionHistory = serde_json::from_str(data).unwrap();
+        assert_eq!(resp.current_page, 1);
+        assert_eq!(resp.items[0].symbol, "BTCUSD.BKKT");
+        assert_eq!(resp.items[0].equity_type, "CRYPTO");
+        assert_eq!(resp.items[0].done_at.unwrap().unix_timestamp(), 1791478565);
+
+        let data = r#"{"universal_exchanges": {"us_lb_apex": {"spec_exchanges": {
+            "reference": {"exchanges": [{"average_rate": 1, "base_currency": "USD", "bid_rate": 1, "offer_rate": 1, "other_currency": "USD"}]},
+            "am": {"exchanges": [{"average_rate": 1, "base_currency": "USD", "bid_rate": 1, "offer_rate": 1, "other_currency": "USD"}]}
+        }}}}"#;
+        let resp: USExchangeRates = serde_json::from_str(data).unwrap();
+        let keys: Vec<_> = resp
+            .exchanges
+            .iter()
+            .map(|e| (e.universal_key.as_str(), e.spec_key.as_str()))
+            .collect();
+        assert_eq!(keys, [("us_lb_apex", "am"), ("us_lb_apex", "reference")]);
+        assert_eq!(resp.exchanges[0].average_rate, "1");
+
+        let data = r#"{
+            "account_type": "1", "asset_timestamp": "1792661597", "currency": "USD",
+            "stock_list": [{"symbol": "MULL", "full_symbol": "MULL.US", "counter_id": "ETF/US/MULL", "quantity": "10", "market": "US"}],
+            "crypto_list": [{
+                "asset_type": "CRYPTO", "average_cost": "", "counter_id": "VA/BKKT/ETHUSD",
+                "currency": "USD", "last_done": "2500", "market": "BKKT", "market_price": "2500",
+                "name": "Ethereum", "open_position_time": "0", "position_side": "LONG",
+                "quantity": "0.1", "symbol": "ETHUSD", "today_pl": "", "trade_status": "105"
+            }],
+            "option_list": [], "multi_leg": {"holdings": []}
+        }"#;
+        let resp: USAssetOverview = serde_json::from_str(data).unwrap();
+        assert_eq!(resp.asset_timestamp.unwrap().unix_timestamp(), 1792661597);
+        assert_eq!(resp.stock_list[0].full_symbol, "MULL.US");
+        let crypto = &resp.crypto_list[0];
+        assert_eq!(crypto.symbol, "ETHUSD");
+        assert_eq!(crypto.market, "BKKT");
+        assert_eq!(crypto.quantity, "0.1");
+        assert_eq!(crypto.name, "Ethereum");
+        assert_eq!(crypto.market_price, "2500");
+    }
+
     #[test]
     fn fund_position_response() {
         let data = r#"

From e090eddb5450a9c8653743e1c0be5c10b1098dfa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Thu, 22 Oct 2026 18:32:53 +0800
Subject: [PATCH 07/16] fix(trade): restrict order_detail to AP accounts

GET /v1/trade/order returns HTTP 500 for US data-center accounts
because the order gateway panics on US orders (seen in the LBUS UAT
and reproduced on US prod). Fail fast with a region error instead and
point US callers at us_order_detail in every layer's docs.
---
 CHANGELOG.md                                                  | 1 +
 c/csrc/include/longbridge.h                                   | 2 ++
 c/src/trade_context/context.rs                                | 2 ++
 cpp/include/trade_context.hpp                                 | 2 ++
 .../src/main/java/com/longbridge/trade/TradeContext.java      | 2 ++
 nodejs/index.d.ts                                             | 3 +++
 nodejs/src/trade/context.rs                                   | 3 +++
 python/pysrc/longbridge/openapi.pyi                           | 4 ++++
 python/src/trade/context.rs                                   | 3 +++
 python/src/trade/context_async.rs                             | 3 +++
 rust/src/trade/context.rs                                     | 4 ++++
 11 files changed, 29 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8945df659..c5841a7a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - **All SDKs:** `fund_positions` and `margin_ratio` are AP-only (`dc_restrict(DcRegion::Ap)`): US data-center accounts have no fund holdings or margin-ratio data, so they now get a region error (`HttpClientError::DcRegionRestricted`) instead of an opaque server error. The method docs in every layer say so.
+- **All SDKs:** `order_detail` is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account, `GET /v1/trade/order` returned HTTP 500 because the order gateway panics on US orders. It now gets a region error without sending the request. US accounts should use `us_order_detail` (`GET /v1/us/orders/{order_id}`).
 
 ### Fixed
 
diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h
index c8456ae57..882503a89 100644
--- a/c/csrc/include/longbridge.h
+++ b/c/csrc/include/longbridge.h
@@ -16537,6 +16537,8 @@ void lb_trade_context_margin_ratio(const struct lb_trade_context_t *ctx,
 
 /**
  * Get order detail
+ *
+ * AP accounts only: US data-center accounts get a region error.
  */
 void lb_trade_context_order_detail(const struct lb_trade_context_t *ctx,
                                    const char *order_id,
diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs
index f61d82601..6924a8394 100644
--- a/c/src/trade_context/context.rs
+++ b/c/src/trade_context/context.rs
@@ -840,6 +840,8 @@ pub unsafe extern "C" fn lb_trade_context_margin_ratio(
 }
 
 /// Get order detail
+///
+/// AP accounts only: US data-center accounts get a region error.
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_trade_context_order_detail(
     ctx: *const CTradeContext,
diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp
index 00938b455..effffdde4 100644
--- a/cpp/include/trade_context.hpp
+++ b/cpp/include/trade_context.hpp
@@ -122,6 +122,8 @@ class TradeContext
                     AsyncCallback callback) const;
 
   /// Get order detail
+  ///
+  /// AP accounts only: US data-center accounts get a region error.
   void order_detail(const std::string& order_id,
                     AsyncCallback callback) const;
 
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 1493d9a4f..22ea48965 100644
--- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java
+++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java
@@ -637,6 +637,8 @@ public synchronized CompletableFuture getMarginRatio(String symbol)
 
     /**
      * Get order detail
+     * 

+ * AP accounts only: US data-center accounts get a region error. * *

      * {@code
diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts
index 5d526c236..b057ae279 100644
--- a/nodejs/index.d.ts
+++ b/nodejs/index.d.ts
@@ -4593,6 +4593,9 @@ export declare class TradeContext {
   /**
    * Get order detail
    *
+   * AP accounts only: US data-center accounts get a region error; use
+   * `usOrderDetail` instead.
+   *
    * #### Example
    *
    * ```javascript
diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs
index 09c675923..ea4c0bab6 100644
--- a/nodejs/src/trade/context.rs
+++ b/nodejs/src/trade/context.rs
@@ -583,6 +583,9 @@ impl TradeContext {
 
     /// Get order detail
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `usOrderDetail` instead.
+    ///
     /// #### Example
     ///
     /// ```javascript
diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index f2d80411c..f026ac5d2 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -8054,6 +8054,8 @@ class TradeContext:
         """
         Get order detail
 
+        AP accounts only: US data-center accounts get a region error; use ``us_order_detail`` instead.
+
         Args:
             order_id: Order id
             is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id
@@ -12058,6 +12060,8 @@ class AsyncTradeContext:
         """
         Get order detail by order_id. Returns an awaitable that resolves to order detail.
 
+        AP accounts only: US data-center accounts get a region error; use ``us_order_detail`` instead.
+
         Args:
             order_id: Order ID.
             is_attached: When set together with order_id, indicates that order_id is an attached sub-order ID. The server will look up using the attached order ID instead of treating it as a regular order ID. Has no effect without order_id.
diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs
index 781a477cb..8d70f8830 100644
--- a/python/src/trade/context.rs
+++ b/python/src/trade/context.rs
@@ -541,6 +541,9 @@ impl TradeContext {
     }
 
     /// Get order detail
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_order_detail` instead.
     #[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);
diff --git a/python/src/trade/context_async.rs b/python/src/trade/context_async.rs
index e9f2ee8a8..5dd61c941 100644
--- a/python/src/trade/context_async.rs
+++ b/python/src/trade/context_async.rs
@@ -626,6 +626,9 @@ impl AsyncTradeContext {
     }
 
     /// Get order detail. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_order_detail` instead.
     #[pyo3(signature = (order_id, is_attached = false))]
     fn order_detail(
         &self,
diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs
index 83a690407..a945e039e 100644
--- a/rust/src/trade/context.rs
+++ b/rust/src/trade/context.rs
@@ -858,6 +858,9 @@ impl TradeContext {
 
     /// Get order detail
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_order_detail` instead.
+    ///
     /// Reference: 
     ///
     /// # Examples
@@ -892,6 +895,7 @@ impl TradeContext {
             .0
             .http_cli
             .request(Method::GET, "/v1/trade/order")
+            .dc_restrict(DcRegion::Ap)
             .response::>()
             .query_params(options.into())
             .send()

From b33d1596a3677c4ff69cf93985e918527dabde8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Fri, 23 Oct 2026 09:29:02 +0800
Subject: [PATCH 08/16] fix(trade): restrict balance, cash flow, positions and
 exchange rate to AP

account_balance (/v1/asset/account), cash_flow (/v1/asset/cashflow),
stock_positions (/v1/asset/stock) and PortfolioContext::exchange_rate
(/v1/asset/exchange_rates) fail with gateway code 12 for US data-center
accounts. Guard them with dc_restrict(DcRegion::Ap) so US tokens get a
region error without a request, and point US callers at the us_*
equivalents in every layer's docs. Also state that fund_positions and
margin_ratio have no US equivalent, and add unit tests that all
AP-only trade/portfolio methods reject a us_ token.
---
 CHANGELOG.md                                  |  1 +
 c/csrc/include/longbridge.h                   | 14 +++-
 c/src/portfolio_context/context.rs            |  2 +
 c/src/trade_context/context.rs                | 12 ++-
 cpp/include/portfolio_context.hpp             |  2 +
 cpp/include/trade_context.hpp                 | 12 ++-
 .../portfolio/PortfolioContext.java           |  2 +
 .../com/longbridge/trade/TradeContext.java    | 12 ++-
 nodejs/index.d.ts                             | 23 +++++-
 nodejs/src/portfolio/context.rs               |  3 +
 nodejs/src/trade/context.rs                   | 16 +++-
 python/pysrc/longbridge/openapi.pyi           | 32 ++++++--
 python/src/portfolio/context.rs               |  3 +
 python/src/portfolio/context_async.rs         |  5 ++
 python/src/trade/context.rs                   | 16 +++-
 python/src/trade/context_async.rs             | 16 +++-
 rust/src/portfolio/context.rs                 | 45 ++++++++++-
 rust/src/trade/context.rs                     | 75 ++++++++++++++++++-
 18 files changed, 260 insertions(+), 31 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5841a7a2..ef12d9337 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - **All SDKs:** `fund_positions` and `margin_ratio` are AP-only (`dc_restrict(DcRegion::Ap)`): US data-center accounts have no fund holdings or margin-ratio data, so they now get a region error (`HttpClientError::DcRegionRestricted`) instead of an opaque server error. The method docs in every layer say so.
 - **All SDKs:** `order_detail` is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account, `GET /v1/trade/order` returned HTTP 500 because the order gateway panics on US orders. It now gets a region error without sending the request. US accounts should use `us_order_detail` (`GET /v1/us/orders/{order_id}`).
+- **All SDKs:** `TradeContext.account_balance` (`GET /v1/asset/account`), `TradeContext.cash_flow` (`GET /v1/asset/cashflow`), `TradeContext.stock_positions` (`GET /v1/asset/stock`) and `PortfolioContext.exchange_rate` (`GET /v1/asset/exchange_rates`) are AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account the gateway rejected them with an opaque code 12; they now get a region error (`HttpClientError::DcRegionRestricted`) without sending the request. US accounts should use `us_cash_and_buy_power` / `us_asset_overview` (balance), `us_transaction_history` (cash flow), `us_asset_overview` `stock_list` (positions) and `us_exchange_rates`. The `fund_positions` and `margin_ratio` docs now state there is no US equivalent (for US margin info see `us_cash_and_buy_power` `margin_status`).
 
 ### Fixed
 
diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h
index 882503a89..8a6706aa9 100644
--- a/c/csrc/include/longbridge.h
+++ b/c/csrc/include/longbridge.h
@@ -15659,6 +15659,8 @@ void lb_portfolio_context_release(const struct lb_portfolio_context_t *ctx);
 
 /**
  * Get exchange rates. Returns CExchangeRates.
+ *
+ * AP accounts only: US data-center accounts get a region error.
  */
 void lb_portfolio_context_exchange_rate(const struct lb_portfolio_context_t *ctx,
                                         lb_async_callback_t callback,
@@ -16487,6 +16489,8 @@ void lb_trade_context_cancel_order_attached(const struct lb_trade_context_t *ctx
 
 /**
  * Get account balance
+ *
+ * AP accounts only: US data-center accounts get a region error.
  */
 void lb_trade_context_account_balance(const struct lb_trade_context_t *ctx,
                                       const char *currency,
@@ -16496,6 +16500,8 @@ void lb_trade_context_account_balance(const struct lb_trade_context_t *ctx,
 /**
  * Get cash flow
  *
+ * AP accounts only: US data-center accounts get a region error.
+ *
  * @param[in] opts Options for get cash flow request
  */
 void lb_trade_context_cash_flow(const struct lb_trade_context_t *ctx,
@@ -16506,7 +16512,8 @@ void lb_trade_context_cash_flow(const struct lb_trade_context_t *ctx,
 /**
  * Get fund positions
  *
- * AP accounts only: US data-center accounts have no fund holdings.
+ * AP accounts only: US data-center accounts have no fund holdings and get a
+ * region error. No US equivalent.
  *
  * @param[in] opts Options for get fund positions request
  */
@@ -16518,6 +16525,8 @@ void lb_trade_context_fund_positions(const struct lb_trade_context_t *ctx,
 /**
  * Get stock positions
  *
+ * AP accounts only: US data-center accounts get a region error.
+ *
  * @param[in] opts Options for get stock positions request
  */
 void lb_trade_context_stock_positions(const struct lb_trade_context_t *ctx,
@@ -16528,7 +16537,8 @@ void lb_trade_context_stock_positions(const struct lb_trade_context_t *ctx,
 /**
  * Get margin ratio
  *
- * AP accounts only: US data-center accounts have no margin-ratio data.
+ * AP accounts only: US data-center accounts get a region error. No US
+ * equivalent.
  */
 void lb_trade_context_margin_ratio(const struct lb_trade_context_t *ctx,
                                    const char *symbol,
diff --git a/c/src/portfolio_context/context.rs b/c/src/portfolio_context/context.rs
index 42b8ec1b8..b29261e32 100644
--- a/c/src/portfolio_context/context.rs
+++ b/c/src/portfolio_context/context.rs
@@ -32,6 +32,8 @@ pub unsafe extern "C" fn lb_portfolio_context_release(ctx: *const CPortfolioCont
 }
 
 /// Get exchange rates. Returns CExchangeRates.
+///
+/// AP accounts only: US data-center accounts get a region error.
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_portfolio_context_exchange_rate(
     ctx: *const CPortfolioContext,
diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs
index 6924a8394..93365de85 100644
--- a/c/src/trade_context/context.rs
+++ b/c/src/trade_context/context.rs
@@ -726,6 +726,8 @@ pub unsafe extern "C" fn lb_trade_context_cancel_order_attached(
 }
 
 /// Get account balance
+///
+/// AP accounts only: US data-center accounts get a region error.
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_trade_context_account_balance(
     ctx: *const CTradeContext,
@@ -744,6 +746,8 @@ pub unsafe extern "C" fn lb_trade_context_account_balance(
 
 /// Get cash flow
 ///
+/// AP accounts only: US data-center accounts get a region error.
+///
 /// @param[in] opts Options for get cash flow request
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_trade_context_cash_flow(
@@ -777,7 +781,8 @@ pub unsafe extern "C" fn lb_trade_context_cash_flow(
 
 /// Get fund positions
 ///
-/// AP accounts only: US data-center accounts have no fund holdings.
+/// AP accounts only: US data-center accounts have no fund holdings and get a
+/// region error. No US equivalent.
 ///
 /// @param[in] opts Options for get fund positions request
 #[unsafe(no_mangle)]
@@ -801,6 +806,8 @@ pub unsafe extern "C" fn lb_trade_context_fund_positions(
 
 /// Get stock positions
 ///
+/// AP accounts only: US data-center accounts get a region error.
+///
 /// @param[in] opts Options for get stock positions request
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_trade_context_stock_positions(
@@ -823,7 +830,8 @@ pub unsafe extern "C" fn lb_trade_context_stock_positions(
 
 /// Get margin ratio
 ///
-/// AP accounts only: US data-center accounts have no margin-ratio data.
+/// AP accounts only: US data-center accounts get a region error. No US
+/// equivalent.
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_trade_context_margin_ratio(
     ctx: *const CTradeContext,
diff --git a/cpp/include/portfolio_context.hpp b/cpp/include/portfolio_context.hpp
index f099549cf..96ad90541 100644
--- a/cpp/include/portfolio_context.hpp
+++ b/cpp/include/portfolio_context.hpp
@@ -110,6 +110,8 @@ private: const lb_portfolio_context_t* ctx_;
   /// Create a PortfolioContext from a Config.
   static PortfolioContext create(const Config& config);
   /// Get exchange rates for all supported currencies.
+  ///
+  /// AP accounts only: US data-center accounts get a region error.
   void exchange_rate(AsyncCallback callback) const;
   /// Get portfolio P&L analysis. start/end: optional "YYYY-MM-DD"; pass empty string for none.
   void profit_analysis(const std::string& start, const std::string& end, AsyncCallback callback) const;
diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp
index effffdde4..348adcc95 100644
--- a/cpp/include/trade_context.hpp
+++ b/cpp/include/trade_context.hpp
@@ -90,34 +90,42 @@ class TradeContext
                     bool is_attached = false) const;
 
   /// Get account balance with currency
+  ///
+  /// AP accounts only: US data-center accounts get a region error.
   void account_balance(
     const std::string& currency,
     AsyncCallback> callback) const;
 
   /// Get account balance
+  ///
+  /// AP accounts only: US data-center accounts get a region error.
   void account_balance(
     AsyncCallback> callback) const;
 
   /// Get cash flow
+  ///
+  /// AP accounts only: US data-center accounts get a region error.
   void account_balance(
     const GetCashFlowOptions& opts,
     AsyncCallback> callback) const;
 
   /// Get fund positions
   ///
-  /// AP accounts only: US data-center accounts have no fund holdings.
+  /// AP accounts only: US data-center accounts have no fund holdings and get a region error. No US equivalent.
   void fund_positions(
     const std::optional& opts,
     AsyncCallback callback) const;
 
   /// Get stock positions
+  ///
+  /// AP accounts only: US data-center accounts get a region error.
   void stock_positions(
     const std::optional& opts,
     AsyncCallback callback) const;
 
   /// Get margin ratio
   ///
-  /// AP accounts only: US data-center accounts have no margin-ratio data.
+  /// AP accounts only: US data-center accounts get a region error. No US equivalent.
   void margin_ratio(const std::string& symbol,
                     AsyncCallback callback) const;
 
diff --git a/java/javasrc/src/main/java/com/longbridge/portfolio/PortfolioContext.java b/java/javasrc/src/main/java/com/longbridge/portfolio/PortfolioContext.java
index 19328b81b..42c526c6a 100644
--- a/java/javasrc/src/main/java/com/longbridge/portfolio/PortfolioContext.java
+++ b/java/javasrc/src/main/java/com/longbridge/portfolio/PortfolioContext.java
@@ -31,6 +31,8 @@ public synchronized void close() throws Exception {
 
     /**
      * Get exchange rates for supported currencies.
+     * 

+ * AP accounts only: US data-center accounts get a region error. * * @return A Future resolving to the current exchange rates * @throws OpenApiException If an error occurs 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 22ea48965..f08ebd969 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -435,6 +435,8 @@ public synchronized CompletableFuture cancelOrder(String orderId, boolean /** * Get account balance with currency + *

+ * AP accounts only: US data-center accounts get a region error. * *

      * {@code
@@ -468,6 +470,8 @@ public synchronized CompletableFuture getAccountBalance(String
 
     /**
      * Get account balance
+     * 

+ * AP accounts only: US data-center accounts get a region error. * *

      * {@code
@@ -500,6 +504,8 @@ public synchronized CompletableFuture getAccountBalance() thro
 
     /**
      * Get cash flow
+     * 

+ * AP accounts only: US data-center accounts get a region error. * *

      * {@code
@@ -538,7 +544,7 @@ public synchronized CompletableFuture getCashFlow(GetCashFlowOptions
     /**
      * Get fund positions
      * 

- * AP accounts only: US data-center accounts have no fund holdings. + * AP accounts only: US data-center accounts have no fund holdings and get a region error. No US equivalent. * *

      * {@code
@@ -571,6 +577,8 @@ public synchronized CompletableFuture getFundPositions(Ge
 
     /**
      * Get stock positions
+     * 

+ * AP accounts only: US data-center accounts get a region error. * *

      * {@code
@@ -604,7 +612,7 @@ public synchronized CompletableFuture getStockPositions(
     /**
      * Get margin ratio
      * 

- * AP accounts only: US data-center accounts have no margin-ratio data. + * AP accounts only: US data-center accounts get a region error. No US equivalent. * *

      * {@code
diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts
index b057ae279..d04821ca2 100644
--- a/nodejs/index.d.ts
+++ b/nodejs/index.d.ts
@@ -2953,7 +2953,12 @@ export declare class ParticipantInfo {
 export declare class PortfolioContext {
   /** Create a new PortfolioContext. */
   static new(config: Config): PortfolioContext
-  /** Get exchange rates for supported currencies. */
+  /**
+   * Get exchange rates for supported currencies.
+   *
+   * AP accounts only: US data-center accounts get a region error; use
+   * `TradeContext.usExchangeRates` instead.
+   */
   exchangeRate(): Promise
   /**
    * Get portfolio P&L analysis (summary + per-security breakdown).
@@ -4507,6 +4512,9 @@ export declare class TradeContext {
   /**
    * Get account balance
    *
+   * AP accounts only: US data-center accounts get a region error; use
+   * `usCashAndBuyPower` (or `usAssetOverview`) instead.
+   *
    * #### Example
    *
    * ```javascript
@@ -4524,6 +4532,9 @@ export declare class TradeContext {
   /**
    * Get cash flow
    *
+   * AP accounts only: US data-center accounts get a region error; use
+   * `usTransactionHistory` instead.
+   *
    * #### Example
    *
    * ```javascript
@@ -4544,7 +4555,8 @@ export declare class TradeContext {
   /**
    * Get fund positions
    *
-   * AP accounts only: US data-center accounts have no fund holdings.
+   * AP accounts only: US data-center accounts have no fund holdings and
+   * get a region error. No US equivalent.
    *
    * #### Example
    *
@@ -4561,6 +4573,9 @@ export declare class TradeContext {
   /**
    * Get stock positions
    *
+   * AP accounts only: US data-center accounts get a region error; use
+   * `usAssetOverview` (`stockList`) instead.
+   *
    * #### Example
    *
    * ```javascript
@@ -4576,7 +4591,9 @@ export declare class TradeContext {
   /**
    * Get margin ratio
    *
-   * AP accounts only: US data-center accounts have no margin-ratio data.
+   * AP accounts only: US data-center accounts get a region error. No US
+   * equivalent; for US margin info see `usCashAndBuyPower`
+   * (`marginStatus`).
    *
    * #### Example
    *
diff --git a/nodejs/src/portfolio/context.rs b/nodejs/src/portfolio/context.rs
index 004ff5881..a9a4712d5 100644
--- a/nodejs/src/portfolio/context.rs
+++ b/nodejs/src/portfolio/context.rs
@@ -22,6 +22,9 @@ impl PortfolioContext {
     }
 
     /// Get exchange rates for supported currencies.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `TradeContext.usExchangeRates` instead.
     #[napi]
     pub async fn exchange_rate(&self) -> Result {
         Ok(self.ctx.exchange_rate().await.map_err(ErrorNewType)?.into())
diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs
index ea4c0bab6..4090f6950 100644
--- a/nodejs/src/trade/context.rs
+++ b/nodejs/src/trade/context.rs
@@ -457,6 +457,9 @@ impl TradeContext {
 
     /// Get account balance
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `usCashAndBuyPower` (or `usAssetOverview`) instead.
+    ///
     /// #### Example
     ///
     /// ```javascript
@@ -482,6 +485,9 @@ impl TradeContext {
 
     /// Get cash flow
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `usTransactionHistory` instead.
+    ///
     /// #### Example
     ///
     /// ```javascript
@@ -510,7 +516,8 @@ impl TradeContext {
 
     /// Get fund positions
     ///
-    /// AP accounts only: US data-center accounts have no fund holdings.
+    /// AP accounts only: US data-center accounts have no fund holdings and
+    /// get a region error. No US equivalent.
     ///
     /// #### Example
     ///
@@ -536,6 +543,9 @@ impl TradeContext {
 
     /// Get stock positions
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `usAssetOverview` (`stockList`) instead.
+    ///
     /// #### Example
     ///
     /// ```javascript
@@ -560,7 +570,9 @@ impl TradeContext {
 
     /// Get margin ratio
     ///
-    /// AP accounts only: US data-center accounts have no margin-ratio data.
+    /// AP accounts only: US data-center accounts get a region error. No US
+    /// equivalent; for US margin info see `usCashAndBuyPower`
+    /// (`marginStatus`).
     ///
     /// #### Example
     ///
diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index f026ac5d2..5b91d3165 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -7904,6 +7904,8 @@ class TradeContext:
         """
         Get account balance
 
+        AP accounts only: US data-center accounts get a region error; use ``us_cash_and_buy_power`` (or ``us_asset_overview``) instead.
+
         Args:
             currency: Currency
 
@@ -7937,6 +7939,8 @@ class TradeContext:
         """
         Get cash flow
 
+        AP accounts only: US data-center accounts get a region error; use ``us_transaction_history`` instead.
+
         Args:
             start_at: Start time
             end_at: End time
@@ -7973,7 +7977,7 @@ class TradeContext:
         """
         Get fund positions
 
-        AP accounts only: US data-center accounts have no fund holdings.
+        AP accounts only: US data-center accounts have no fund holdings and get a region error. No US equivalent.
 
         Args:
             symbols: Filter by fund codes
@@ -8002,6 +8006,8 @@ class TradeContext:
         """
         Get stock positions
 
+        AP accounts only: US data-center accounts get a region error; use ``us_asset_overview`` (``stock_list``) instead.
+
         Args:
             symbols: Filter by stock codes
 
@@ -8027,7 +8033,7 @@ class TradeContext:
         """
         Get margin ratio
 
-        AP accounts only: US data-center accounts have no margin-ratio data.
+        AP accounts only: US data-center accounts get a region error. No US equivalent; for US margin info see ``us_cash_and_buy_power`` (``margin_status``).
 
         Args:
             symbol: Security symbol
@@ -11905,6 +11911,8 @@ class AsyncTradeContext:
         """
         Get account balance. Optional currency filter. Returns an awaitable that resolves to account balance list.
 
+        AP accounts only: US data-center accounts get a region error; use ``us_cash_and_buy_power`` (or ``us_asset_overview``) instead.
+
         Args:
             currency: Currency filter.
 
@@ -11939,6 +11947,8 @@ class AsyncTradeContext:
         """
         Get cash flow. Required: start_at, end_at. Optional: business_type, symbol, page, size. Returns an awaitable that resolves to cash flow list.
 
+        AP accounts only: US data-center accounts get a region error; use ``us_transaction_history`` instead.
+
         Args:
             start_at: Start time.
             end_at: End time.
@@ -11976,7 +11986,7 @@ class AsyncTradeContext:
         """
         Get fund positions. Optional filter: symbols. Returns an awaitable that resolves to fund positions response.
 
-        AP accounts only: US data-center accounts have no fund holdings.
+        AP accounts only: US data-center accounts have no fund holdings and get a region error. No US equivalent.
 
         Args:
             symbols: Filter by fund codes.
@@ -12006,6 +12016,8 @@ class AsyncTradeContext:
         """
         Get stock positions. Optional filter: symbols. Returns an awaitable that resolves to stock positions response.
 
+        AP accounts only: US data-center accounts get a region error; use ``us_asset_overview`` (``stock_list``) instead.
+
         Args:
             symbols: Filter by stock codes.
 
@@ -12032,7 +12044,7 @@ class AsyncTradeContext:
         """
         Get margin ratio for symbol. Returns an awaitable that resolves to margin ratio.
 
-        AP accounts only: US data-center accounts have no margin-ratio data.
+        AP accounts only: US data-center accounts get a region error. No US equivalent; for US margin info see ``us_cash_and_buy_power`` (``margin_status``).
 
         Args:
             symbol: Security symbol.
@@ -16458,7 +16470,11 @@ class PortfolioContext:
         ...
 
     def exchange_rate(self) -> "ExchangeRates":
-        """Get exchange rates for supported currencies."""
+        """
+        Get exchange rates for supported currencies.
+
+        AP accounts only: US data-center accounts get a region error; use ``TradeContext.us_exchange_rates`` instead.
+        """
         ...
 
     def profit_analysis(
@@ -16545,7 +16561,11 @@ class AsyncPortfolioContext:
     def create(cls, config: Config) -> AsyncPortfolioContext: ...
 
     async def exchange_rate(self) -> "ExchangeRates":
-        """Get exchange rates for supported currencies."""
+        """
+        Get exchange rates for supported currencies.
+
+        AP accounts only: US data-center accounts get a region error; use ``TradeContext.us_exchange_rates`` instead.
+        """
         ...
 
     async def profit_analysis(
diff --git a/python/src/portfolio/context.rs b/python/src/portfolio/context.rs
index 72eb47a56..419f4e2ca 100644
--- a/python/src/portfolio/context.rs
+++ b/python/src/portfolio/context.rs
@@ -21,6 +21,9 @@ impl PortfolioContext {
     }
 
     /// Get exchange rates.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `TradeContext.us_exchange_rates` instead.
     fn exchange_rate(&self) -> PyResult {
         Ok(self.ctx.exchange_rate().map_err(ErrorNewType)?.into())
     }
diff --git a/python/src/portfolio/context_async.rs b/python/src/portfolio/context_async.rs
index 35f94a64b..e3ea20bdd 100644
--- a/python/src/portfolio/context_async.rs
+++ b/python/src/portfolio/context_async.rs
@@ -19,6 +19,11 @@ impl AsyncPortfolioContext {
             ctx: Arc::new(PortfolioContext::new(Arc::new(config.0.clone()))),
         }
     }
+
+    /// Get exchange rates. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `TradeContext.us_exchange_rates` instead.
     fn exchange_rate(&self, py: Python<'_>) -> PyResult> {
         let ctx = self.ctx.clone();
         pyo3_async_runtimes::tokio::future_into_py(py, async move {
diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs
index 8d70f8830..1716dec7d 100644
--- a/python/src/trade/context.rs
+++ b/python/src/trade/context.rs
@@ -466,6 +466,9 @@ impl TradeContext {
     }
 
     /// Get account balance
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_cash_and_buy_power` (or `us_asset_overview`) instead.
     #[pyo3(signature = (currency = None))]
     fn account_balance(&self, currency: Option) -> PyResult> {
         self.ctx
@@ -477,6 +480,9 @@ impl TradeContext {
     }
 
     /// Get cash flow
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_transaction_history` instead.
     #[pyo3(signature = (start_at, end_at, business_type = None, symbol = None, page = None, size = None))]
     fn cash_flow(
         &self,
@@ -512,7 +518,8 @@ impl TradeContext {
 
     /// Get fund positions
     ///
-    /// AP accounts only: US data-center accounts have no fund holdings.
+    /// AP accounts only: US data-center accounts have no fund holdings and
+    /// get a region error. No US equivalent.
     #[pyo3(signature = (symbols = None))]
     fn fund_positions(&self, symbols: Option>) -> PyResult {
         self.ctx
@@ -522,6 +529,9 @@ impl TradeContext {
     }
 
     /// Get stock positions
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_asset_overview` (`stock_list`) instead.
     #[pyo3(signature = (symbols = None))]
     fn stock_positions(&self, symbols: Option>) -> PyResult {
         self.ctx
@@ -532,7 +542,9 @@ impl TradeContext {
 
     /// Get margin ratio
     ///
-    /// AP accounts only: US data-center accounts have no margin-ratio data.
+    /// AP accounts only: US data-center accounts get a region error. No US
+    /// equivalent; for US margin info see `us_cash_and_buy_power`
+    /// (`margin_status`).
     fn margin_ratio(&self, symbol: String) -> PyResult {
         self.ctx
             .margin_ratio(symbol)
diff --git a/python/src/trade/context_async.rs b/python/src/trade/context_async.rs
index 5dd61c941..94bc19341 100644
--- a/python/src/trade/context_async.rs
+++ b/python/src/trade/context_async.rs
@@ -524,6 +524,9 @@ impl AsyncTradeContext {
     }
 
     /// Get account balance. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_cash_and_buy_power` (or `us_asset_overview`) instead.
     #[pyo3(signature = (currency = None))]
     fn account_balance(&self, py: Python<'_>, currency: Option) -> PyResult> {
         let ctx = self.ctx.clone();
@@ -540,6 +543,9 @@ impl AsyncTradeContext {
     }
 
     /// Get cash flow. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_transaction_history` instead.
     #[allow(clippy::too_many_arguments)]
     #[pyo3(signature = (start_at, end_at, business_type = None, symbol = None, page = None, size = None))]
     fn cash_flow(
@@ -577,7 +583,8 @@ impl AsyncTradeContext {
 
     /// Get fund positions. Returns awaitable.
     ///
-    /// AP accounts only: US data-center accounts have no fund holdings.
+    /// AP accounts only: US data-center accounts have no fund holdings and
+    /// get a region error. No US equivalent.
     #[pyo3(signature = (symbols = None))]
     fn fund_positions(&self, py: Python<'_>, symbols: Option>) -> PyResult> {
         let ctx = self.ctx.clone();
@@ -594,6 +601,9 @@ impl AsyncTradeContext {
     }
 
     /// Get stock positions. Returns awaitable.
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_asset_overview` (`stock_list`) instead.
     #[pyo3(signature = (symbols = None))]
     fn stock_positions(&self, py: Python<'_>, symbols: Option>) -> PyResult> {
         let ctx = self.ctx.clone();
@@ -611,7 +621,9 @@ impl AsyncTradeContext {
 
     /// Get margin ratio. Returns awaitable.
     ///
-    /// AP accounts only: US data-center accounts have no margin-ratio data.
+    /// AP accounts only: US data-center accounts get a region error. No US
+    /// equivalent; for US margin info see `us_cash_and_buy_power`
+    /// (`margin_status`).
     fn margin_ratio(&self, py: Python<'_>, symbol: String) -> PyResult> {
         let ctx = self.ctx.clone();
         pyo3_async_runtimes::tokio::future_into_py(py, async move {
diff --git a/rust/src/portfolio/context.rs b/rust/src/portfolio/context.rs
index b40921f2c..6e29325a5 100644
--- a/rust/src/portfolio/context.rs
+++ b/rust/src/portfolio/context.rs
@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use longbridge_httpcli::{HttpClient, Json, Method};
+use longbridge_httpcli::{DcRegion, HttpClient, Json, Method};
 use serde::{Serialize, de::DeserializeOwned};
 use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
 
@@ -67,11 +67,21 @@ impl PortfolioContext {
 
     /// Get exchange rates for supported currencies.
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `TradeContext::us_exchange_rates` instead.
+    ///
     /// Path: `GET /v1/asset/exchange_rates`
     pub async fn exchange_rate(&self) -> Result {
-        #[derive(Serialize)]
-        struct Empty {}
-        self.get("/v1/asset/exchange_rates", Empty {}).await
+        Ok(self
+            .0
+            .http_cli
+            .request(Method::GET, "/v1/asset/exchange_rates")
+            .dc_restrict(DcRegion::Ap)
+            .response::>()
+            .send()
+            .with_subscriber(self.0.log_subscriber.clone())
+            .await?
+            .0)
     }
 
     // ── profit_analysis ───────────────────────────────────────────
@@ -258,3 +268,30 @@ fn date_to_unix_opt(date: Option<&str>) -> Option {
 fn date_to_unix_end_opt(date: Option<&str>) -> Option {
     date_to_unix_opt(date).map(|ts| ts + 86399)
 }
+
+#[cfg(test)]
+mod dc_restrict_tests {
+    use longbridge_httpcli::HttpClientError;
+
+    use super::*;
+    use crate::Error;
+
+    /// `exchange_rate` fails fast for a US (`us_`) token.
+    #[tokio::test(flavor = "multi_thread")]
+    async fn exchange_rate_rejects_us_token() {
+        let config = Arc::new(Config::from_apikey("us_app_key", "app_secret", "us_token"));
+        let ctx = PortfolioContext::new(config);
+        match ctx.exchange_rate().await {
+            Err(Error::HttpClient(HttpClientError::DcRegionRestricted {
+                path,
+                required,
+                current,
+            })) => {
+                assert_eq!(path, "/v1/asset/exchange_rates", "restricted path mismatch");
+                assert_eq!(required, DcRegion::Ap, "expected AP-only restriction");
+                assert_eq!(current, DcRegion::Us, "expected US session region");
+            }
+            other => panic!("expected DcRegionRestricted, got {other:?}"),
+        }
+    }
+}
diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs
index a945e039e..df47b110b 100644
--- a/rust/src/trade/context.rs
+++ b/rust/src/trade/context.rs
@@ -634,6 +634,9 @@ impl TradeContext {
 
     /// Get account balance
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_cash_and_buy_power` (or `us_asset_overview`) instead.
+    ///
     /// Reference: 
     ///
     /// # Examples
@@ -670,6 +673,7 @@ impl TradeContext {
             .0
             .http_cli
             .request(Method::GET, "/v1/asset/account")
+            .dc_restrict(DcRegion::Ap)
             .query_params(Request { currency })
             .response::>()
             .send()
@@ -681,6 +685,9 @@ impl TradeContext {
 
     /// Get cash flow
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_transaction_history` instead.
+    ///
     /// Reference: 
     ///
     /// # Examples
@@ -718,6 +725,7 @@ impl TradeContext {
             .0
             .http_cli
             .request(Method::GET, "/v1/asset/cashflow")
+            .dc_restrict(DcRegion::Ap)
             .query_params(options)
             .response::>()
             .send()
@@ -731,8 +739,8 @@ impl TradeContext {
     ///
     /// Reference: 
     ///
-    /// AP accounts only: US data-center accounts have no fund holdings, and
-    /// the call returns an error for them.
+    /// AP accounts only: US data-center accounts have no fund holdings and
+    /// get a region error. No US equivalent.
     ///
     /// # Examples
     ///
@@ -772,6 +780,9 @@ impl TradeContext {
 
     /// Get stock positions
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_asset_overview` (`stock_list`) instead.
+    ///
     /// Reference: 
     ///
     /// # Examples
@@ -801,6 +812,7 @@ impl TradeContext {
             .0
             .http_cli
             .request(Method::GET, "/v1/asset/stock")
+            .dc_restrict(DcRegion::Ap)
             .query_params(opts.into().unwrap_or_default())
             .response::>()
             .send()
@@ -813,8 +825,9 @@ impl TradeContext {
     ///
     /// Reference: 
     ///
-    /// AP accounts only: US data-center accounts have no margin-ratio data,
-    /// and the call returns an error for them.
+    /// AP accounts only: US data-center accounts get a region error. No US
+    /// equivalent; for US margin info see `us_cash_and_buy_power`
+    /// (`margin_status`).
     ///
     /// # Examples
     ///
@@ -1243,3 +1256,57 @@ impl TradeContext {
             .0)
     }
 }
+
+#[cfg(test)]
+mod dc_restrict_tests {
+    use longbridge_httpcli::HttpClientError;
+    use time::macros::datetime;
+
+    use super::*;
+    use crate::Error;
+
+    fn us_trade_context() -> TradeContext {
+        let config = Arc::new(Config::from_apikey("us_app_key", "app_secret", "us_token"));
+        TradeContext::new(config).0
+    }
+
+    #[track_caller]
+    fn assert_ap_restricted(res: Result, path: &str)
+    where
+        T: std::fmt::Debug,
+    {
+        match res {
+            Err(Error::HttpClient(HttpClientError::DcRegionRestricted {
+                path: p,
+                required,
+                current,
+            })) => {
+                assert_eq!(p, path, "restricted path mismatch");
+                assert_eq!(required, DcRegion::Ap, "expected AP-only restriction");
+                assert_eq!(current, DcRegion::Us, "expected US session region");
+            }
+            other => panic!("expected DcRegionRestricted for {path}, got {other:?}"),
+        }
+    }
+
+    /// AP-only asset/risk/order endpoints fail fast for a US (`us_`) token
+    /// instead of sending a request the US data center rejects.
+    #[tokio::test(flavor = "multi_thread")]
+    async fn ap_only_endpoints_reject_us_token() {
+        let ctx = us_trade_context();
+
+        assert_ap_restricted(ctx.account_balance(None).await, "/v1/asset/account");
+        assert_ap_restricted(
+            ctx.cash_flow(GetCashFlowOptions::new(
+                datetime!(2026-01-01 0:00 UTC),
+                datetime!(2026-01-02 0:00 UTC),
+            ))
+            .await,
+            "/v1/asset/cashflow",
+        );
+        assert_ap_restricted(ctx.fund_positions(None).await, "/v1/asset/fund");
+        assert_ap_restricted(ctx.stock_positions(None).await, "/v1/asset/stock");
+        assert_ap_restricted(ctx.margin_ratio("700.HK").await, "/v1/risk/margin-ratio");
+        assert_ap_restricted(ctx.order_detail("1").await, "/v1/trade/order");
+    }
+}

From 56796291d1759414f3aed54cd6e18ab350f6fcd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Fri, 23 Oct 2026 10:27:47 +0800
Subject: [PATCH 09/16] style: wrap long comments for the latest nightly
 rustfmt

---
 java/src/init.rs                        | 6 ++++--
 python/src/fundamental/context_async.rs | 3 ++-
 rust/src/lib.rs                         | 2 +-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/java/src/init.rs b/java/src/init.rs
index 154b7caa3..fa1e80222 100644
--- a/java/src/init.rs
+++ b/java/src/init.rs
@@ -288,7 +288,8 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init<'a>(
         longbridge::dca::DcaSupportInfo,
         longbridge::dca::DcaSupportList,
         longbridge::dca::DcaCalcDateResult,
-        // AlertContext - list returns JSON via AlertList IntoJValue (serde_json)
+        // AlertContext - list returns JSON via AlertList IntoJValue
+        // (serde_json)
         longbridge::dca::DcaPlan,
         longbridge::dca::DcaList,
         longbridge::dca::DcaStats,
@@ -360,7 +361,8 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init<'a>(
         longbridge::dca::DcaSupportInfo,
         longbridge::dca::DcaSupportList,
         longbridge::dca::DcaCalcDateResult,
-        // AlertContext - list returns JSON via AlertList IntoJValue (serde_json)
+        // AlertContext - list returns JSON via AlertList IntoJValue
+        // (serde_json)
         longbridge::dca::DcaPlan,
         longbridge::dca::DcaList,
         longbridge::dca::DcaStats,
diff --git a/python/src/fundamental/context_async.rs b/python/src/fundamental/context_async.rs
index da0557080..8e95ae414 100644
--- a/python/src/fundamental/context_async.rs
+++ b/python/src/fundamental/context_async.rs
@@ -2,7 +2,8 @@ use std::sync::Arc;
 
 use longbridge::FundamentalContext;
 use pyo3::{prelude::*, types::PyType};
-// needed for us_financial_overview / us_key_financial_metrics / us_analyst_consensus
+// needed for us_financial_overview / us_key_financial_metrics /
+// us_analyst_consensus
 #[allow(unused_imports)]
 use pythonize;
 
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 3e46352cb..3bc6e0813 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -47,7 +47,7 @@ pub use dca::DCAContext;
 pub use error::{Error, Result, SimpleError, SimpleErrorKind};
 pub use fund::FundContext;
 pub use fundamental::FundamentalContext;
-// ── US-market type re-exports ─────────────────────────────────────────────────
+// US-market type re-exports
 pub use fundamental::types::{
     FinancialStatementKind, IndustryRankIndicator, IndustryRankSortType, USCompanyDividends,
     USCompanyOverview, USDividendItem, USETFDividendInfo, USETFFile, USETFFilesResponse,

From 93f3b42f0d0dac7ddbee7c42c7e7571228c927b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Fri, 23 Oct 2026 11:09:37 +0800
Subject: [PATCH 10/16] fix(httpclient): name the alternative API in region
 errors

Append a path-based hint to the HttpClientError::DcRegionRestricted
message so callers and LLM agents know what to call instead: the
us_* equivalent for AP-only asset/order APIs, "no US equivalent" for
fund positions and margin ratio, and a US-accounts-only note for US
APIs. The existing message stays as the prefix and the variant's
fields are unchanged.

Also add the AP-only and "US token required" notes to the blocking
trade/portfolio wrappers, and fix the AsyncPortfolioContext stub that
pointed at the sync TradeContext.
---
 CHANGELOG.md                        |   1 +
 python/pysrc/longbridge/openapi.pyi |   2 +-
 rust/crates/httpclient/src/error.rs | 106 +++++++++++++++++++++++++++-
 rust/src/blocking/portfolio.rs      |   3 +
 rust/src/blocking/trade.rs          |  35 +++++++++
 5 files changed, 145 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef12d9337..7b7d2b6c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+- **All SDKs:** the `HttpClientError::DcRegionRestricted` message now names the API to call instead, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept as a prefix and a hint is appended by path: `/v1/asset/account` → `us_cash_and_buy_power` or `us_asset_overview`; `/v1/asset/cashflow` → `us_transaction_history`; `/v1/asset/stock` → `us_asset_overview`; `/v1/asset/exchange_rates` → `us_exchange_rates`; `/v1/trade/order` → `us_order_detail`; `/v1/asset/fund` and `/v1/risk/margin-ratio` → no US equivalent (margin: `us_cash_and_buy_power` `margin_status`); US-only APIs called from an AP account → "this API is for US data-center accounts only". The variant's fields are unchanged, and bindings still surface it as error kind `Other`.
 - **Rust core, Node.js, Python:** `USAssetOverview.crypto_list` entries (`USCryptoEntry`) now carry the holding `quantity`, `market`, `name`, prices (`last_done`, `market_price`, `prev_close`, ...), `today_pl`, `position_side` and the other fields `GET /v1/us/assets/overview` returns for crypto, which were previously dropped, so a crypto holding could not be sized. `symbol` is the bare pair code (e.g. `BTCUSD`); the user-facing symbol is `.` (e.g. `BTCUSD.BKKT`).
 - **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged
 
diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index 5b91d3165..90bd7844e 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -16564,7 +16564,7 @@ class AsyncPortfolioContext:
         """
         Get exchange rates for supported currencies.
 
-        AP accounts only: US data-center accounts get a region error; use ``TradeContext.us_exchange_rates`` instead.
+        AP accounts only: US data-center accounts get a region error; use ``AsyncTradeContext.us_exchange_rates`` instead.
         """
         ...
 
diff --git a/rust/crates/httpclient/src/error.rs b/rust/crates/httpclient/src/error.rs
index 7636be944..840661781 100644
--- a/rust/crates/httpclient/src/error.rs
+++ b/rust/crates/httpclient/src/error.rs
@@ -37,8 +37,12 @@ pub enum HttpClientError {
 
     /// The requested API is restricted to one data center and cannot be reached
     /// from a session in a different region.
+    ///
+    /// The message names the API to call instead when one exists, so callers
+    /// (including LLM agents) can recover without parsing the path.
     #[error(
-        "this API ({path}) is only available in the {required} data center and is not supported for your {current}-region account"
+        "this API ({path}) is only available in the {required} data center and is not supported for your {current}-region account{}",
+        DcRegionHint::new(.path, *.required)
     )]
     DcRegionRestricted {
         /// The restricted API path (or WebSocket command) that was requested.
@@ -111,6 +115,44 @@ pub enum HttpClientError {
     Sse(String),
 }
 
+/// Suffix appended to [`HttpClientError::DcRegionRestricted`] messages that
+/// tells the caller which API to use instead.
+struct DcRegionHint(Option<&'static str>);
+
+impl DcRegionHint {
+    fn new(path: &str, required: DcRegion) -> Self {
+        Self(region_alternative(path, required))
+    }
+}
+
+impl std::fmt::Display for DcRegionHint {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self.0 {
+            Some(hint) => write!(f, "; {hint}"),
+            None => Ok(()),
+        }
+    }
+}
+
+/// Returns the recovery hint for a region-restricted API path.
+fn region_alternative(path: &str, required: DcRegion) -> Option<&'static str> {
+    match (path, required) {
+        ("/v1/asset/account", DcRegion::Ap) => {
+            Some("use us_cash_and_buy_power or us_asset_overview instead")
+        }
+        ("/v1/asset/cashflow", DcRegion::Ap) => Some("use us_transaction_history instead"),
+        ("/v1/asset/stock", DcRegion::Ap) => Some("use us_asset_overview instead"),
+        ("/v1/asset/exchange_rates", DcRegion::Ap) => Some("use us_exchange_rates instead"),
+        ("/v1/trade/order", DcRegion::Ap) => Some("use us_order_detail instead"),
+        ("/v1/asset/fund", DcRegion::Ap) => Some("there is no US equivalent"),
+        ("/v1/risk/margin-ratio", DcRegion::Ap) => {
+            Some("there is no US equivalent; see margin_status in us_cash_and_buy_power instead")
+        }
+        (_, DcRegion::Us) => Some("this API is for US data-center accounts only"),
+        _ => None,
+    }
+}
+
 /// Represents an HTTP error
 #[derive(Debug)]
 pub struct HttpError(pub reqwest::Error);
@@ -136,3 +178,65 @@ impl std::error::Error for HttpError {}
 
 /// Http client result type
 pub type HttpClientResult = std::result::Result;
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn restricted(path: &str, required: DcRegion) -> String {
+        let current = match required {
+            DcRegion::Ap => DcRegion::Us,
+            DcRegion::Us => DcRegion::Ap,
+        };
+        HttpClientError::DcRegionRestricted {
+            path: path.to_string(),
+            required,
+            current,
+        }
+        .to_string()
+    }
+
+    #[test]
+    fn dc_region_restricted_names_us_alternative() {
+        let cases = [
+            (
+                "/v1/asset/account",
+                "use us_cash_and_buy_power or us_asset_overview instead",
+            ),
+            ("/v1/asset/cashflow", "use us_transaction_history instead"),
+            ("/v1/asset/stock", "use us_asset_overview instead"),
+            ("/v1/asset/exchange_rates", "use us_exchange_rates instead"),
+            ("/v1/trade/order", "use us_order_detail instead"),
+            ("/v1/asset/fund", "there is no US equivalent"),
+            (
+                "/v1/risk/margin-ratio",
+                "there is no US equivalent; see margin_status in us_cash_and_buy_power instead",
+            ),
+        ];
+        for (path, hint) in cases {
+            assert_eq!(
+                restricted(path, DcRegion::Ap),
+                format!(
+                    "this API ({path}) is only available in the AP data center and is not supported for your US-region account; {hint}"
+                ),
+                "unexpected message for {path}"
+            );
+        }
+    }
+
+    #[test]
+    fn dc_region_restricted_us_only_hint() {
+        assert_eq!(
+            restricted("/v1/us/assets/overview", DcRegion::Us),
+            "this API (/v1/us/assets/overview) is only available in the US data center and is not supported for your AP-region account; this API is for US data-center accounts only"
+        );
+    }
+
+    #[test]
+    fn dc_region_restricted_without_hint_keeps_message() {
+        assert_eq!(
+            restricted("quote/brokers", DcRegion::Ap),
+            "this API (quote/brokers) is only available in the AP data center and is not supported for your US-region account"
+        );
+    }
+}
diff --git a/rust/src/blocking/portfolio.rs b/rust/src/blocking/portfolio.rs
index cb9b376c6..1d7e1864d 100644
--- a/rust/src/blocking/portfolio.rs
+++ b/rust/src/blocking/portfolio.rs
@@ -29,6 +29,9 @@ impl PortfolioContextSync {
     }
 
     /// Get exchange rates
+    ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `TradeContextSync::us_exchange_rates` instead.
     pub fn exchange_rate(&self) -> Result {
         self.rt.call(|ctx| async move { ctx.exchange_rate().await })
     }
diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs
index 74e9a9472..9fdad6060 100644
--- a/rust/src/blocking/trade.rs
+++ b/rust/src/blocking/trade.rs
@@ -313,6 +313,9 @@ impl TradeContextSync {
 
     /// Get account balance
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_cash_and_buy_power` (or `us_asset_overview`) instead.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -339,6 +342,9 @@ impl TradeContextSync {
 
     /// Get cash flow
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_transaction_history` instead.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -366,6 +372,9 @@ impl TradeContextSync {
 
     /// Get fund positions
     ///
+    /// AP accounts only: US data-center accounts have no fund holdings and
+    /// get a region error. No US equivalent.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -394,6 +403,9 @@ impl TradeContextSync {
 
     /// Get stock positions
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_asset_overview` (`stock_list`) instead.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -422,6 +434,10 @@ impl TradeContextSync {
 
     /// Get margin ratio
     ///
+    /// AP accounts only: US data-center accounts get a region error. No US
+    /// equivalent; for US margin info see `us_cash_and_buy_power`
+    /// (`margin_status`).
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -447,6 +463,9 @@ impl TradeContextSync {
 
     /// Get order detail
     ///
+    /// AP accounts only: US data-center accounts get a region error; use
+    /// `us_order_detail` instead.
+    ///
     /// Reference: 
     ///
     /// # Examples
@@ -489,12 +508,16 @@ impl TradeContextSync {
     // ───────────────────────────────────────────
 
     /// Query the paginated US order list (blocking)
+    ///
+    /// US token required.
     pub fn us_query_orders(&self, opts: GetUSHistoryOrders) -> Result {
         self.rt
             .call(move |ctx| async move { ctx.us_query_orders(opts).await })
     }
 
     /// Get US order detail (blocking)
+    ///
+    /// US token required.
     pub fn us_order_detail(
         &self,
         order_id: impl Into + Send + 'static,
@@ -504,18 +527,24 @@ impl TradeContextSync {
     }
 
     /// Get the full US account asset overview (blocking)
+    ///
+    /// US token required.
     pub fn us_asset_overview(&self) -> Result {
         self.rt
             .call(move |ctx| async move { ctx.us_asset_overview().await })
     }
 
     /// Get realized P&L for the US account (blocking)
+    ///
+    /// US token required.
     pub fn us_realized_pl(&self, opts: GetUSRealizedPLOptions) -> Result {
         self.rt
             .call(move |ctx| async move { ctx.us_realized_pl(opts).await })
     }
 
     /// Get cash, buying power and margin status for the US account (blocking)
+    ///
+    /// US token required.
     pub fn us_cash_and_buy_power(&self, currency: Option<&str>) -> Result {
         let currency = currency.map(ToString::to_string);
         self.rt
@@ -523,6 +552,8 @@ impl TradeContextSync {
     }
 
     /// Get the quantity of a US holding that is available to trade (blocking)
+    ///
+    /// US token required.
     pub fn us_available_holding(
         &self,
         opts: GetUSAvailableHoldingOptions,
@@ -532,6 +563,8 @@ impl TradeContextSync {
     }
 
     /// Get the US account transaction history (blocking)
+    ///
+    /// US token required.
     pub fn us_transaction_history(
         &self,
         opts: GetUSTransactionHistoryOptions,
@@ -541,6 +574,8 @@ impl TradeContextSync {
     }
 
     /// Get the exchange rates used by the US account (blocking)
+    ///
+    /// US token required.
     pub fn us_exchange_rates(&self) -> Result {
         self.rt
             .call(move |ctx| async move { ctx.us_exchange_rates().await })

From 059c009b35645d8202689aeb6b0baf1e8e6a9562 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Fri, 23 Oct 2026 11:36:46 +0800
Subject: [PATCH 11/16] fix(httpclient): name alternatives by endpoint in
 region errors

The region-error hint named Rust/Python methods, but the text is shared by Node.js, Java, C/C++ and MCP servers whose names differ. Name the alternative by HTTP endpoint instead, cover every dc_restrict call site in both directions, and state when there is no equivalent.
---
 CHANGELOG.md                        |   3 +-
 rust/crates/httpclient/src/error.rs | 190 +++++++++++++++++++++++-----
 2 files changed, 162 insertions(+), 31 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b7d2b6c0..863c83f00 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,10 +21,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - **All SDKs:** `fund_positions` and `margin_ratio` are AP-only (`dc_restrict(DcRegion::Ap)`): US data-center accounts have no fund holdings or margin-ratio data, so they now get a region error (`HttpClientError::DcRegionRestricted`) instead of an opaque server error. The method docs in every layer say so.
 - **All SDKs:** `order_detail` is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account, `GET /v1/trade/order` returned HTTP 500 because the order gateway panics on US orders. It now gets a region error without sending the request. US accounts should use `us_order_detail` (`GET /v1/us/orders/{order_id}`).
 - **All SDKs:** `TradeContext.account_balance` (`GET /v1/asset/account`), `TradeContext.cash_flow` (`GET /v1/asset/cashflow`), `TradeContext.stock_positions` (`GET /v1/asset/stock`) and `PortfolioContext.exchange_rate` (`GET /v1/asset/exchange_rates`) are AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account the gateway rejected them with an opaque code 12; they now get a region error (`HttpClientError::DcRegionRestricted`) without sending the request. US accounts should use `us_cash_and_buy_power` / `us_asset_overview` (balance), `us_transaction_history` (cash flow), `us_asset_overview` `stock_list` (positions) and `us_exchange_rates`. The `fund_positions` and `margin_ratio` docs now state there is no US equivalent (for US margin info see `us_cash_and_buy_power` `margin_status`).
+- **All SDKs:** the `HttpClientError::DcRegionRestricted` message (its `Display` text) gained a suffix naming the alternative by HTTP endpoint, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept unchanged as a prefix and `; ` is appended; code that matches the message by exact equality must be updated (prefix matching keeps working). The hint names endpoints rather than methods because the text is shared by every binding (Rust/Python snake_case, Node.js camelCase, Java/C/C++ without the US methods) and by MCP servers. US-region accounts calling an AP-only path: `/v1/asset/account` → `GET /v1/us/assets/cash-and-buy-power` or `GET /v1/us/assets/overview`; `/v1/asset/cashflow` → `POST /v1/us/assets/transaction-history`; `/v1/asset/stock` → `GET /v1/us/assets/overview`; `/v1/asset/exchange_rates` → `GET /v1/us/assets/exchange-rates` (on `TradeContext`); `/v1/trade/order` → `GET /v1/us/orders/{order_id}`; `/v1/risk/margin-ratio` → no US equivalent (account-level margin: `GET /v1/us/assets/cash-and-buy-power` `margin_status`); `/v1/asset/fund`, quote `brokers`, `/v1/quote/operatings`, `/v1/quote/broker-holding*` and DCA `/v1/dailycoins/*` → no US equivalent. AP-region accounts calling a US-only path: `/v1/us/orders/{id}` → `GET /v1/trade/order`; `/v1/us/orders/query` → `GET /v1/trade/order/today` or `/v1/trade/order/history`; `/v1/us/assets/overview` → `GET /v1/asset/account` + `GET /v1/asset/stock`; `/v1/us/assets/transaction-history` → `GET /v1/asset/cashflow`; `/v1/us/assets/exchange-rates` → `GET /v1/asset/exchange_rates`; `/v1/us/assets/cash-and-buy-power` → `GET /v1/asset/account`; `/v1/us/assets/available-holding` → `GET /v1/asset/stock` (`available_quantity`); other US paths (realized P&L, fundamentals, crypto overview) → no AP equivalent. The variant's fields are unchanged, and bindings still surface it as error kind `Other`.
+- **Rust blocking, Python:** the AP-only methods on `TradeContextSync` / `PortfolioContextSync` now document the region restriction and the US alternative, and the `AsyncPortfolioContext.exchange_rate` docstring in `openapi.pyi` points to `AsyncTradeContext.us_exchange_rates` (not the sync `TradeContext`).
 
 ### Fixed
 
-- **All SDKs:** the `HttpClientError::DcRegionRestricted` message now names the API to call instead, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept as a prefix and a hint is appended by path: `/v1/asset/account` → `us_cash_and_buy_power` or `us_asset_overview`; `/v1/asset/cashflow` → `us_transaction_history`; `/v1/asset/stock` → `us_asset_overview`; `/v1/asset/exchange_rates` → `us_exchange_rates`; `/v1/trade/order` → `us_order_detail`; `/v1/asset/fund` and `/v1/risk/margin-ratio` → no US equivalent (margin: `us_cash_and_buy_power` `margin_status`); US-only APIs called from an AP account → "this API is for US data-center accounts only". The variant's fields are unchanged, and bindings still surface it as error kind `Other`.
 - **Rust core, Node.js, Python:** `USAssetOverview.crypto_list` entries (`USCryptoEntry`) now carry the holding `quantity`, `market`, `name`, prices (`last_done`, `market_price`, `prev_close`, ...), `today_pl`, `position_side` and the other fields `GET /v1/us/assets/overview` returns for crypto, which were previously dropped, so a crypto holding could not be sized. `symbol` is the bare pair code (e.g. `BTCUSD`); the user-facing symbol is `.` (e.g. `BTCUSD.BKKT`).
 - **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged
 
diff --git a/rust/crates/httpclient/src/error.rs b/rust/crates/httpclient/src/error.rs
index 840661781..d31757d23 100644
--- a/rust/crates/httpclient/src/error.rs
+++ b/rust/crates/httpclient/src/error.rs
@@ -134,23 +134,78 @@ impl std::fmt::Display for DcRegionHint {
     }
 }
 
+/// Hint for an AP-only path with no US counterpart.
+const NO_US_EQUIVALENT: &str = "there is no US equivalent";
+
+/// Hint for a US-only path with no AP counterpart.
+const NO_AP_EQUIVALENT: &str = "there is no AP equivalent";
+
 /// Returns the recovery hint for a region-restricted API path.
+///
+/// The hint is shared by every binding (Rust, Python, Node.js, Java, C, C++)
+/// and by MCP servers built on the SDK, whose method and tool names differ,
+/// so it names the alternative by HTTP endpoint rather than by method.
 fn region_alternative(path: &str, required: DcRegion) -> Option<&'static str> {
-    match (path, required) {
-        ("/v1/asset/account", DcRegion::Ap) => {
-            Some("use us_cash_and_buy_power or us_asset_overview instead")
+    match required {
+        DcRegion::Ap => ap_only_alternative(path),
+        DcRegion::Us => us_only_alternative(path),
+    }
+}
+
+/// Hint for a US-region account calling an AP-only path.
+fn ap_only_alternative(path: &str) -> Option<&'static str> {
+    let hint = match path {
+        "/v1/asset/account" => {
+            "use the US endpoint GET /v1/us/assets/cash-and-buy-power or GET \
+             /v1/us/assets/overview (TradeContext US methods)"
         }
-        ("/v1/asset/cashflow", DcRegion::Ap) => Some("use us_transaction_history instead"),
-        ("/v1/asset/stock", DcRegion::Ap) => Some("use us_asset_overview instead"),
-        ("/v1/asset/exchange_rates", DcRegion::Ap) => Some("use us_exchange_rates instead"),
-        ("/v1/trade/order", DcRegion::Ap) => Some("use us_order_detail instead"),
-        ("/v1/asset/fund", DcRegion::Ap) => Some("there is no US equivalent"),
-        ("/v1/risk/margin-ratio", DcRegion::Ap) => {
-            Some("there is no US equivalent; see margin_status in us_cash_and_buy_power instead")
+        "/v1/asset/cashflow" => {
+            "use the US endpoint POST /v1/us/assets/transaction-history (TradeContext US methods)"
         }
-        (_, DcRegion::Us) => Some("this API is for US data-center accounts only"),
-        _ => None,
-    }
+        "/v1/asset/stock" => {
+            "use the US endpoint GET /v1/us/assets/overview (TradeContext US methods)"
+        }
+        "/v1/asset/exchange_rates" => {
+            "the US equivalent is on TradeContext (GET /v1/us/assets/exchange-rates)"
+        }
+        "/v1/trade/order" => {
+            "use the US endpoint GET /v1/us/orders/{order_id} (TradeContext US methods)"
+        }
+        "/v1/risk/margin-ratio" => {
+            "there is no US equivalent; account-level margin is at GET \
+             /v1/us/assets/cash-and-buy-power (margin_status)"
+        }
+        "/v1/asset/fund" | "quote/brokers" | "/v1/quote/operatings" => NO_US_EQUIVALENT,
+        _ if path.starts_with("/v1/quote/broker-holding")
+            || path.starts_with("/v1/dailycoins/") =>
+        {
+            NO_US_EQUIVALENT
+        }
+        _ => return None,
+    };
+    Some(hint)
+}
+
+/// Hint for an AP-region account calling a US-only path.
+fn us_only_alternative(path: &str) -> Option<&'static str> {
+    let hint = match path {
+        "/v1/us/orders/query" => {
+            "use the AP endpoint GET /v1/trade/order/today or GET /v1/trade/order/history"
+        }
+        "/v1/us/assets/overview" => {
+            "use the AP endpoints GET /v1/asset/account and GET /v1/asset/stock"
+        }
+        "/v1/us/assets/transaction-history" => "use the AP endpoint GET /v1/asset/cashflow",
+        "/v1/us/assets/exchange-rates" => "use the AP endpoint GET /v1/asset/exchange_rates",
+        "/v1/us/assets/cash-and-buy-power" => "use the AP endpoint GET /v1/asset/account",
+        "/v1/us/assets/available-holding" => {
+            "use the AP endpoint GET /v1/asset/stock (available_quantity)"
+        }
+        _ if path.starts_with("/v1/us/orders/") => "use the AP endpoint GET /v1/trade/order",
+        _ if path.starts_with("/v1/us/") => NO_AP_EQUIVALENT,
+        _ => return None,
+    };
+    Some(hint)
 }
 
 /// Represents an HTTP error
@@ -196,47 +251,122 @@ mod tests {
         .to_string()
     }
 
+    const AP_PREFIX: &str =
+        "is only available in the AP data center and is not supported for your US-region account";
+    const US_PREFIX: &str =
+        "is only available in the US data center and is not supported for your AP-region account";
+
     #[test]
     fn dc_region_restricted_names_us_alternative() {
         let cases = [
             (
                 "/v1/asset/account",
-                "use us_cash_and_buy_power or us_asset_overview instead",
+                "use the US endpoint GET /v1/us/assets/cash-and-buy-power or GET /v1/us/assets/overview (TradeContext US methods)",
+            ),
+            (
+                "/v1/asset/cashflow",
+                "use the US endpoint POST /v1/us/assets/transaction-history (TradeContext US methods)",
+            ),
+            (
+                "/v1/asset/stock",
+                "use the US endpoint GET /v1/us/assets/overview (TradeContext US methods)",
+            ),
+            (
+                "/v1/asset/exchange_rates",
+                "the US equivalent is on TradeContext (GET /v1/us/assets/exchange-rates)",
+            ),
+            (
+                "/v1/trade/order",
+                "use the US endpoint GET /v1/us/orders/{order_id} (TradeContext US methods)",
             ),
-            ("/v1/asset/cashflow", "use us_transaction_history instead"),
-            ("/v1/asset/stock", "use us_asset_overview instead"),
-            ("/v1/asset/exchange_rates", "use us_exchange_rates instead"),
-            ("/v1/trade/order", "use us_order_detail instead"),
-            ("/v1/asset/fund", "there is no US equivalent"),
             (
                 "/v1/risk/margin-ratio",
-                "there is no US equivalent; see margin_status in us_cash_and_buy_power instead",
+                "there is no US equivalent; account-level margin is at GET /v1/us/assets/cash-and-buy-power (margin_status)",
+            ),
+            ("/v1/asset/fund", "there is no US equivalent"),
+            ("quote/brokers", "there is no US equivalent"),
+            ("/v1/quote/operatings", "there is no US equivalent"),
+            ("/v1/quote/broker-holding", "there is no US equivalent"),
+            (
+                "/v1/quote/broker-holding/detail",
+                "there is no US equivalent",
+            ),
+            (
+                "/v1/quote/broker-holding/daily",
+                "there is no US equivalent",
             ),
+            ("/v1/dailycoins/query", "there is no US equivalent"),
+            ("/v1/dailycoins/create", "there is no US equivalent"),
         ];
         for (path, hint) in cases {
             assert_eq!(
                 restricted(path, DcRegion::Ap),
-                format!(
-                    "this API ({path}) is only available in the AP data center and is not supported for your US-region account; {hint}"
-                ),
+                format!("this API ({path}) {AP_PREFIX}; {hint}"),
                 "unexpected message for {path}"
             );
         }
     }
 
     #[test]
-    fn dc_region_restricted_us_only_hint() {
-        assert_eq!(
-            restricted("/v1/us/assets/overview", DcRegion::Us),
-            "this API (/v1/us/assets/overview) is only available in the US data center and is not supported for your AP-region account; this API is for US data-center accounts only"
-        );
+    fn dc_region_restricted_names_ap_alternative() {
+        let cases = [
+            (
+                "/v1/us/orders/123",
+                "use the AP endpoint GET /v1/trade/order",
+            ),
+            (
+                "/v1/us/orders/query",
+                "use the AP endpoint GET /v1/trade/order/today or GET /v1/trade/order/history",
+            ),
+            (
+                "/v1/us/assets/overview",
+                "use the AP endpoints GET /v1/asset/account and GET /v1/asset/stock",
+            ),
+            (
+                "/v1/us/assets/transaction-history",
+                "use the AP endpoint GET /v1/asset/cashflow",
+            ),
+            (
+                "/v1/us/assets/exchange-rates",
+                "use the AP endpoint GET /v1/asset/exchange_rates",
+            ),
+            (
+                "/v1/us/assets/cash-and-buy-power",
+                "use the AP endpoint GET /v1/asset/account",
+            ),
+            (
+                "/v1/us/assets/available-holding",
+                "use the AP endpoint GET /v1/asset/stock (available_quantity)",
+            ),
+            ("/v1/us/assets/pl/realized", "there is no AP equivalent"),
+            ("/v1/us/gemini/crypto-overview", "there is no AP equivalent"),
+            (
+                "/v1/us/stock-info/company-overview",
+                "there is no AP equivalent",
+            ),
+            (
+                "/v1/us/quote/financials/statements",
+                "there is no AP equivalent",
+            ),
+        ];
+        for (path, hint) in cases {
+            assert_eq!(
+                restricted(path, DcRegion::Us),
+                format!("this API ({path}) {US_PREFIX}; {hint}"),
+                "unexpected message for {path}"
+            );
+        }
     }
 
     #[test]
     fn dc_region_restricted_without_hint_keeps_message() {
         assert_eq!(
-            restricted("quote/brokers", DcRegion::Ap),
-            "this API (quote/brokers) is only available in the AP data center and is not supported for your US-region account"
+            restricted("/v1/unknown", DcRegion::Ap),
+            format!("this API (/v1/unknown) {AP_PREFIX}")
+        );
+        assert_eq!(
+            restricted("/v1/unknown", DcRegion::Us),
+            format!("this API (/v1/unknown) {US_PREFIX}")
         );
     }
 }

From a932a17469989fd8060a3d6ee90d587bb94eae23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?=
 
Date: Fri, 23 Oct 2026 11:49:31 +0800
Subject: [PATCH 12/16] fix(trade): restrict all_executions to AP and point US
 fundamentals hints at AP endpoints

GET /v3/trade/execution/all answers HTTP 500 code 12 for US accounts. Guard all_executions with dc_restrict(DcRegion::Ap) and hint at GET /v1/trade/execution/history. AP accounts calling US fundamentals now get pointed at the matching /v1/quote/ endpoints, and the exchange-rates hint uses the same wording as the other US hints.
---
 CHANGELOG.md                                  |  3 +-
 c/csrc/include/longbridge.h                   |  3 ++
 c/src/trade_context/context.rs                |  3 ++
 cpp/include/trade_context.hpp                 |  3 ++
 .../com/longbridge/trade/TradeContext.java    |  3 ++
 nodejs/index.d.ts                             |  7 ++-
 nodejs/src/trade/context.rs                   |  3 ++
 python/pysrc/longbridge/openapi.pyi           |  4 ++
 python/src/trade/context.rs                   |  3 ++
 python/src/trade/context_async.rs             |  3 ++
 rust/crates/httpclient/src/error.rs           | 51 +++++++++++++++++--
 rust/src/blocking/trade.rs                    |  3 ++
 rust/src/trade/context.rs                     |  5 ++
 13 files changed, 88 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 863c83f00..19489608d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,9 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - **All SDKs:** `fund_positions` and `margin_ratio` are AP-only (`dc_restrict(DcRegion::Ap)`): US data-center accounts have no fund holdings or margin-ratio data, so they now get a region error (`HttpClientError::DcRegionRestricted`) instead of an opaque server error. The method docs in every layer say so.
+- **All SDKs:** `all_executions` (`GET /v3/trade/execution/all`) is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account the gateway answered HTTP 500 with code 12; it now gets a region error without sending the request. US accounts should use `history_executions` (`GET /v1/trade/execution/history`), which works in both regions. The method docs in every layer say so.
 - **All SDKs:** `order_detail` is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account, `GET /v1/trade/order` returned HTTP 500 because the order gateway panics on US orders. It now gets a region error without sending the request. US accounts should use `us_order_detail` (`GET /v1/us/orders/{order_id}`).
 - **All SDKs:** `TradeContext.account_balance` (`GET /v1/asset/account`), `TradeContext.cash_flow` (`GET /v1/asset/cashflow`), `TradeContext.stock_positions` (`GET /v1/asset/stock`) and `PortfolioContext.exchange_rate` (`GET /v1/asset/exchange_rates`) are AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account the gateway rejected them with an opaque code 12; they now get a region error (`HttpClientError::DcRegionRestricted`) without sending the request. US accounts should use `us_cash_and_buy_power` / `us_asset_overview` (balance), `us_transaction_history` (cash flow), `us_asset_overview` `stock_list` (positions) and `us_exchange_rates`. The `fund_positions` and `margin_ratio` docs now state there is no US equivalent (for US margin info see `us_cash_and_buy_power` `margin_status`).
-- **All SDKs:** the `HttpClientError::DcRegionRestricted` message (its `Display` text) gained a suffix naming the alternative by HTTP endpoint, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept unchanged as a prefix and `; ` is appended; code that matches the message by exact equality must be updated (prefix matching keeps working). The hint names endpoints rather than methods because the text is shared by every binding (Rust/Python snake_case, Node.js camelCase, Java/C/C++ without the US methods) and by MCP servers. US-region accounts calling an AP-only path: `/v1/asset/account` → `GET /v1/us/assets/cash-and-buy-power` or `GET /v1/us/assets/overview`; `/v1/asset/cashflow` → `POST /v1/us/assets/transaction-history`; `/v1/asset/stock` → `GET /v1/us/assets/overview`; `/v1/asset/exchange_rates` → `GET /v1/us/assets/exchange-rates` (on `TradeContext`); `/v1/trade/order` → `GET /v1/us/orders/{order_id}`; `/v1/risk/margin-ratio` → no US equivalent (account-level margin: `GET /v1/us/assets/cash-and-buy-power` `margin_status`); `/v1/asset/fund`, quote `brokers`, `/v1/quote/operatings`, `/v1/quote/broker-holding*` and DCA `/v1/dailycoins/*` → no US equivalent. AP-region accounts calling a US-only path: `/v1/us/orders/{id}` → `GET /v1/trade/order`; `/v1/us/orders/query` → `GET /v1/trade/order/today` or `/v1/trade/order/history`; `/v1/us/assets/overview` → `GET /v1/asset/account` + `GET /v1/asset/stock`; `/v1/us/assets/transaction-history` → `GET /v1/asset/cashflow`; `/v1/us/assets/exchange-rates` → `GET /v1/asset/exchange_rates`; `/v1/us/assets/cash-and-buy-power` → `GET /v1/asset/account`; `/v1/us/assets/available-holding` → `GET /v1/asset/stock` (`available_quantity`); other US paths (realized P&L, fundamentals, crypto overview) → no AP equivalent. The variant's fields are unchanged, and bindings still surface it as error kind `Other`.
+- **All SDKs:** the `HttpClientError::DcRegionRestricted` message (its `Display` text) gained a suffix naming the alternative by HTTP endpoint, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept unchanged as a prefix and `; ` is appended; code that matches the message by exact equality must be updated (prefix matching keeps working). The hint names endpoints rather than methods because the text is shared by every binding (Rust/Python snake_case, Node.js camelCase, Java/C/C++ without the US methods) and by MCP servers. US-region accounts calling an AP-only path: `/v1/asset/account` → `GET /v1/us/assets/cash-and-buy-power` or `GET /v1/us/assets/overview`; `/v1/asset/cashflow` → `POST /v1/us/assets/transaction-history`; `/v1/asset/stock` → `GET /v1/us/assets/overview`; `/v1/asset/exchange_rates` → `GET /v1/us/assets/exchange-rates` (TradeContext US methods); `/v1/trade/order` → `GET /v1/us/orders/{order_id}`; `/v3/trade/execution/all` → `GET /v1/trade/execution/history`; `/v1/risk/margin-ratio` → no US equivalent (account-level margin: `GET /v1/us/assets/cash-and-buy-power` `margin_status`); `/v1/asset/fund`, quote `brokers`, `/v1/quote/operatings`, `/v1/quote/broker-holding*` and DCA `/v1/dailycoins/*` → no US equivalent. AP-region accounts calling a US-only path: `/v1/us/orders/{id}` → `GET /v1/trade/order`; `/v1/us/orders/query` → `GET /v1/trade/order/today` or `/v1/trade/order/history`; `/v1/us/assets/overview` → `GET /v1/asset/account` + `GET /v1/asset/stock`; `/v1/us/assets/transaction-history` → `GET /v1/asset/cashflow`; `/v1/us/assets/exchange-rates` → `GET /v1/asset/exchange_rates`; `/v1/us/assets/cash-and-buy-power` → `GET /v1/asset/account`; `/v1/us/assets/available-holding` → `GET /v1/asset/stock` (`available_quantity`); US fundamentals → the AP fundamentals endpoints: `/v1/us/stock-info/company-overview` → `GET /v1/quote/comp-overview`, `/v1/us/stock-info/valuation-overview` → `GET /v1/quote/valuation`, `/v1/us/stock-info/finn-overview`, `/v1/us/quote/financials/statements` and `/v1/us/stock-info/fin-keyfactor` → `GET /v1/quote/financial-reports`, `/v1/us/stock-info/fin-consensus` → `GET /v1/quote/financial-consensus-detail`, `/v1/us/stock-info/etf-dividend-info` and `/v1/us/stock-info/company-dividends` → `GET /v1/quote/dividends`; other US paths (realized P&L `/v1/us/assets/pl/realized`, crypto overview, ETF files) → no AP equivalent. The variant's fields are unchanged, and bindings still surface it as error kind `Other`.
 - **Rust blocking, Python:** the AP-only methods on `TradeContextSync` / `PortfolioContextSync` now document the region restriction and the US alternative, and the `AsyncPortfolioContext.exchange_rate` docstring in `openapi.pyi` points to `AsyncTradeContext.us_exchange_rates` (not the sync `TradeContext`).
 
 ### Fixed
diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h
index 8a6706aa9..d9981feeb 100644
--- a/c/csrc/include/longbridge.h
+++ b/c/csrc/include/longbridge.h
@@ -16412,6 +16412,9 @@ void lb_trade_context_today_executions(const struct lb_trade_context_t *ctx,
 /**
  * Get all executions
  *
+ * AP accounts only: US data-center accounts get a region error; use
+ * `lb_trade_context_history_executions` instead.
+ *
  * @param[in] opts Options for get all executions request (can be null)
  */
 void lb_trade_context_all_executions(const struct lb_trade_context_t *ctx,
diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs
index 93365de85..22cd670d3 100644
--- a/c/src/trade_context/context.rs
+++ b/c/src/trade_context/context.rs
@@ -311,6 +311,9 @@ pub unsafe extern "C" fn lb_trade_context_today_executions(
 
 /// Get all executions
 ///
+/// AP accounts only: US data-center accounts get a region error; use
+/// `lb_trade_context_history_executions` instead.
+///
 /// @param[in] opts Options for get all executions request (can be null)
 #[unsafe(no_mangle)]
 pub unsafe extern "C" fn lb_trade_context_all_executions(
diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp
index 348adcc95..8202ea171 100644
--- a/cpp/include/trade_context.hpp
+++ b/cpp/include/trade_context.hpp
@@ -54,6 +54,9 @@ class TradeContext
     AsyncCallback> callback) const;
 
   /// Get all executions
+  ///
+  /// AP accounts only: US data-center accounts get a region error; use
+  /// `history_executions` instead.
   void all_executions(
     const std::optional& opts,
     AsyncCallback callback) const;
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 f08ebd969..0dca87672 100644
--- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java
+++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java
@@ -181,6 +181,9 @@ public synchronized CompletableFuture getTodayExecutions(GetTodayEx
 
     /**
      * Get all executions
+     * 

+ * AP accounts only: US data-center accounts get a region error; use + * {@link #getHistoryExecutions} instead. * * @param opts Options for this request * @return A Future representing the result of the operation diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index d04821ca2..5c9ed279f 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -4354,7 +4354,12 @@ export declare class TradeContext { * ``` */ todayExecutions(opts?: GetTodayExecutionsOptions | undefined | null): Promise> - /** Get all executions */ + /** + * Get all executions + * + * AP accounts only: US data-center accounts get a region error; use + * `historyExecutions` instead. + */ allExecutions(opts?: GetAllExecutionsOptions | undefined | null): Promise /** * Get history orders diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs index 4090f6950..3a1de6f3b 100644 --- a/nodejs/src/trade/context.rs +++ b/nodejs/src/trade/context.rs @@ -231,6 +231,9 @@ impl TradeContext { } /// Get all executions + /// + /// AP accounts only: US data-center accounts get a region error; use + /// `historyExecutions` instead. #[napi] pub async fn all_executions( &self, diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 90bd7844e..b453df23f 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -7593,6 +7593,8 @@ class TradeContext: """ Get all executions + AP accounts only: US data-center accounts get a region error; use ``history_executions`` instead. + Args: symbol: Filter by security code order_id: Filter by order ID @@ -11560,6 +11562,8 @@ class AsyncTradeContext: """ Get all executions. Returns an awaitable that resolves to AllExecutionsResponse. + AP accounts only: US data-center accounts get a region error; use ``history_executions`` instead. + Args: symbol: Filter by security code. order_id: Filter by order ID. diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs index 1716dec7d..bb117cd84 100644 --- a/python/src/trade/context.rs +++ b/python/src/trade/context.rs @@ -147,6 +147,9 @@ impl TradeContext { } /// Get all executions + /// + /// AP accounts only: US data-center accounts get a region error; use + /// `history_executions` instead. #[pyo3(signature = (symbol = None, order_id = None, start_at = None, end_at = None, page = None))] fn all_executions( &self, diff --git a/python/src/trade/context_async.rs b/python/src/trade/context_async.rs index 94bc19341..f9fd65245 100644 --- a/python/src/trade/context_async.rs +++ b/python/src/trade/context_async.rs @@ -174,6 +174,9 @@ impl AsyncTradeContext { } /// Get all executions. Returns awaitable. + /// + /// AP accounts only: US data-center accounts get a region error; use + /// `history_executions` instead. #[pyo3(signature = (symbol = None, order_id = None, start_at = None, end_at = None, page = None))] fn all_executions( &self, diff --git a/rust/crates/httpclient/src/error.rs b/rust/crates/httpclient/src/error.rs index d31757d23..6d5328864 100644 --- a/rust/crates/httpclient/src/error.rs +++ b/rust/crates/httpclient/src/error.rs @@ -166,11 +166,12 @@ fn ap_only_alternative(path: &str) -> Option<&'static str> { "use the US endpoint GET /v1/us/assets/overview (TradeContext US methods)" } "/v1/asset/exchange_rates" => { - "the US equivalent is on TradeContext (GET /v1/us/assets/exchange-rates)" + "use the US endpoint GET /v1/us/assets/exchange-rates (TradeContext US methods)" } "/v1/trade/order" => { "use the US endpoint GET /v1/us/orders/{order_id} (TradeContext US methods)" } + "/v3/trade/execution/all" => "use GET /v1/trade/execution/history", "/v1/risk/margin-ratio" => { "there is no US equivalent; account-level margin is at GET \ /v1/us/assets/cash-and-buy-power (margin_status)" @@ -201,6 +202,19 @@ fn us_only_alternative(path: &str) -> Option<&'static str> { "/v1/us/assets/available-holding" => { "use the AP endpoint GET /v1/asset/stock (available_quantity)" } + "/v1/us/stock-info/company-overview" => "use the AP endpoint GET /v1/quote/comp-overview", + "/v1/us/stock-info/valuation-overview" => "use the AP endpoint GET /v1/quote/valuation", + "/v1/us/stock-info/finn-overview" + | "/v1/us/quote/financials/statements" + | "/v1/us/stock-info/fin-keyfactor" => { + "use the AP endpoint GET /v1/quote/financial-reports" + } + "/v1/us/stock-info/fin-consensus" => { + "use the AP endpoint GET /v1/quote/financial-consensus-detail" + } + "/v1/us/stock-info/etf-dividend-info" | "/v1/us/stock-info/company-dividends" => { + "use the AP endpoint GET /v1/quote/dividends" + } _ if path.starts_with("/v1/us/orders/") => "use the AP endpoint GET /v1/trade/order", _ if path.starts_with("/v1/us/") => NO_AP_EQUIVALENT, _ => return None, @@ -273,7 +287,7 @@ mod tests { ), ( "/v1/asset/exchange_rates", - "the US equivalent is on TradeContext (GET /v1/us/assets/exchange-rates)", + "use the US endpoint GET /v1/us/assets/exchange-rates (TradeContext US methods)", ), ( "/v1/trade/order", @@ -283,6 +297,10 @@ mod tests { "/v1/risk/margin-ratio", "there is no US equivalent; account-level margin is at GET /v1/us/assets/cash-and-buy-power (margin_status)", ), + ( + "/v3/trade/execution/all", + "use GET /v1/trade/execution/history", + ), ("/v1/asset/fund", "there is no US equivalent"), ("quote/brokers", "there is no US equivalent"), ("/v1/quote/operatings", "there is no US equivalent"), @@ -340,13 +358,38 @@ mod tests { ), ("/v1/us/assets/pl/realized", "there is no AP equivalent"), ("/v1/us/gemini/crypto-overview", "there is no AP equivalent"), + ("/v1/us/stock-info/etf-files", "there is no AP equivalent"), ( "/v1/us/stock-info/company-overview", - "there is no AP equivalent", + "use the AP endpoint GET /v1/quote/comp-overview", + ), + ( + "/v1/us/stock-info/valuation-overview", + "use the AP endpoint GET /v1/quote/valuation", + ), + ( + "/v1/us/stock-info/finn-overview", + "use the AP endpoint GET /v1/quote/financial-reports", ), ( "/v1/us/quote/financials/statements", - "there is no AP equivalent", + "use the AP endpoint GET /v1/quote/financial-reports", + ), + ( + "/v1/us/stock-info/fin-keyfactor", + "use the AP endpoint GET /v1/quote/financial-reports", + ), + ( + "/v1/us/stock-info/fin-consensus", + "use the AP endpoint GET /v1/quote/financial-consensus-detail", + ), + ( + "/v1/us/stock-info/etf-dividend-info", + "use the AP endpoint GET /v1/quote/dividends", + ), + ( + "/v1/us/stock-info/company-dividends", + "use the AP endpoint GET /v1/quote/dividends", ), ]; for (path, hint) in cases { diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs index 9fdad6060..28010f569 100644 --- a/rust/src/blocking/trade.rs +++ b/rust/src/blocking/trade.rs @@ -117,6 +117,9 @@ impl TradeContextSync { } /// Get all executions + /// + /// AP accounts only: US data-center accounts get a region error; use + /// `history_executions` instead. pub fn all_executions( &self, options: impl Into> + Send + 'static, diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs index df47b110b..fd3162c48 100644 --- a/rust/src/trade/context.rs +++ b/rust/src/trade/context.rs @@ -314,6 +314,9 @@ impl TradeContext { /// Get all executions /// + /// AP accounts only: US data-center accounts get a region error; use + /// `history_executions` instead. + /// /// Reference: pub async fn all_executions( &self, @@ -323,6 +326,7 @@ impl TradeContext { .0 .http_cli .request(Method::GET, "/v3/trade/execution/all") + .dc_restrict(DcRegion::Ap) .query_params(options.into().unwrap_or_default()) .response::>() .send() @@ -1308,5 +1312,6 @@ mod dc_restrict_tests { assert_ap_restricted(ctx.stock_positions(None).await, "/v1/asset/stock"); assert_ap_restricted(ctx.margin_ratio("700.HK").await, "/v1/risk/margin-ratio"); assert_ap_restricted(ctx.order_detail("1").await, "/v1/trade/order"); + assert_ap_restricted(ctx.all_executions(None).await, "/v3/trade/execution/all"); } } From a69be57579e4101ab4da6edcb3bd2a5f2b3ffe64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Fri, 23 Oct 2026 11:51:56 +0800 Subject: [PATCH 13/16] fix(http): point the US key-metrics hint at the AP earnings snapshot --- CHANGELOG.md | 2 +- rust/crates/httpclient/src/error.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19489608d..20136eebc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **All SDKs:** `all_executions` (`GET /v3/trade/execution/all`) is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account the gateway answered HTTP 500 with code 12; it now gets a region error without sending the request. US accounts should use `history_executions` (`GET /v1/trade/execution/history`), which works in both regions. The method docs in every layer say so. - **All SDKs:** `order_detail` is AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account, `GET /v1/trade/order` returned HTTP 500 because the order gateway panics on US orders. It now gets a region error without sending the request. US accounts should use `us_order_detail` (`GET /v1/us/orders/{order_id}`). - **All SDKs:** `TradeContext.account_balance` (`GET /v1/asset/account`), `TradeContext.cash_flow` (`GET /v1/asset/cashflow`), `TradeContext.stock_positions` (`GET /v1/asset/stock`) and `PortfolioContext.exchange_rate` (`GET /v1/asset/exchange_rates`) are AP-only (`dc_restrict(DcRegion::Ap)`). For a US data-center account the gateway rejected them with an opaque code 12; they now get a region error (`HttpClientError::DcRegionRestricted`) without sending the request. US accounts should use `us_cash_and_buy_power` / `us_asset_overview` (balance), `us_transaction_history` (cash flow), `us_asset_overview` `stock_list` (positions) and `us_exchange_rates`. The `fund_positions` and `margin_ratio` docs now state there is no US equivalent (for US margin info see `us_cash_and_buy_power` `margin_status`). -- **All SDKs:** the `HttpClientError::DcRegionRestricted` message (its `Display` text) gained a suffix naming the alternative by HTTP endpoint, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept unchanged as a prefix and `; ` is appended; code that matches the message by exact equality must be updated (prefix matching keeps working). The hint names endpoints rather than methods because the text is shared by every binding (Rust/Python snake_case, Node.js camelCase, Java/C/C++ without the US methods) and by MCP servers. US-region accounts calling an AP-only path: `/v1/asset/account` → `GET /v1/us/assets/cash-and-buy-power` or `GET /v1/us/assets/overview`; `/v1/asset/cashflow` → `POST /v1/us/assets/transaction-history`; `/v1/asset/stock` → `GET /v1/us/assets/overview`; `/v1/asset/exchange_rates` → `GET /v1/us/assets/exchange-rates` (TradeContext US methods); `/v1/trade/order` → `GET /v1/us/orders/{order_id}`; `/v3/trade/execution/all` → `GET /v1/trade/execution/history`; `/v1/risk/margin-ratio` → no US equivalent (account-level margin: `GET /v1/us/assets/cash-and-buy-power` `margin_status`); `/v1/asset/fund`, quote `brokers`, `/v1/quote/operatings`, `/v1/quote/broker-holding*` and DCA `/v1/dailycoins/*` → no US equivalent. AP-region accounts calling a US-only path: `/v1/us/orders/{id}` → `GET /v1/trade/order`; `/v1/us/orders/query` → `GET /v1/trade/order/today` or `/v1/trade/order/history`; `/v1/us/assets/overview` → `GET /v1/asset/account` + `GET /v1/asset/stock`; `/v1/us/assets/transaction-history` → `GET /v1/asset/cashflow`; `/v1/us/assets/exchange-rates` → `GET /v1/asset/exchange_rates`; `/v1/us/assets/cash-and-buy-power` → `GET /v1/asset/account`; `/v1/us/assets/available-holding` → `GET /v1/asset/stock` (`available_quantity`); US fundamentals → the AP fundamentals endpoints: `/v1/us/stock-info/company-overview` → `GET /v1/quote/comp-overview`, `/v1/us/stock-info/valuation-overview` → `GET /v1/quote/valuation`, `/v1/us/stock-info/finn-overview`, `/v1/us/quote/financials/statements` and `/v1/us/stock-info/fin-keyfactor` → `GET /v1/quote/financial-reports`, `/v1/us/stock-info/fin-consensus` → `GET /v1/quote/financial-consensus-detail`, `/v1/us/stock-info/etf-dividend-info` and `/v1/us/stock-info/company-dividends` → `GET /v1/quote/dividends`; other US paths (realized P&L `/v1/us/assets/pl/realized`, crypto overview, ETF files) → no AP equivalent. The variant's fields are unchanged, and bindings still surface it as error kind `Other`. +- **All SDKs:** the `HttpClientError::DcRegionRestricted` message (its `Display` text) gained a suffix naming the alternative by HTTP endpoint, so a caller (or an LLM agent) can recover without a lookup. The existing text is kept unchanged as a prefix and `; ` is appended; code that matches the message by exact equality must be updated (prefix matching keeps working). The hint names endpoints rather than methods because the text is shared by every binding (Rust/Python snake_case, Node.js camelCase, Java/C/C++ without the US methods) and by MCP servers. US-region accounts calling an AP-only path: `/v1/asset/account` → `GET /v1/us/assets/cash-and-buy-power` or `GET /v1/us/assets/overview`; `/v1/asset/cashflow` → `POST /v1/us/assets/transaction-history`; `/v1/asset/stock` → `GET /v1/us/assets/overview`; `/v1/asset/exchange_rates` → `GET /v1/us/assets/exchange-rates` (TradeContext US methods); `/v1/trade/order` → `GET /v1/us/orders/{order_id}`; `/v3/trade/execution/all` → `GET /v1/trade/execution/history`; `/v1/risk/margin-ratio` → no US equivalent (account-level margin: `GET /v1/us/assets/cash-and-buy-power` `margin_status`); `/v1/asset/fund`, quote `brokers`, `/v1/quote/operatings`, `/v1/quote/broker-holding*` and DCA `/v1/dailycoins/*` → no US equivalent. AP-region accounts calling a US-only path: `/v1/us/orders/{id}` → `GET /v1/trade/order`; `/v1/us/orders/query` → `GET /v1/trade/order/today` or `/v1/trade/order/history`; `/v1/us/assets/overview` → `GET /v1/asset/account` + `GET /v1/asset/stock`; `/v1/us/assets/transaction-history` → `GET /v1/asset/cashflow`; `/v1/us/assets/exchange-rates` → `GET /v1/asset/exchange_rates`; `/v1/us/assets/cash-and-buy-power` → `GET /v1/asset/account`; `/v1/us/assets/available-holding` → `GET /v1/asset/stock` (`available_quantity`); US fundamentals → the AP fundamentals endpoints: `/v1/us/stock-info/company-overview` → `GET /v1/quote/comp-overview`, `/v1/us/stock-info/valuation-overview` → `GET /v1/quote/valuation`, `/v1/us/stock-info/finn-overview` and `/v1/us/quote/financials/statements` → `GET /v1/quote/financial-reports`, `/v1/us/stock-info/fin-keyfactor` → `GET /v1/quote/financials/earnings-snapshot`, `/v1/us/stock-info/fin-consensus` → `GET /v1/quote/financial-consensus-detail`, `/v1/us/stock-info/etf-dividend-info` and `/v1/us/stock-info/company-dividends` → `GET /v1/quote/dividends`; other US paths (realized P&L `/v1/us/assets/pl/realized`, crypto overview, ETF files) → no AP equivalent. The variant's fields are unchanged, and bindings still surface it as error kind `Other`. - **Rust blocking, Python:** the AP-only methods on `TradeContextSync` / `PortfolioContextSync` now document the region restriction and the US alternative, and the `AsyncPortfolioContext.exchange_rate` docstring in `openapi.pyi` points to `AsyncTradeContext.us_exchange_rates` (not the sync `TradeContext`). ### Fixed diff --git a/rust/crates/httpclient/src/error.rs b/rust/crates/httpclient/src/error.rs index 6d5328864..e19a27ec1 100644 --- a/rust/crates/httpclient/src/error.rs +++ b/rust/crates/httpclient/src/error.rs @@ -204,11 +204,12 @@ fn us_only_alternative(path: &str) -> Option<&'static str> { } "/v1/us/stock-info/company-overview" => "use the AP endpoint GET /v1/quote/comp-overview", "/v1/us/stock-info/valuation-overview" => "use the AP endpoint GET /v1/quote/valuation", - "/v1/us/stock-info/finn-overview" - | "/v1/us/quote/financials/statements" - | "/v1/us/stock-info/fin-keyfactor" => { + "/v1/us/stock-info/finn-overview" | "/v1/us/quote/financials/statements" => { "use the AP endpoint GET /v1/quote/financial-reports" } + "/v1/us/stock-info/fin-keyfactor" => { + "use the AP endpoint GET /v1/quote/financials/earnings-snapshot" + } "/v1/us/stock-info/fin-consensus" => { "use the AP endpoint GET /v1/quote/financial-consensus-detail" } @@ -377,7 +378,7 @@ mod tests { ), ( "/v1/us/stock-info/fin-keyfactor", - "use the AP endpoint GET /v1/quote/financial-reports", + "use the AP endpoint GET /v1/quote/financials/earnings-snapshot", ), ( "/v1/us/stock-info/fin-consensus", From 16356f2200ceaa5f3197ccacb482342750106c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Fri, 23 Oct 2026 13:43:54 +0800 Subject: [PATCH 14/16] fix(trade): default Execution.side when the response omits it --- CHANGELOG.md | 1 + rust/src/trade/types.rs | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20136eebc..4b7aae44a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Rust blocking, Python:** the AP-only methods on `TradeContextSync` / `PortfolioContextSync` now document the region restriction and the US alternative, and the `AsyncPortfolioContext.exchange_rate` docstring in `openapi.pyi` points to `AsyncTradeContext.us_exchange_rates` (not the sync `TradeContext`). ### Fixed +- **All SDKs:** `Execution.side` now defaults to `Unknown` when the response omits it. `/v1/trade/execution/history` returns trades without `side` for Longbridge US accounts, which made `history_executions` fail to deserialize. - **Rust core, Node.js, Python:** `USAssetOverview.crypto_list` entries (`USCryptoEntry`) now carry the holding `quantity`, `market`, `name`, prices (`last_done`, `market_price`, `prev_close`, ...), `today_pl`, `position_side` and the other fields `GET /v1/us/assets/overview` returns for crypto, which were previously dropped, so a crypto holding could not be sized. `symbol` is the bare pair code (e.g. `BTCUSD`); the user-facing symbol is `.` (e.g. `BTCUSD.BKKT`). - **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index 0ff901125..3b0a5a2c6 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -135,7 +135,9 @@ pub struct Execution { pub quantity: Decimal, /// Executed price pub price: Decimal, - /// Order side + /// Order side. `Unknown` when the response omits it, as + /// `/v1/trade/execution/history` does for Longbridge US accounts. + #[serde(default)] pub side: OrderSide, } @@ -2071,6 +2073,27 @@ mod tests { use super::*; + #[test] + fn execution_without_side_defaults_to_unknown() { + // `/v1/trade/execution/history` on a Longbridge US account returns + // trades with no `side`; they must still deserialize. + let data = r#"{ + "order_id": "1000000000000000001", + "trade_id": "1000000000000000001-1", + "symbol": "ETHUSD.BKKT", + "trade_done_at": "1790203596", + "quantity": "0.168", + "price": "2690.34165646" + }"#; + let execution: Execution = serde_json::from_str(data).expect("execution without side"); + assert_eq!( + execution.side, + OrderSide::Unknown, + "missing side defaults to Unknown" + ); + assert_eq!(execution.symbol, "ETHUSD.BKKT", "symbol is kept"); + } + #[test] fn us_exchange_rates_response() { let data = r#"{ From f80298022d92e971e67be30b7ac7ec0ac0afc767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Fri, 23 Oct 2026 13:44:10 +0800 Subject: [PATCH 15/16] docs(changelog): keep the Fixed list layout --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7aae44a..e102ccb89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Rust blocking, Python:** the AP-only methods on `TradeContextSync` / `PortfolioContextSync` now document the region restriction and the US alternative, and the `AsyncPortfolioContext.exchange_rate` docstring in `openapi.pyi` points to `AsyncTradeContext.us_exchange_rates` (not the sync `TradeContext`). ### Fixed -- **All SDKs:** `Execution.side` now defaults to `Unknown` when the response omits it. `/v1/trade/execution/history` returns trades without `side` for Longbridge US accounts, which made `history_executions` fail to deserialize. +- **All SDKs:** `Execution.side` now defaults to `Unknown` when the response omits it. `/v1/trade/execution/history` returns trades without `side` for Longbridge US accounts, which made `history_executions` fail to deserialize. - **Rust core, Node.js, Python:** `USAssetOverview.crypto_list` entries (`USCryptoEntry`) now carry the holding `quantity`, `market`, `name`, prices (`last_done`, `market_price`, `prev_close`, ...), `today_pl`, `position_side` and the other fields `GET /v1/us/assets/overview` returns for crypto, which were previously dropped, so a crypto holding could not be sized. `symbol` is the bare pair code (e.g. `BTCUSD`); the user-facing symbol is `.` (e.g. `BTCUSD.BKKT`). - **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged From 1f1b90da8c82645d061f46e918316d82103a65ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Fri, 23 Oct 2026 14:01:40 +0800 Subject: [PATCH 16/16] docs(trade): note Unknown execution side on US accounts --- CHANGELOG.md | 2 +- c/csrc/include/longbridge.h | 5 ++++- c/src/trade_context/context.rs | 2 ++ c/src/trade_context/types.rs | 3 ++- cpp/include/trade_context.hpp | 2 ++ cpp/include/types.hpp | 3 ++- .../src/main/java/com/longbridge/trade/Execution.java | 3 +++ .../src/main/java/com/longbridge/trade/TradeContext.java | 2 ++ nodejs/index.d.ts | 7 ++++++- nodejs/src/trade/context.rs | 2 ++ nodejs/src/trade/types.rs | 3 ++- python/pysrc/longbridge/openapi.pyi | 6 +++++- python/src/trade/types.rs | 3 ++- rust/src/blocking/trade.rs | 2 ++ rust/src/trade/context.rs | 2 ++ 15 files changed, 39 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e102ccb89..8a8eabc9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **All SDKs:** `Execution.side` now defaults to `Unknown` when the response omits it. `/v1/trade/execution/history` returns trades without `side` for Longbridge US accounts, which made `history_executions` fail to deserialize. - **Rust core, Node.js, Python:** `USAssetOverview.crypto_list` entries (`USCryptoEntry`) now carry the holding `quantity`, `market`, `name`, prices (`last_done`, `market_price`, `prev_close`, ...), `today_pl`, `position_side` and the other fields `GET /v1/us/assets/overview` returns for crypto, which were previously dropped, so a crypto holding could not be sized. `symbol` is the bare pair code (e.g. `BTCUSD`); the user-facing symbol is `.` (e.g. `BTCUSD.BKKT`). -- **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged +- **All SDKs:** `QuoteContext.option_chain_info_by_date` now calls `POST /v1/quote/options/strikes` with a JSON body. The `GET /v1/gemini/option/option_chain_list` endpoint it used since 5.1.0 was renamed on the gateway and returned `404000 api not found` in every region, so the method failed on every call. The signature and the returned `OptionChainContract` are unchanged. ## [5.2.2] - 2026-10-16 diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index d9981feeb..d81cba568 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -5740,7 +5740,8 @@ typedef struct lb_execution_t { */ const struct lb_decimal_t *price; /** - * Order side + * Order side. `Unknown` when the response omits it (Longbridge US + * `/v1/trade/execution/history`) */ enum lb_order_side_t side; } lb_execution_t; @@ -16392,6 +16393,8 @@ void lb_trade_context_unsubscribe(const struct lb_trade_context_t *ctx, /** * Get history executions * + * `side` may be `Unknown` on Longbridge US accounts. + * * @param[in] opts Options for get history executions request (can be null) */ void lb_trade_context_history_executions(const struct lb_trade_context_t *ctx, diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs index 22cd670d3..8ce00cb5d 100644 --- a/c/src/trade_context/context.rs +++ b/c/src/trade_context/context.rs @@ -252,6 +252,8 @@ pub unsafe extern "C" fn lb_trade_context_unsubscribe( /// Get history executions /// +/// `side` may be `Unknown` on Longbridge US accounts. +/// /// @param[in] opts Options for get history executions request (can be null) #[unsafe(no_mangle)] pub unsafe extern "C" fn lb_trade_context_history_executions( diff --git a/c/src/trade_context/types.rs b/c/src/trade_context/types.rs index 081b2ac77..e414e3f6e 100644 --- a/c/src/trade_context/types.rs +++ b/c/src/trade_context/types.rs @@ -764,7 +764,8 @@ pub struct CExecution { pub quantity: *const CDecimal, /// Executed price pub price: *const CDecimal, - /// Order side + /// Order side. `Unknown` when the response omits it (Longbridge US + /// `/v1/trade/execution/history`) pub side: COrderSide, } diff --git a/cpp/include/trade_context.hpp b/cpp/include/trade_context.hpp index 8202ea171..f395bce1a 100644 --- a/cpp/include/trade_context.hpp +++ b/cpp/include/trade_context.hpp @@ -44,6 +44,8 @@ class TradeContext PushCallback callback) const; /// Get history executions + /// + /// `side` may be `Unknown` on Longbridge US accounts. void history_executions( const std::optional& opts, AsyncCallback> callback) const; diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index 069660d3c..523a4f69d 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1440,7 +1440,8 @@ struct Execution int64_t trade_done_at; Decimal quantity; Decimal price; - /// Order side + /// Order side. `Unknown` when the response omits it (Longbridge US + /// `/v1/trade/execution/history`) OrderSide side; }; diff --git a/java/javasrc/src/main/java/com/longbridge/trade/Execution.java b/java/javasrc/src/main/java/com/longbridge/trade/Execution.java index 6bae9831e..c3ed68a76 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/Execution.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/Execution.java @@ -71,6 +71,9 @@ public BigDecimal getPrice() { /** * Returns the order side. + *

+ * {@code Unknown} when the response omits it (Longbridge US + * {@code /v1/trade/execution/history}). * * @return order side */ 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 0dca87672..2cbf9b13d 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -108,6 +108,8 @@ public synchronized CompletableFuture unsubscribe(TopicType[] topics) thro /** * Get history executions + *

+ * {@code side} may be {@code Unknown} on Longbridge US accounts. * *

      * {@code
diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts
index 5c9ed279f..ad1a992a3 100644
--- a/nodejs/index.d.ts
+++ b/nodejs/index.d.ts
@@ -716,7 +716,10 @@ export declare class Execution {
   get quantity(): Decimal
   /** Executed price */
   get price(): Decimal
-  /** Order side */
+  /**
+   * Order side. `Unknown` when the response omits it (Longbridge US
+   * `/v1/trade/execution/history`)
+   */
   get side(): OrderSide
 }
 
@@ -4319,6 +4322,8 @@ export declare class TradeContext {
   /**
    * Get history executions
    *
+   * `side` may be `Unknown` on Longbridge US accounts.
+   *
    * #### Example
    *
    * ```javascript
diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs
index 3a1de6f3b..2684d1db3 100644
--- a/nodejs/src/trade/context.rs
+++ b/nodejs/src/trade/context.rs
@@ -172,6 +172,8 @@ impl TradeContext {
 
     /// Get history executions
     ///
+    /// `side` may be `Unknown` on Longbridge US accounts.
+    ///
     /// #### Example
     ///
     /// ```javascript
diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs
index 02b6247e8..c75382642 100644
--- a/nodejs/src/trade/types.rs
+++ b/nodejs/src/trade/types.rs
@@ -30,7 +30,8 @@ pub struct Execution {
     quantity: Decimal,
     /// Executed price
     price: Decimal,
-    /// Order side
+    /// Order side. `Unknown` when the response omits it (Longbridge US
+    /// `/v1/trade/execution/history`)
     side: OrderSide,
 }
 
diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi
index b453df23f..0c898bbba 100644
--- a/python/pysrc/longbridge/openapi.pyi
+++ b/python/pysrc/longbridge/openapi.pyi
@@ -5906,7 +5906,7 @@ class Execution:
 
     side: OrderSide
     """
-    Order side
+    Order side. `Unknown` when the response omits it (Longbridge US `/v1/trade/execution/history`)
     """
 
 class AllExecutionsResponse:
@@ -7526,6 +7526,8 @@ class TradeContext:
         """
         Get history executions
 
+        ``side`` may be ``OrderSide.Unknown`` on Longbridge US accounts.
+
         Args:
             symbol: Filter by security code, example: `700.HK`, `AAPL.US`
             start_at: Start time
@@ -11493,6 +11495,8 @@ class AsyncTradeContext:
         """
         Get history executions. Optional filters: symbol, start_at, end_at. Returns an awaitable that resolves to execution list.
 
+        ``side`` may be ``OrderSide.Unknown`` on Longbridge US accounts.
+
         Args:
             symbol: Filter by security code.
             start_at: Start time.
diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs
index 1115cd19e..b30ab715a 100644
--- a/python/src/trade/types.rs
+++ b/python/src/trade/types.rs
@@ -34,7 +34,8 @@ pub(crate) struct Execution {
     quantity: PyDecimal,
     /// Executed price
     price: PyDecimal,
-    /// Order side
+    /// Order side. `Unknown` when the response omits it (Longbridge US
+    /// `/v1/trade/execution/history`)
     side: OrderSide,
 }
 
diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs
index 28010f569..4bb8208f1 100644
--- a/rust/src/blocking/trade.rs
+++ b/rust/src/blocking/trade.rs
@@ -54,6 +54,8 @@ impl TradeContextSync {
 
     /// Get history executions
     ///
+    /// `side` may be `Unknown` on Longbridge US accounts.
+    ///
     /// # Examples
     ///
     /// ```no_run
diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs
index fd3162c48..2bbb67c55 100644
--- a/rust/src/trade/context.rs
+++ b/rust/src/trade/context.rs
@@ -182,6 +182,8 @@ impl TradeContext {
 
     /// Get history executions
     ///
+    /// `side` may be `Unknown` on Longbridge US accounts.
+    ///
     /// Reference: 
     ///
     /// # Examples