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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Rust SDK:** `TradeContext::history_executions` now pages through all results internally via the `page` parameter (the endpoint caps each response at 1000 records) until `has_more` is false, so callers get the complete execution set instead of a silently-truncated first page. Applies to the blocking wrapper and every binding that delegates to it.
- **Python SDK:** reconciled the hand-maintained type stub `python/pysrc/longbridge/openapi.pyi` with the actual PyO3 implementation. Removed phantom methods that did not exist and would raise `AttributeError` when called (`AlertContext.enable`/`disable`, `AsyncQuoteContext.option_volume`/`option_volume_daily`); fixed wrong signatures/return types (`FundamentalContext.macroeconomic_indicators` had dropped its `country`/`keyword` params and had the wrong return type, `macroeconomic` was missing `offset`, `DCAContext.create`/`update` return `DcaCreateResult` not `DcaList`, `DCAContext.pause`/`resume`/`stop` and `SharelistContext.create` return `None`); added missing methods (`QuoteContext.filings` + async, `AlertContext.update`, `DCAContext.update`, `TradeContext.set_on_grid_order_changed` + async); added the three entirely-missing async context classes (`AsyncMarketContext`, `AsyncCalendarContext`, `AsyncPortfolioContext`) plus the missing method surfaces of `AsyncFundamentalContext` and `AsyncContentContext`; and added the missing referenced types (`FilingItem`, `DcaCreateResult`, `MacroeconomicCountry`, `MacroeconomicIndicatorListResponse`, `PushGridOrderChanged`). Type hints only — no runtime/behaviour change to the native module
- **Java SDK:** fixed six JNI methods whose Rust `extern "system"` signature no longer matched the Java `native` declaration, so every call aborted with `java.lang.RuntimeException: JNI call failed` (or read misaligned stack arguments). The Java layer had been migrated to options objects / trimmed argument lists but the Rust JNI side was left in the old positional form. `MarketContext.getRankList` (`RankListOptions`), `QuoteContext.getShortTrades` (`ShortTradesOptions`), `ScreenerContext.getStrategy` (`ScreenerStrategyOptions`), `FundamentalContext.shareholderDetail` (`ShareholderDetailOptions`) and `FundamentalContext.valuationComparison` (`ValuationComparisonOptions`) now read their fields off the options object. Separately, `QuoteContext.getShortPositions` was missing the `count` parameter that the Rust core, Node.js and Python bindings all require — the Rust JNI still expected it, so the call crashed — so `getShortPositions(String symbol)` becomes `getShortPositions(String symbol, int count)`. Reported as longbridge/developers#1249 (`getRankList`)
- **C/C++ SDKs:** every list argument that crosses the FFI boundary now tolerates a null pointer with a zero length. `std::vector::data()` is allowed to return `nullptr` for an empty vector, which is exactly what the C++ binding passes for an omitted list argument, but the C layer fed it straight to `std::slice::from_raw_parts` — undefined behaviour that **aborts the process** under the debug UB checks. Hit live by `QuoteContext::warrant_list` with no filters (`c/src/quote_context/context.rs:784`); all 17 call sites across `quote_context`, `trade_context`, `agent_context`, `alert_context`, and `types` now go through a null-tolerant `slice_from_raw_parts` helper
Expand Down
50 changes: 39 additions & 11 deletions rust/src/trade/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,22 +214,50 @@ impl TradeContext {
&self,
options: impl Into<Option<GetHistoryExecutionsOptions>>,
) -> Result<Vec<Execution>> {
use std::collections::HashSet;

#[derive(Deserialize)]
struct Response {
#[serde(default)]
has_more: bool,
trades: Vec<Execution>,
}

Ok(self
.0
.http_cli
.request(Method::GET, "/v1/trade/execution/history")
.query_params(options.into().unwrap_or_default())
.response::<Json<Response>>()
.send()
.with_subscriber(self.0.log_subscriber.clone())
.await?
.0
.trades)
// The endpoint caps each response at 1000 records; walk the `page`
// param (1-based) until `has_more` is false. Dedupe by
// `trade_id` and stop if a page adds nothing new, guarding
// against the gateway ignoring `page`. Bounded to 1000 pages as
// a runaway guard.
let mut options = options.into().unwrap_or_default();
let mut all: Vec<Execution> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for page in 1..=1000u32 {
options = options.with_page(page);
let resp = self
.0
.http_cli
.request(Method::GET, "/v1/trade/execution/history")
.query_params(&options)
.response::<Json<Response>>()
.send()
.with_subscriber(self.0.log_subscriber.clone())
.await?
.0;
if resp.trades.is_empty() {
break;
}
let mut added = 0usize;
for t in resp.trades {
if seen.insert(t.trade_id.clone()) {
all.push(t);
added += 1;
}
}
if !resp.has_more || added == 0 {
break;
}
}
Ok(all)
}

/// Get today executions
Expand Down
14 changes: 14 additions & 0 deletions rust/src/trade/requests/get_history_executions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub struct GetHistoryExecutionsOptions {
with = "serde_utils::timestamp_opt"
)]
end_at: Option<OffsetDateTime>,
// Pagination cursor (1-based), set internally by `history_executions` while
// walking pages. Not a public builder — callers always get every page.
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl GetHistoryExecutionsOptions {
Expand Down Expand Up @@ -56,4 +60,14 @@ impl GetHistoryExecutionsOptions {
..self
}
}

/// Internal 1-based pagination cursor used by `history_executions` to walk
/// all pages. Not a public builder — the SDK sets this while paginating.
#[inline]
pub(crate) fn with_page(self, page: u32) -> Self {
Self {
page: Some(page),
..self
}
}
}
Loading