diff --git a/CHANGELOG.md b/CHANGELOG.md index 048f440bf..23ede6a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **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 - **C++ SDK:** `asset::AssetContext` (`statements` / `statement_download_url`) is now actually built and usable. `longbridge.hpp` has always included `asset_context.hpp`, but `cpp/src/asset_context.cpp` was never listed in `cpp/CMakeLists.txt`, so the class was declared to users and then failed to link. It had also never compiled: it included neither `longbridge.h` nor the C declarations, and `statement_download_url` read `res->data` as a `lb_statement_download_url_response_t*` — a type that does not exist anywhere in the C layer, which delivers the URL as a bare `const char*` (the same convention as `QuoteContext::quote_level`). Fixed the include and the callback, and added the file to the build - **C SDK:** export `lb_statement_item_t` from `longbridge.h`. `CStatementItem` is only reachable through the `void*` async-result pointer, so cbindgen did not emit it and no C or C++ caller could read what `lb_asset_context_statements` returns. Also added the missing `CAssetContext` → `lb_asset_context_t` entry to the cbindgen rename map: every other context type was mapped, so the header exposed the raw Rust name (`const struct CAssetContext *lb_asset_context_new(...)`) while the C++ side forward-declared `lb_asset_context_t` diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 211e29544..9e8011782 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -353,7 +353,7 @@ public static native void gridContextTriggerHistory(long context, GetGridTrigger // ── QuoteContext extensions (Step 3) ───────────────────────── - public static native void quoteContextShortPositions(long context, String symbol, AsyncCallback callback); + public static native void quoteContextShortPositions(long context, String symbol, int count, AsyncCallback callback); public static native void quoteContextOptionVolume(long context, String symbol, AsyncCallback callback); public static native void quoteContextOptionVolumeDaily(long context, Object opts, AsyncCallback callback); diff --git a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java index 9fadca066..c93809aea 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java @@ -1396,12 +1396,13 @@ public synchronized CompletableFuture getRealtimeTrades(String symbol, * Get short positions for a symbol * * @param symbol Security symbol + * @param count Number of records to return * @return A Future representing the short positions response * @throws OpenApiException If an error occurs */ - public synchronized CompletableFuture getShortPositions(String symbol) throws OpenApiException { + public synchronized CompletableFuture getShortPositions(String symbol, int count) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { - SdkNative.quoteContextShortPositions(raw(), symbol, callback); + SdkNative.quoteContextShortPositions(raw(), symbol, count, callback); }); } diff --git a/java/src/fundamental_context.rs b/java/src/fundamental_context.rs index 87bb4146e..2c50d6c02 100644 --- a/java/src/fundamental_context.rs +++ b/java/src/fundamental_context.rs @@ -344,14 +344,14 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextSh mut env: JNIEnv, _class: JClass, context: i64, - symbol: JObject, - object_id: i64, + opts: 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 symbol: String = get_field(env, &opts, "symbol")?; + let object_id: i64 = get_field(env, &opts, "objectId")?; async_util::execute(env, callback, async move { let resp = __owned_ctx.shareholder_detail(symbol, object_id).await?; Ok(resp) @@ -365,22 +365,17 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextVa mut env: JNIEnv, _class: JClass, context: i64, - symbol: JObject, - currency: JObject, - comparison_symbols: JObject, + opts: 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 currency: String = FromJValue::from_jvalue(env, currency.into())?; - let comparison_syms: Option> = if comparison_symbols.is_null() { - None - } else { - let arr: ObjectArray = FromJValue::from_jvalue(env, comparison_symbols.into())?; - Some(arr.0) - }; + let symbol: String = get_field(env, &opts, "symbol")?; + let currency: String = get_field(env, &opts, "currency")?; + let comparison_syms: Option> = + get_field::<_, _, Option>>(env, &opts, "comparisonSymbols")? + .map(|arr| arr.0); async_util::execute(env, callback, async move { let resp = __owned_ctx .valuation_comparison(symbol, currency, comparison_syms) diff --git a/java/src/market_context.rs b/java/src/market_context.rs index 25bc9c6cc..d0359bc70 100644 --- a/java/src/market_context.rs +++ b/java/src/market_context.rs @@ -238,14 +238,14 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_marketContextRankLis mut env: JNIEnv, _class: JClass, context: i64, - key: JObject, - need_article: bool, + opts: JObject, callback: JObject, ) { jni_result(&mut env, (), |env| { let context = &*(context as *const ContextObj); let __owned_ctx = context.ctx.clone(); - let key: String = FromJValue::from_jvalue(env, key.into())?; + let key: String = get_field(env, &opts, "key")?; + let need_article: bool = get_field(env, &opts, "needArticle")?; async_util::execute(env, callback, async move { let resp = __owned_ctx.rank_list(key, need_article).await?; Ok(resp) diff --git a/java/src/quote_context.rs b/java/src/quote_context.rs index dcea00cf4..67ea1831f 100644 --- a/java/src/quote_context.rs +++ b/java/src/quote_context.rs @@ -1250,14 +1250,14 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextShortTra mut env: JNIEnv, _class: JClass, context: i64, - symbol: JObject, - count: i32, + opts: 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 symbol: String = get_field(env, &opts, "symbol")?; + let count: i32 = get_field(env, &opts, "count")?; let count = count.max(1) as u32; async_util::execute(env, callback, async move { let resp = __owned_ctx.short_trades(symbol, count).await?; diff --git a/java/src/screener_context.rs b/java/src/screener_context.rs index 3d2aaa4d4..0a7466b91 100644 --- a/java/src/screener_context.rs +++ b/java/src/screener_context.rs @@ -81,12 +81,13 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_screenerContextStrat mut env: JNIEnv, _class: JClass, context: i64, - id: i64, + opts: JObject, callback: JObject, ) { jni_result(&mut env, (), |env| { let context = &*(context as *const ContextObj); let __owned_ctx = context.ctx.clone(); + let id: i64 = get_field(env, &opts, "id")?; async_util::execute(env, callback, async move { let resp = __owned_ctx.screener_strategy(id).await?; Ok(resp) diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index b0e6e23d0..09c16f65d 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -2875,6 +2875,41 @@ class TradeSessions: All """ +class FilingItem: + """ + Filing item + """ + + id: str + """ + Filing ID + """ + + title: str + """ + Title + """ + + description: str + """ + Description + """ + + file_name: str + """ + File name + """ + + file_urls: List[str] + """ + File URLs + """ + + published_at: datetime + """ + Published time + """ + class MarketTemperature: """ Market temperature @@ -4086,6 +4121,17 @@ class QuoteContext: """ ... + def filings(self, symbol: str) -> List["FilingItem"]: + """Get corporate filings for a security. + + Args: + symbol: Security symbol, e.g. ``"AAPL.US"`` + + Returns: + List of :class:`FilingItem` + """ + ... + class AsyncQuoteContext: """ Async quote context for use with asyncio. Create via `AsyncQuoteContext.create(config)` and await inside asyncio. @@ -5438,40 +5484,25 @@ class AsyncQuoteContext: """ ... - def option_volume(self, symbol: str) -> "Awaitable[OptionVolumeStats]": - """Get real-time option call/put volume. Returns awaitable. - - Args: - symbol: Underlying symbol, e.g. ``"AAPL.US"`` - - Returns: - Awaitable resolving to :class:`OptionVolumeStats` - """ - ... - - def option_volume_daily( - self, symbol: str, timestamp: int = 0, count: int = 30 - ) -> "Awaitable[OptionVolumeDaily]": - """Get daily historical option volume. Returns awaitable. + def us_crypto_overview(self, symbol: str) -> "Awaitable[USCryptoOverview]": + """Get US cryptocurrency market overview. US token required. Returns awaitable. Args: - symbol: Underlying symbol, e.g. ``"AAPL.US"`` - timestamp: Start timestamp (0 = most recent) - count: Number of days to return (default 30) + symbol: Trading-pair symbol, e.g. ``"BTCUSD.BKKT"`` Returns: - Awaitable resolving to :class:`OptionVolumeDaily` + Awaitable resolving to :class:`USCryptoOverview` """ ... - def us_crypto_overview(self, symbol: str) -> "Awaitable[USCryptoOverview]": - """Get US cryptocurrency market overview. US token required. Returns awaitable. + async def filings(self, symbol: str) -> List["FilingItem"]: + """Get corporate filings for a security. Args: - symbol: Trading-pair symbol, e.g. ``"BTCUSD.BKKT"`` + symbol: Security symbol, e.g. ``"AAPL.US"`` Returns: - Awaitable resolving to :class:`USCryptoOverview` + List of :class:`FilingItem` """ ... @@ -5770,6 +5801,56 @@ class AllExecutionsResponse: Execution list """ +class PushGridOrderChanged: + """ + Grid order changed push event + """ + + order_id: str + """Grid master order ID""" + + status: str + """Order status""" + + symbol: str + """Security symbol (e.g. ``700.HK``)""" + + suspend_reason: str + """Suspend reason, if any""" + + submitted_base_price: str + """Submitted base price""" + + current_base_price: str + """Current base price""" + + upper_limit_price: str + """Upper price bound""" + + lower_limit_price: str + """Lower price bound""" + + trigger_price_type: int + """Trigger price type""" + + trigger_quantity: str + """Quantity per trigger""" + + settlement_currency: str + """Settlement currency""" + + time_in_force: int + """Time in force (``0`` = Day, ``1`` = GTC, ``6`` = GTD)""" + + rth: int + """Regular trading hours flag""" + + grid_order_type_up: str + """Sell-side order type when depth is 0""" + + grid_order_type_down: str + """Buy-side order type when depth is 0""" + class PushOrderChanged: """ Order changed message @@ -7926,6 +8007,10 @@ class TradeContext: """ ... + def set_on_grid_order_changed(self, callback: Callable[["PushGridOrderChanged"], None]) -> None: + """Set the grid-order-changed push callback.""" + ... + class TriggerPriceType: """ How grid trigger thresholds are interpreted @@ -9648,6 +9733,10 @@ class AsyncTradeContext: """ ... + async def set_on_grid_order_changed(self, callback: Callable[["PushGridOrderChanged"], None]) -> None: + """Set the grid-order-changed push callback.""" + ... + class StatementType: """ Statement type @@ -10495,103 +10584,319 @@ class AsyncContentContext: # ── FundamentalContext ──────────────────────────────────────────── -class FinancialReports: - """ - Financial reports response. - - ``list`` contains raw nested data keyed by report kind - (``"IS"``, ``"BS"``, ``"CF"``). - """ + async def create_topic( + self, + title: str, + body: str, + topic_type: Optional[str] = None, + tickers: Optional[List[str]] = None, + hashtags: Optional[List[str]] = None, + ) -> str: + """ + Create a new community topic - list: object - """Raw financial data dict (IS/BS/CF indicators)""" + Args: + title: Topic title (required for "article"; optional for "post") + body: Topic body (plain text for "post", Markdown for "article") + topic_type: "post" (default) or "article" + tickers: Associated stock symbols, e.g. ["700.HK"], max 10 + hashtags: Hashtag names, max 5 + Returns: + The new topic ID -class DividendItem: - """One dividend or distribution event.""" + Examples: + :: - symbol: str - """Security symbol, e.g. ``"700.HK"``""" - id: str - """Internal record ID""" - desc: str - """Human-readable description, e.g. ``"每股派息 5.3 HKD"``""" - record_date: str - """Record / book-close date""" - ex_date: str - """Ex-dividend date""" - payment_date: str - """Payment date""" + from longbridge.openapi import OAuthBuilder, ContentContext, Config + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + topic_id = ctx.create_topic( + title="My Article", + body="Hello world", + topic_type="article", + tickers=["700.HK"], + ) + print(topic_id) + """ + ... -class DividendList: - """Dividend history response.""" + async def create_topic_reply( + self, + topic_id: str, + body: str, + reply_to_id: Optional[str] = None, + ) -> TopicReply: + """ + Post a reply to a community topic - list: list[DividendItem] - """List of dividend events""" + Args: + topic_id: Topic ID + body: Reply body (plain text only) + reply_to_id: ID of the parent reply to nest under; empty or "0" for top-level + Returns: + The created reply -class RatingEvaluate: - """Analyst rating distribution counts.""" + Examples: + :: - buy: int - """Number of Buy ratings""" - over: int - """Number of Strong Buy / Outperform ratings""" - hold: int - """Number of Hold ratings""" - under: int - """Number of Underperform ratings""" - sell: int - """Number of Sell ratings""" - no_opinion: int - """Number of No Opinion ratings""" - total: int - """Total analyst count""" - start_date: str - """Window start (unix timestamp string)""" - end_date: str - """Window end (unix timestamp string)""" + from longbridge.openapi import OAuthBuilder, ContentContext, Config + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + reply = ctx.create_topic_reply("123456", "Great post!") + print(reply.id) + """ + ... -class RatingTarget: - """Analyst target price range.""" + async def list_topic_replies( + self, + topic_id: str, + page: Optional[int] = None, + size: Optional[int] = None, + ) -> List[TopicReply]: + """ + List replies on a topic - highest_price: str - """Highest price target""" - lowest_price: str - """Lowest price target""" - prev_close: str - """Previous close price""" - start_date: str - """Window start""" - end_date: str - """Window end""" + Args: + topic_id: Topic ID + page: Page number (default 1) + size: Page size (default 20, range 1-50) + Returns: + List of topic replies -class InstitutionRatingLatest: - """Latest analyst rating snapshot.""" + Examples: + :: - evaluate: RatingEvaluate - """Rating distribution counts""" - target: RatingTarget - """Target price range""" - industry_id: int - """Industry classification ID""" - industry_name: str - """Industry name""" - industry_rank: int - """Rank within the industry (1 = highest)""" - industry_total: int - """Total securities in the industry""" - industry_mean: int - """Mean analyst count in the industry""" - industry_median: int - """Median analyst count in the industry""" + from longbridge.openapi import OAuthBuilder, ContentContext, Config + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + replies = ctx.list_topic_replies("123456") + for r in replies: + print(r.id, r.body) + """ + ... -class RatingSummaryEvaluate: - """Simplified rating distribution for consensus summary.""" + async def my_topics( + self, + page: Optional[int] = None, + size: Optional[int] = None, + topic_type: Optional[str] = None, + ) -> List[OwnedTopic]: + """ + Get topics created by the current authenticated user + + Args: + page: Page number (default 1) + size: Page size (default 50, range 1-500) + topic_type: Filter by type: "article" or "post"; empty returns all + + Returns: + List of owned topics + + Examples: + :: + + from longbridge.openapi import OAuthBuilder, ContentContext, Config + + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + topics = ctx.my_topics(size=20) + for t in topics: + print(t.id, t.title) + """ + ... + + async def news(self, symbol: str) -> List[NewsItem]: + """ + Get news list for a symbol + + Args: + symbol: Security symbol, e.g. "700.HK" + + Returns: + List of news items + + Examples: + :: + + from longbridge.openapi import OAuthBuilder, ContentContext, Config + + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + news = ctx.news("700.HK") + for n in news: + print(n.id, n.title) + """ + ... + + async def topic_detail(self, id: str) -> OwnedTopic: + """ + Get full details of a topic by its ID + + Args: + id: Topic ID + + Returns: + Full topic detail + + Examples: + :: + + from longbridge.openapi import OAuthBuilder, ContentContext, Config + + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + topic = ctx.topic_detail("123456") + print(topic.title, topic.body) + """ + ... + + async def topics(self, symbol: str) -> List[TopicItem]: + """ + Get discussion topics list for a symbol + + Args: + symbol: Security symbol, e.g. "700.HK" + + Returns: + List of topic items + + Examples: + :: + + from longbridge.openapi import OAuthBuilder, ContentContext, Config + + oauth = OAuthBuilder("your-client-id").build( + lambda url: print("Visit:", url) + ) + config = Config.from_oauth(oauth) + ctx = ContentContext(config) + topics = ctx.topics("700.HK") + for t in topics: + print(t.id, t.title) + """ + ... + +class FinancialReports: + """ + Financial reports response. + + ``list`` contains raw nested data keyed by report kind + (``"IS"``, ``"BS"``, ``"CF"``). + """ + + list: object + """Raw financial data dict (IS/BS/CF indicators)""" + + +class DividendItem: + """One dividend or distribution event.""" + + symbol: str + """Security symbol, e.g. ``"700.HK"``""" + id: str + """Internal record ID""" + desc: str + """Human-readable description, e.g. ``"每股派息 5.3 HKD"``""" + record_date: str + """Record / book-close date""" + ex_date: str + """Ex-dividend date""" + payment_date: str + """Payment date""" + + +class DividendList: + """Dividend history response.""" + + list: list[DividendItem] + """List of dividend events""" + + +class RatingEvaluate: + """Analyst rating distribution counts.""" + + buy: int + """Number of Buy ratings""" + over: int + """Number of Strong Buy / Outperform ratings""" + hold: int + """Number of Hold ratings""" + under: int + """Number of Underperform ratings""" + sell: int + """Number of Sell ratings""" + no_opinion: int + """Number of No Opinion ratings""" + total: int + """Total analyst count""" + start_date: str + """Window start (unix timestamp string)""" + end_date: str + """Window end (unix timestamp string)""" + + +class RatingTarget: + """Analyst target price range.""" + + highest_price: str + """Highest price target""" + lowest_price: str + """Lowest price target""" + prev_close: str + """Previous close price""" + start_date: str + """Window start""" + end_date: str + """Window end""" + + +class InstitutionRatingLatest: + """Latest analyst rating snapshot.""" + + evaluate: RatingEvaluate + """Rating distribution counts""" + target: RatingTarget + """Target price range""" + industry_id: int + """Industry classification ID""" + industry_name: str + """Industry name""" + industry_rank: int + """Rank within the industry (1 = highest)""" + industry_total: int + """Total securities in the industry""" + industry_mean: int + """Mean analyst count in the industry""" + industry_median: int + """Median analyst count in the industry""" + + +class RatingSummaryEvaluate: + """Simplified rating distribution for consensus summary.""" buy: int """Number of Buy ratings""" @@ -11604,18 +11909,22 @@ class FundamentalContext: def macroeconomic_indicators( self, + country: "MacroeconomicCountry | None" = None, + keyword: str | None = None, offset: int | None = None, limit: int | None = None, - ) -> list["MacroeconomicIndicator"]: + ) -> "MacroeconomicIndicatorListResponse": """ List macroeconomic indicators. Args: + country: Filter by country / region (optional) + keyword: Filter by keyword (optional) offset: Pagination offset (default 0) limit: Page size (default 100, max 1000) Returns: - List of :class:`MacroeconomicIndicator` + :class:`MacroeconomicIndicatorListResponse` """ ... @@ -11624,6 +11933,7 @@ class FundamentalContext: indicator_code: str, start_date: str | None = None, end_date: str | None = None, + offset: int | None = None, limit: int | None = None, ) -> "MacroeconomicResponse": """ @@ -11633,6 +11943,7 @@ class FundamentalContext: indicator_code: External vendor code from ``macroeconomic_indicators`` start_date: Start date in ``"YYYY-MM-DD"`` format (optional) end_date: End date in ``"YYYY-MM-DD"`` format (optional) + offset: Pagination offset (optional) limit: Max records to return (default 100, max 100) Returns: @@ -11850,16 +12161,228 @@ class AsyncFundamentalContext: """Get US ETF document list. US token required. Args: - symbol: ETF symbol, e.g. ``"SPY.US"`` - size: Number of files to return; ``None`` returns all + symbol: ETF symbol, e.g. ``"SPY.US"`` + size: Number of files to return; ``None`` returns all + + Returns: + Awaitable[:class:`USETFFilesResponse`] + """ + ... + + +# ── FundamentalContext new response types ───────────────────────── + + @classmethod + def create(cls, config: Config) -> AsyncFundamentalContext: ... + + async def buyback(self, symbol: str) -> "BuybackData": + """ + Get buyback data for a security. + + Args: + symbol: Security symbol, e.g. ``"AAPL.US"`` + + Returns: + :class:`BuybackData` + """ + ... + + async def company(self, symbol: str) -> "CompanyOverview": + """Get company overview.""" + ... + + async def consensus(self, symbol: str) -> "FinancialConsensus": + """Get financial consensus estimates.""" + ... + + async def corp_action(self, symbol: str) -> "CorpActions": + """Get corporate actions (dividends, splits, buybacks, etc.).""" + ... + + async def dividend(self, symbol: str) -> "DividendList": + """Get dividend history.""" + ... + + async def dividend_detail(self, symbol: str) -> "DividendList": + """Get detailed dividend information.""" + ... + + async def etf_asset_allocation(self, symbol: str) -> "AssetAllocationResponse": + """ + Get ETF asset allocation (holdings / regional / asset class / industry). + + Args: + symbol: ETF security code (e.g. ``"QQQ.US"``) + + Returns: + :class:`AssetAllocationResponse` with allocation groups + """ + ... + + async def executive(self, symbol: str) -> "ExecutiveList": + """Get executive and board member information.""" + ... + + async def financial_report( + self, + symbol: str, + kind: "FinancialReportKind" = ..., + period: "FinancialReportPeriod | None" = None, + ) -> "FinancialReports": + """ + Get financial reports. + + Args: + symbol: Security symbol, e.g. ``"700.HK"`` + kind: Report kind (default ``All``) + period: Report period (``None`` means not specified) + + Returns: + Financial reports response + """ + ... + + async def forecast_eps(self, symbol: str) -> "ForecastEps": + """Get EPS forecasts.""" + ... + + async def fund_holder(self, symbol: str) -> "FundHolders": + """Get funds and ETFs that hold the security.""" + ... + + async def industry_valuation(self, symbol: str) -> "IndustryValuationList": + """Get industry peer valuation comparison.""" + ... + + async def industry_valuation_dist(self, symbol: str) -> "IndustryValuationDist": + """Get industry valuation distribution.""" + ... + + async def institution_rating(self, symbol: str) -> "InstitutionRating": + """ + Get analyst ratings (latest snapshot + consensus summary). + + Args: + symbol: Security symbol + + Returns: + Combined analyst rating response + """ + ... + + async def institution_rating_detail(self, symbol: str) -> "InstitutionRatingDetail": + """Get historical analyst rating details.""" + ... + + async def invest_relation(self, symbol: str) -> "InvestRelations": + """Get investor relations / investment holdings.""" + ... + + async def macroeconomic( + self, + indicator_code: str, + start_date: str | None = None, + end_date: str | None = None, + offset: int | None = None, + limit: int | None = None, + ) -> "MacroeconomicResponse": + """ + Get historical data for a macroeconomic indicator. + + Args: + indicator_code: External vendor code from ``macroeconomic_indicators`` + start_date: Start date in ``"YYYY-MM-DD"`` format (optional) + end_date: End date in ``"YYYY-MM-DD"`` format (optional) + offset: Pagination offset (optional) + limit: Max records to return (default 100, max 100) + + Returns: + :class:`MacroeconomicResponse` + """ + ... + + async def macroeconomic_indicators( + self, + country: "MacroeconomicCountry | None" = None, + keyword: str | None = None, + offset: int | None = None, + limit: int | None = None, + ) -> "MacroeconomicIndicatorListResponse": + """ + List macroeconomic indicators. + + Args: + country: Filter by country / region (optional) + keyword: Filter by keyword (optional) + offset: Pagination offset (default 0) + limit: Page size (default 100, max 1000) + + Returns: + :class:`MacroeconomicIndicatorListResponse` + """ + ... + + async def operating(self, symbol: str) -> "OperatingList": + """Get operating metrics and financial report summaries.""" + ... + + async def shareholder(self, symbol: str) -> "ShareholderList": + """Get major shareholders.""" + ... + + async def shareholder_detail( + self, symbol: str, object_id: int + ) -> "ShareholderDetailResponse": + """ + Get holding history and detail for one shareholder. + + Args: + symbol: Security symbol + object_id: Shareholder object ID + + Returns: + :class:`ShareholderDetailResponse` with raw JSON data + """ + ... + + async def shareholder_top(self, symbol: str) -> "ShareholderTopResponse": + """ + Get ranked list of top shareholders. + + Args: + symbol: Security symbol + + Returns: + :class:`ShareholderTopResponse` with raw JSON data + """ + ... + + async def valuation(self, symbol: str) -> "ValuationData": + """Get valuation metrics (PE / PB / PS / dividend yield).""" + ... + + async def valuation_comparison( + self, + symbol: str, + currency: str, + comparison_symbols: Optional[List[str]] = None, + ) -> "ValuationComparisonResponse": + """ + Get valuation comparison between a security and optional peers. + + Args: + symbol: Security symbol + currency: Currency code (e.g. ``"USD"``) + comparison_symbols: Optional list of peer symbols Returns: - Awaitable[:class:`USETFFilesResponse`] + :class:`ValuationComparisonResponse` with raw JSON data """ ... - -# ── FundamentalContext new response types ───────────────────────── + async def valuation_history(self, symbol: str) -> "ValuationHistoryResponse": + """Get historical valuation data.""" + ... class ShareholderTopResponse: """Top-shareholder list response. ``data`` is a Python dict/list from JSON.""" @@ -12009,6 +12532,44 @@ class MultiLanguageText: traditional_chinese: str +class MacroeconomicCountry: + """ + Macroeconomic country / region + """ + + class HongKong(MacroeconomicCountry): + """Hong Kong""" + + class China(MacroeconomicCountry): + """China""" + + class UnitedStates(MacroeconomicCountry): + """United States""" + + class EuroZone(MacroeconomicCountry): + """Euro Zone""" + + class Japan(MacroeconomicCountry): + """Japan""" + + class Singapore(MacroeconomicCountry): + """Singapore""" + +class MacroeconomicIndicatorListResponse: + """ + Response for :meth:`FundamentalContext.macroeconomic_indicators` + """ + + data: List["MacroeconomicIndicator"] + """ + Macroeconomic indicators + """ + + count: int + """ + Total number of indicators + """ + class MacroeconomicIndicator: """Metadata for one macroeconomic indicator.""" @@ -12497,6 +13058,136 @@ class MarketContext: # ── MarketContext new response types ────────────────────────────── + +class AsyncMarketContext: + """ + Async market context. Create via ``AsyncMarketContext.create(config)``. + """ + + @classmethod + def create(cls, config: Config) -> AsyncMarketContext: ... + + async def market_status(self) -> "MarketStatusResponse": + """Get current trading status for all markets.""" + ... + + async def broker_holding( + self, + symbol: str, + period: "BrokerHoldingPeriod" = ..., + ) -> "BrokerHoldingTop": + """ + Get top broker holdings (buy/sell leaders) for a security. + + Args: + symbol: Security symbol + period: Lookback period (default ``Rct1``) + """ + ... + + async def broker_holding_detail(self, symbol: str) -> "BrokerHoldingDetail": + """Get full broker holding details for a security.""" + ... + + async def broker_holding_daily( + self, symbol: str, broker_id: str + ) -> "BrokerHoldingDailyHistory": + """ + Get daily holding history for a specific broker. + + Args: + symbol: Security symbol + broker_id: Broker participant number, e.g. ``"B01451"`` + """ + ... + + async def ah_premium( + self, + symbol: str, + period: "AhPremiumPeriod" = ..., + count: int = 100, + ) -> "AhPremiumKlines": + """ + Get A/H premium K-line data for a dual-listed security. + + Args: + symbol: H-share symbol, e.g. ``"2318.HK"`` + period: K-line period (default ``Day``) + count: Number of K-lines to return + """ + ... + + async def ah_premium_intraday(self, symbol: str) -> "AhPremiumIntraday": + """Get A/H premium intraday data for a dual-listed security.""" + ... + + async def trade_stats(self, symbol: str) -> "TradeStatsResponse": + """Get buy/sell/neutral trade statistics for a security.""" + ... + + async def anomaly(self, market: str) -> "AnomalyResponse": + """ + Get market anomaly alerts (unusual price/volume events). + + Args: + market: Market code: ``"HK"``, ``"US"``, ``"CN"``, ``"SG"`` + """ + ... + + async def constituent(self, symbol: str) -> "IndexConstituents": + """ + Get constituent stocks for an index. + + Args: + symbol: Index symbol, e.g. ``"HSI.HK"`` + """ + ... + + async def top_movers( + self, + markets: List[str], + sort: int = 0, + date: Optional[str] = None, + limit: int = 20, + ) -> "TopMoversResponse": + """ + Get top movers (stocks with unusual price movements) across one or more markets. + + Args: + markets: List of market codes, e.g. ``["HK", "US"]`` + sort: Sort order (0=ascending, 1=descending) + date: Optional date filter (``"YYYY-MM-DD"``) + limit: Max records to return + + Returns: + :class:`TopMoversResponse` with raw JSON data + """ + ... + + async def rank_categories(self) -> "RankCategoriesResponse": + """ + Get all available rank category keys and labels. + + Returns: + :class:`RankCategoriesResponse` with typed categories + """ + ... + + async def rank_list( + self, key: str, need_article: bool = False + ) -> "RankListResponse": + """ + Get a ranked list of securities for the given category key. + + Args: + key: Category key from :meth:`rank_categories` + need_article: Whether to include article content + + Returns: + :class:`RankListResponse` with raw JSON data + """ + ... + class TopMoversStock: """Stock information within a top-movers event.""" @@ -12916,6 +13607,33 @@ class CalendarContext: # ── PortfolioContext ────────────────────────────────────────────── + +class AsyncCalendarContext: + """ + Async calendar context. Create via ``AsyncCalendarContext.create(config)``. + """ + + @classmethod + def create(cls, config: Config) -> AsyncCalendarContext: ... + + async def finance_calendar( + self, + category: "CalendarCategory", + start: str, + end: str, + market: str | None = None, + ) -> "CalendarEventsResponse": + """ + Get financial calendar events. + + Args: + category: Event category + start: Start date in ``YYYY-MM-DD`` format + end: End date in ``YYYY-MM-DD`` format + market: Optional market filter, e.g. ``"HK"`` + """ + ... + class ExchangeRate: """One currency exchange rate.""" @@ -13266,6 +13984,71 @@ class PortfolioContext: ... +class AsyncPortfolioContext: + """ + Async portfolio context. Create via ``AsyncPortfolioContext.create(config)``. + """ + + @classmethod + def create(cls, config: Config) -> AsyncPortfolioContext: ... + + async def exchange_rate(self) -> "ExchangeRates": + """Get exchange rates for supported currencies.""" + ... + + async def profit_analysis( + self, + start: str | None = None, + end: str | None = None, + ) -> "ProfitAnalysis": + """ + Get portfolio P&L analysis (summary + per-security breakdown). + + Args: + start: Optional start date in ``YYYY-MM-DD`` format + end: Optional end date in ``YYYY-MM-DD`` format + """ + ... + + async def profit_analysis_detail( + self, + symbol: str, + start: str | None = None, + end: str | None = None, + ) -> "ProfitAnalysisDetail": + """ + Get P&L detail for a specific security. + + Args: + symbol: Security symbol, e.g. ``"700.HK"`` + start: Optional start date + end: Optional end date + """ + ... + + async def profit_analysis_flows( + self, + symbol: str, + page: int, + size: int, + derivative: bool, + start: str | None = None, + end: str | None = None, + ) -> "ProfitAnalysisFlows": + """ + Get paginated P&L flow records for a security. + + Args: + symbol: Security symbol, e.g. ``"700.HK"`` + page: Page number (1-based) + size: Page size + derivative: Whether to include derivative flows + start: Optional start date in ``YYYY-MM-DD`` format + end: Optional end date in ``YYYY-MM-DD`` format + """ + ... + + class ProfitAnalysisByMarketItem: """One security entry in a by-market P&L response.""" @@ -13450,12 +14233,8 @@ class AlertContext: """ ... - def enable(self, alert_id: str) -> None: - """Enable a price alert.""" - ... - - def disable(self, alert_id: str) -> None: - """Disable a price alert.""" + def update(self, item: "AlertItem") -> None: + """Update an existing price alert.""" ... def delete(self, alert_ids: list[str]) -> None: @@ -13514,6 +14293,16 @@ class DcaPlan: """Cumulative profit/loss""" +class DcaCreateResult: + """ + Result of creating or updating a DCA plan + """ + + plan_id: str + """ + The created or updated plan ID + """ + class DcaList: """DCA plan list response.""" @@ -13669,7 +14458,7 @@ class DCAContext: day_of_week: str | None = None, day_of_month: int | None = None, allow_margin: bool = False, - ) -> "DcaList": + ) -> "DcaCreateResult": """ Create a new DCA plan. @@ -13683,15 +14472,37 @@ class DCAContext: """ ... - def pause(self, plan_id: str) -> "DcaList": + def update( + self, + plan_id: str, + amount: str | None = None, + frequency: "DCAFrequency | None" = None, + day_of_week: str | None = None, + day_of_month: int | None = None, + allow_margin: bool | None = None, + ) -> "DcaCreateResult": + """ + Update an existing DCA plan. Only the provided fields are changed. + + Args: + plan_id: Plan ID + amount: Investment amount per period + frequency: Investment frequency + day_of_week: Day of week for weekly plans, e.g. ``"Mon"`` + day_of_month: Day of month for monthly plans (1–28) + allow_margin: Whether to allow margin finance + """ + ... + + def pause(self, plan_id: str) -> None: """Pause (suspend) a DCA plan.""" ... - def resume(self, plan_id: str) -> "DcaList": + def resume(self, plan_id: str) -> None: """Resume a suspended DCA plan.""" ... - def stop(self, plan_id: str) -> "DcaList": + def stop(self, plan_id: str) -> None: """Permanently stop a DCA plan.""" ... @@ -13888,7 +14699,7 @@ class SharelistContext: """ ... - def create(self, name: str, description: str | None = None) -> "SharelistDetail": + def create(self, name: str, description: str | None = None) -> None: """ Create a new community sharelist.