From 558b19294c90ae1de9f5a5b612649f3daa4cd2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Fri, 15 May 2026 19:28:49 +0800 Subject: [PATCH 1/6] feat(fundamental): add business-segments, institution-rating-views, industry-rank, industry-peers, financial-report-snapshot APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ported from longbridge-terminal PR #202: - business_segments / business_segments_history — GET /v1/quote/fundamentals/business-segments[/history] - institution_rating_views — GET /v1/quote/ratings/institutional - industry_rank — GET /v1/quote/industry/rank - industry_peers — GET /v1/quote/industries/peers - financial_report_snapshot — GET /v1/quote/financials/earnings-snapshot Includes types, async context methods, and blocking wrappers. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- CHANGELOG.md | 13 ++ rust/src/blocking/fundamental.rs | 69 +++++++++ rust/src/fundamental/context.rs | 178 +++++++++++++++++++++++ rust/src/fundamental/types.rs | 240 +++++++++++++++++++++++++++++++ 4 files changed, 500 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a1666f3..458d650f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ 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:** Six new `FundamentalContext` methods: + - `business_segments` — GET `/v1/quote/fundamentals/business-segments`: latest business segment breakdown (name, percent). + - `business_segments_history` — GET `/v1/quote/fundamentals/business-segments/history`: historical business and regional segment breakdowns with optional `report` and `cate` filters. + - `institution_rating_views` — GET `/v1/quote/ratings/institutional`: historical rating distribution time-series (buy/over/hold/under/sell/total per date). + - `industry_rank` — GET `/v1/quote/industry/rank`: industry leaderboard for a market with configurable indicator and sort direction. + - `industry_peers` — GET `/v1/quote/industries/peers`: recursive industry peer chain; accepts both symbol-style (`AAPL.US`) and raw counter IDs (`BK/US/123`). + - `financial_report_snapshot` — GET `/v1/quote/financials/earnings-snapshot`: earnings snapshot with forecast (revenue/EBIT/EPS) and reported (P&L, cash-flows, balance-sheet ratios) metrics. +- All new methods are also available on `FundamentalContextSync` (blocking API). + # [4.1.0] ## Breaking changes diff --git a/rust/src/blocking/fundamental.rs b/rust/src/blocking/fundamental.rs index c2e674432..b0e539c51 100644 --- a/rust/src/blocking/fundamental.rs +++ b/rust/src/blocking/fundamental.rs @@ -179,4 +179,73 @@ impl FundamentalContextSync { self.rt .call(move |ctx| async move { ctx.ratings(symbol).await }) } + + /// Get latest business segment breakdown + pub fn business_segments( + &self, + symbol: impl Into + Send + 'static, + ) -> Result { + self.rt + .call(move |ctx| async move { ctx.business_segments(symbol).await }) + } + + /// Get historical business segment breakdowns + pub fn business_segments_history( + &self, + symbol: impl Into + Send + 'static, + report: Option<&'static str>, + cate: Option, + ) -> Result { + self.rt.call( + move |ctx| async move { ctx.business_segments_history(symbol, report, cate).await }, + ) + } + + /// Get historical institutional rating views + pub fn institution_rating_views( + &self, + symbol: impl Into + Send + 'static, + ) -> Result { + self.rt + .call(move |ctx| async move { ctx.institution_rating_views(symbol).await }) + } + + /// Get industry rank for a market + pub fn industry_rank( + &self, + market: impl Into + Send + 'static, + indicator: impl Into + Send + 'static, + sort_type: impl Into + Send + 'static, + limit: u32, + ) -> Result { + self.rt.call(move |ctx| async move { + ctx.industry_rank(market, indicator, sort_type, limit).await + }) + } + + /// Get industry peer chain + pub fn industry_peers( + &self, + counter_id: impl Into + Send + 'static, + market: impl Into + Send + 'static, + industry_id: Option, + ) -> Result { + self.rt.call( + move |ctx| async move { ctx.industry_peers(counter_id, market, industry_id).await }, + ) + } + + /// Get financial report snapshot (earnings snapshot) + pub fn financial_report_snapshot( + &self, + symbol: impl Into + Send + 'static, + report: Option<&'static str>, + fiscal_year: Option, + fiscal_period: Option<&'static str>, + ) -> Result { + self.rt.call(move |ctx| async move { + ctx.financial_report_snapshot(symbol, report, fiscal_year, fiscal_period) + .await + }) + } } diff --git a/rust/src/fundamental/context.rs b/rust/src/fundamental/context.rs index 015d3c992..a71fffbd0 100644 --- a/rust/src/fundamental/context.rs +++ b/rust/src/fundamental/context.rs @@ -496,4 +496,182 @@ impl FundamentalContext { ) .await } + + // ── business_segments ──────────────────────────────────────── + + /// Get the latest business segment breakdown for a security. + /// + /// Path: `GET /v1/quote/fundamentals/business-segments` + pub async fn business_segments(&self, symbol: impl Into) -> Result { + #[derive(Serialize)] + struct Query { + counter_id: String, + } + self.get( + "/v1/quote/fundamentals/business-segments", + Query { + counter_id: symbol_to_counter_id(&symbol.into()), + }, + ) + .await + } + + /// Get historical business segment breakdowns for a security. + /// + /// Path: `GET /v1/quote/fundamentals/business-segments/history` + pub async fn business_segments_history( + &self, + symbol: impl Into, + report: Option<&'static str>, + cate: Option, + ) -> Result { + #[derive(Serialize)] + struct Query { + counter_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + report: Option<&'static str>, + #[serde(skip_serializing_if = "Option::is_none")] + cate: Option, + } + self.get( + "/v1/quote/fundamentals/business-segments/history", + Query { + counter_id: symbol_to_counter_id(&symbol.into()), + report, + cate, + }, + ) + .await + } + + // ── institution_rating_views ────────────────────────────────── + + /// Get historical institutional rating view time-series for a security. + /// + /// Path: `GET /v1/quote/ratings/institutional` + pub async fn institution_rating_views( + &self, + symbol: impl Into, + ) -> Result { + #[derive(Serialize)] + struct Query { + counter_id: String, + } + self.get( + "/v1/quote/ratings/institutional", + Query { + counter_id: symbol_to_counter_id(&symbol.into()), + }, + ) + .await + } + + // ── industry_rank ───────────────────────────────────────────── + + /// Get industry rank for a market. + /// + /// Path: `GET /v1/quote/industry/rank` + /// + /// `indicator` is a numeric string `"0"`–`"7"`; + /// `sort_type` is `"0"` (ascending) or `"1"` (descending). + pub async fn industry_rank( + &self, + market: impl Into, + indicator: impl Into, + sort_type: impl Into, + limit: u32, + ) -> Result { + #[derive(Serialize)] + struct Query { + market: String, + indicator: String, + sort_type: String, + limit: u32, + } + self.get( + "/v1/quote/industry/rank", + Query { + market: market.into(), + indicator: indicator.into(), + sort_type: sort_type.into(), + limit, + }, + ) + .await + } + + // ── industry_peers ──────────────────────────────────────────── + + /// Get the industry peer chain for a security or industry. + /// + /// Path: `GET /v1/quote/industries/peers` + /// + /// `counter_id` may be a regular symbol (e.g. `"AAPL.US"`) or an industry + /// counter ID (e.g. `"BK/US/123"`) — pass it through as-is if it already + /// contains a `/`. + pub async fn industry_peers( + &self, + counter_id: impl Into, + market: impl Into, + industry_id: Option, + ) -> Result { + let raw = counter_id.into(); + let cid = if raw.contains('/') { + raw + } else { + symbol_to_counter_id(&raw) + }; + #[derive(Serialize)] + struct Query { + #[serde(rename = "type")] + kind: &'static str, + market: String, + industry_id: String, + counter_id: String, + } + self.get( + "/v1/quote/industries/peers", + Query { + kind: "1", + market: market.into(), + industry_id: industry_id.unwrap_or_default(), + counter_id: cid, + }, + ) + .await + } + + // ── financial_report_snapshot ───────────────────────────────── + + /// Get a financial report snapshot (earnings snapshot) for a security. + /// + /// Path: `GET /v1/quote/financials/earnings-snapshot` + pub async fn financial_report_snapshot( + &self, + symbol: impl Into, + report: Option<&'static str>, + fiscal_year: Option, + fiscal_period: Option<&'static str>, + ) -> Result { + #[derive(Serialize)] + struct Query { + counter_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + report: Option<&'static str>, + #[serde(skip_serializing_if = "Option::is_none")] + fiscal_year: Option, + #[serde(skip_serializing_if = "Option::is_none")] + fiscal_period: Option<&'static str>, + } + self.get( + "/v1/quote/financials/earnings-snapshot", + Query { + counter_id: symbol_to_counter_id(&symbol.into()), + report, + fiscal_year, + fiscal_period, + }, + ) + .await + } } diff --git a/rust/src/fundamental/types.rs b/rust/src/fundamental/types.rs index a9c5b6af5..ad60b9d36 100644 --- a/rust/src/fundamental/types.rs +++ b/rust/src/fundamental/types.rs @@ -1110,6 +1110,246 @@ pub enum FinancialReportKind { All, } +// ── business_segments ───────────────────────────────────────────── + +/// Response for [`crate::FundamentalContext::business_segments`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BusinessSegments { + /// Report date + pub date: String, + /// Total revenue + pub total: String, + /// Reporting currency + pub currency: String, + /// Business segment breakdown + #[serde(default)] + pub business: Vec, +} + +/// One business segment item (latest snapshot) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BusinessSegmentItem { + /// Segment name + pub name: String, + /// Percentage of total revenue + pub percent: String, +} + +/// Response for [`crate::FundamentalContext::business_segments_history`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BusinessSegmentsHistory { + /// Historical snapshots + #[serde(default)] + pub historical: Vec, +} + +/// One historical business segments snapshot +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BusinessSegmentsHistoricalItem { + /// Report date + pub date: String, + /// Total revenue + pub total: String, + /// Reporting currency + pub currency: String, + /// Business segment breakdown + #[serde(default)] + pub business: Vec, + /// Regional breakdown + #[serde(default)] + pub regionals: Vec, +} + +/// One business/regional segment item in a historical snapshot +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BusinessSegmentHistoryItem { + /// Segment name + pub name: String, + /// Percentage of total + pub percent: String, + /// Absolute value + pub value: String, +} + +// ── institution_rating_views ────────────────────────────────────── + +/// Response for [`crate::FundamentalContext::institution_rating_views`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstitutionRatingViews { + /// Historical rating distribution snapshots + #[serde(default)] + pub elist: Vec, +} + +/// One historical rating distribution snapshot +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstitutionRatingViewItem { + /// Date as unix timestamp (int64) + pub date: i64, + /// Number of "Buy" ratings + pub buy: i32, + /// Number of "Outperform" ratings + pub over: i32, + /// Number of "Hold" ratings + pub hold: i32, + /// Number of "Underperform" ratings + pub under: i32, + /// Number of "Sell" ratings + pub sell: i32, + /// Total analyst count + pub total: i32, +} + +// ── industry_rank ───────────────────────────────────────────────── + +/// Response for [`crate::FundamentalContext::industry_rank`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IndustryRankResponse { + /// Grouped rank items + #[serde(default)] + pub items: Vec, +} + +/// A group of ranked industry items +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IndustryRankGroup { + /// Items in this group + #[serde(default)] + pub lists: Vec, +} + +/// One ranked industry item +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IndustryRankItem { + /// Industry / sector name + pub name: String, + /// Counter ID of the industry + pub counter_id: String, + /// Change percentage + pub chg: String, + /// Name of the leading stock + pub leading_name: String, + /// Ticker of the leading stock + pub leading_ticker: String, + /// Change percentage of the leading stock + pub leading_chg: String, + /// Value label name + pub value_name: String, + /// Value data + pub value_data: String, +} + +// ── industry_peers ──────────────────────────────────────────────── + +/// Response for [`crate::FundamentalContext::industry_peers`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IndustryPeersResponse { + /// Top-level industry node info + pub top: IndustryPeersTop, + /// Root peer chain node (may be absent if no data) + pub chain: Option, +} + +/// Top-level industry info in the peers response +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IndustryPeersTop { + /// Industry name + pub name: String, + /// Market code + pub market: String, +} + +/// A node in the recursive industry peer chain +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IndustryPeerNode { + /// Node name + pub name: String, + /// Counter ID + pub counter_id: String, + /// Number of stocks in this node + pub stock_num: String, + /// Change percentage + pub chg: String, + /// Year-to-date change + pub ytd_chg: String, + /// Child nodes (recursive) + #[serde(default)] + pub next: Vec, +} + +// ── financial_report_snapshot ───────────────────────────────────── + +/// Response for [`crate::FundamentalContext::financial_report_snapshot`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FinancialReportSnapshot { + /// Company name + pub name: String, + /// Ticker code + pub ticker: String, + /// Fiscal period start date + pub fp_start: String, + /// Fiscal period end date + pub fp_end: String, + /// Reporting currency + pub currency: String, + /// Report description + pub report_desc: String, + /// Forecast revenue + pub fo_revenue: Option, + /// Forecast EBIT + pub fo_ebit: Option, + /// Forecast EPS + pub fo_eps: Option, + /// Reported revenue + pub fr_revenue: Option, + /// Reported net profit + pub fr_profit: Option, + /// Reported operating cash flow + pub fr_operate_cash: Option, + /// Reported investing cash flow + pub fr_invest_cash: Option, + /// Reported financing cash flow + pub fr_finance_cash: Option, + /// Reported total assets + pub fr_total_assets: Option, + /// Reported total liabilities + pub fr_total_liability: Option, + /// ROE TTM + pub fr_roe_ttm: String, + /// Profit margin + pub fr_profit_margin: String, + /// Profit margin TTM + pub fr_profit_margin_ttm: String, + /// Asset turnover TTM + pub fr_asset_turn_ttm: String, + /// Leverage TTM + pub fr_leverage_ttm: String, + /// Debt-to-assets ratio + pub fr_debt_assets_ratio: String, +} + +/// A forecast metric in the financial report snapshot +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SnapshotForecastMetric { + /// Actual value + pub value: String, + /// Year-over-year change + pub yoy: String, + /// Beat/miss description + pub cmp_desc: String, + /// Consensus estimate value + pub est_value: String, +} + +/// A reported metric in the financial report snapshot +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SnapshotReportedMetric { + /// Actual value + pub value: String, + /// Year-over-year change + pub yoy: String, +} + /// Financial report period type #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum FinancialReportPeriod { From 8bd0d1799e58c85c7a6937f3ac0b742c93bd14ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Mon, 18 May 2026 11:53:21 +0800 Subject: [PATCH 2/6] fix(fundamental): fix wire types for InstitutionRatingViews and IndustryPeerNode Confirmed via live API testing: - InstitutionRatingViewItem: date/buy/over/hold/under/sell/total come as strings - IndustryPeerNode: stock_num comes as an integer (i32) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- rust/src/fundamental/types.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/rust/src/fundamental/types.rs b/rust/src/fundamental/types.rs index ad60b9d36..556074048 100644 --- a/rust/src/fundamental/types.rs +++ b/rust/src/fundamental/types.rs @@ -1184,20 +1184,20 @@ pub struct InstitutionRatingViews { /// One historical rating distribution snapshot #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstitutionRatingViewItem { - /// Date as unix timestamp (int64) - pub date: i64, - /// Number of "Buy" ratings - pub buy: i32, - /// Number of "Outperform" ratings - pub over: i32, - /// Number of "Hold" ratings - pub hold: i32, - /// Number of "Underperform" ratings - pub under: i32, - /// Number of "Sell" ratings - pub sell: i32, - /// Total analyst count - pub total: i32, + /// Date as unix timestamp string (API returns as quoted or bare integer) + pub date: String, + /// Number of "Buy" ratings (API returns as string) + pub buy: String, + /// Number of "Outperform" ratings (API returns as string) + pub over: String, + /// Number of "Hold" ratings (API returns as string) + pub hold: String, + /// Number of "Underperform" ratings (API returns as string) + pub under: String, + /// Number of "Sell" ratings (API returns as string) + pub sell: String, + /// Total analyst count (API returns as string) + pub total: String, } // ── industry_rank ───────────────────────────────────────────────── @@ -1266,8 +1266,8 @@ pub struct IndustryPeerNode { pub name: String, /// Counter ID pub counter_id: String, - /// Number of stocks in this node - pub stock_num: String, + /// Number of stocks in this node (API returns as integer) + pub stock_num: i32, /// Change percentage pub chg: String, /// Year-to-date change From d07f48d734a14dd8e49bfc33beb7553bfed2a0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Mon, 18 May 2026 17:42:53 +0800 Subject: [PATCH 3/6] feat(fundamental): add business-segments, institution-rating-views, industry-rank, industry-peers, financial-report-snapshot to all language SDKs Adds the 6 new fundamental APIs to Python, Node.js, Java, C, and C++: - business_segments / business_segments_history - institution_rating_views - industry_rank - industry_peers - financial_report_snapshot Co-Authored-By: Claude Sonnet 4.6 (1M context) --- CHANGELOG.md | 7 + c/cbindgen.toml | 23 + c/csrc/include/longbridge.h | 477 +++++++++++ c/src/fundamental_context/context.rs | 197 +++++ c/src/fundamental_context/types.rs | 760 ++++++++++++++++++ cpp/include/fundamental_context.hpp | 38 + cpp/include/types.hpp | 153 ++++ cpp/src/convert.hpp | 92 +++ cpp/src/fundamental_context.cpp | 18 + .../BusinessSegmentHistoryItem.java | 11 + .../fundamental/BusinessSegmentItem.java | 9 + .../fundamental/BusinessSegments.java | 13 + .../BusinessSegmentsHistoricalItem.java | 15 + .../fundamental/BusinessSegmentsHistory.java | 7 + .../BusinessSegmentsHistoryOptions.java | 11 + .../fundamental/FinancialReportSnapshot.java | 49 ++ .../FinancialReportSnapshotOptions.java | 13 + .../fundamental/FundamentalContext.java | 48 ++ .../fundamental/IndustryPeerNode.java | 21 + .../fundamental/IndustryPeersOptions.java | 11 + .../fundamental/IndustryPeersResponse.java | 9 + .../fundamental/IndustryPeersTop.java | 9 + .../fundamental/IndustryRankGroup.java | 7 + .../fundamental/IndustryRankItem.java | 21 + .../fundamental/IndustryRankOptions.java | 13 + .../fundamental/IndustryRankResponse.java | 7 + .../InstitutionRatingViewItem.java | 19 + .../fundamental/InstitutionRatingViews.java | 7 + .../fundamental/SnapshotForecastMetric.java | 13 + .../fundamental/SnapshotReportedMetric.java | 9 + java/src/fundamental_context.rs | 159 ++++ java/src/init.rs | 17 + java/src/types/classes.rs | 217 +++++ nodejs/index.d.ts | 164 +++- nodejs/index.js | 253 +++++- nodejs/src/fundamental/context.rs | 110 +++ nodejs/src/fundamental/types.rs | 363 +++++++++ python/pysrc/longbridge/openapi.pyi | 326 ++++++++ python/src/fundamental/context.rs | 101 +++ python/src/fundamental/context_async.rs | 133 +++ python/src/fundamental/types.rs | 363 +++++++++ 41 files changed, 4254 insertions(+), 39 deletions(-) create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentHistoryItem.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentItem.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegments.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoricalItem.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistory.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoryOptions.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshot.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshotOptions.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeerNode.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersOptions.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersResponse.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersTop.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankGroup.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankItem.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankOptions.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankResponse.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViewItem.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViews.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotForecastMetric.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotReportedMetric.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 458d650f9..7e2290229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `industry_peers` — GET `/v1/quote/industries/peers`: recursive industry peer chain; accepts both symbol-style (`AAPL.US`) and raw counter IDs (`BK/US/123`). - `financial_report_snapshot` — GET `/v1/quote/financials/earnings-snapshot`: earnings snapshot with forecast (revenue/EBIT/EPS) and reported (P&L, cash-flows, balance-sheet ratios) metrics. - All new methods are also available on `FundamentalContextSync` (blocking API). +- **Python, Node.js, Java, C, C++:** All six new `FundamentalContext` methods ported to every non-Rust SDK, including: + - New response types: `BusinessSegmentItem`, `BusinessSegments`, `BusinessSegmentHistoryItem`, `BusinessSegmentsHistoricalItem`, `BusinessSegmentsHistory`, `InstitutionRatingViewItem`, `InstitutionRatingViews`, `IndustryRankItem`, `IndustryRankGroup`, `IndustryRankResponse`, `IndustryPeersTop`, `IndustryPeerNode` (with `next_json` serialising the recursive child list), `IndustryPeersResponse`, `SnapshotForecastMetric`, `SnapshotReportedMetric`, `FinancialReportSnapshot`. + - **Python:** Sync (`FundamentalContext`) and async (`AsyncFundamentalContext`) variants; full type stubs in `openapi.pyi`. + - **Node.js:** `index.d.ts` regenerated with all new types and methods. + - **Java:** JNI bindings, Java source classes, and options POJOs; new methods on `FundamentalContext`. + - **C:** New C structs and functions in `longbridge.h` (regenerated via cbindgen). + - **C++:** New struct types in `types.hpp`, convert functions in `convert.hpp`, method declarations in `fundamental_context.hpp`, and implementations in `fundamental_context.cpp`. # [4.1.0] diff --git a/c/cbindgen.toml b/c/cbindgen.toml index e7edabefc..b4620ed58 100644 --- a/c/cbindgen.toml +++ b/c/cbindgen.toml @@ -279,6 +279,23 @@ cpp_compat = true "CRatingSubIndicatorGroup" = "lb_rating_sub_indicator_group_t" "CRatingCategory" = "lb_rating_category_t" "CStockRatings" = "lb_stock_ratings_t" +# FundamentalContext: new APIs +"CBusinessSegmentItem" = "lb_business_segment_item_t" +"CBusinessSegments" = "lb_business_segments_t" +"CBusinessSegmentHistoryItem" = "lb_business_segment_history_item_t" +"CBusinessSegmentsHistoricalItem" = "lb_business_segments_historical_item_t" +"CBusinessSegmentsHistory" = "lb_business_segments_history_t" +"CInstitutionRatingViewItem" = "lb_institution_rating_view_item_t" +"CInstitutionRatingViews" = "lb_institution_rating_views_t" +"CIndustryRankItem" = "lb_industry_rank_item_t" +"CIndustryRankGroup" = "lb_industry_rank_group_t" +"CIndustryRankResponse" = "lb_industry_rank_response_t" +"CIndustryPeersTop" = "lb_industry_peers_top_t" +"CIndustryPeerNode" = "lb_industry_peer_node_t" +"CIndustryPeersResponse" = "lb_industry_peers_response_t" +"CSnapshotForecastMetric" = "lb_snapshot_forecast_metric_t" +"CSnapshotReportedMetric" = "lb_snapshot_reported_metric_t" +"CFinancialReportSnapshot" = "lb_financial_report_snapshot_t" # QuoteContext extensions "CShortPosition" = "lb_short_position_t" "CShortPositionsResponse" = "lb_short_positions_response_t" @@ -362,6 +379,12 @@ include = [ "CProfessional", "CExecutiveGroup", "CExecutiveList", "CRecentBuybacks", "CBuybackHistoryItem", "CBuybackRatios", "CBuybackData", "CRatingLeafIndicator", "CRatingIndicator", "CRatingSubIndicatorGroup", "CRatingCategory", "CStockRatings", + "CBusinessSegmentItem", "CBusinessSegments", + "CBusinessSegmentHistoryItem", "CBusinessSegmentsHistoricalItem", "CBusinessSegmentsHistory", + "CInstitutionRatingViewItem", "CInstitutionRatingViews", + "CIndustryRankItem", "CIndustryRankGroup", "CIndustryRankResponse", + "CIndustryPeersTop", "CIndustryPeerNode", "CIndustryPeersResponse", + "CSnapshotForecastMetric", "CSnapshotReportedMetric", "CFinancialReportSnapshot", "CCalendarDataKv", "CCalendarEventInfo", "CCalendarDateGroup", "CCalendarEventsResponse", "CExchangeRate", "CExchangeRates", # AlertContext enums + data types diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index 0d02bd504..0f133cdd9 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -6566,6 +6566,416 @@ typedef struct lb_stock_ratings_t { uintptr_t num_ratings; } lb_stock_ratings_t; +/** + * One business segment item (latest snapshot). + */ +typedef struct lb_business_segment_item_t { + /** + * Segment name. + */ + const char *name; + /** + * Percentage of total revenue. + */ + const char *percent; +} lb_business_segment_item_t; + +/** + * Business segments response. + */ +typedef struct lb_business_segments_t { + /** + * Report date. + */ + const char *date; + /** + * Total revenue. + */ + const char *total; + /** + * Reporting currency. + */ + const char *currency; + /** + * Pointer to the array of business segment items. + */ + const struct lb_business_segment_item_t *business; + /** + * Number of items in `business`. + */ + uintptr_t num_business; +} lb_business_segments_t; + +/** + * One business/regional segment item in a historical snapshot. + */ +typedef struct lb_business_segment_history_item_t { + /** + * Segment name. + */ + const char *name; + /** + * Percentage of total. + */ + const char *percent; + /** + * Absolute value. + */ + const char *value; +} lb_business_segment_history_item_t; + +/** + * One historical business segments snapshot. + */ +typedef struct lb_business_segments_historical_item_t { + /** + * Report date. + */ + const char *date; + /** + * Total revenue. + */ + const char *total; + /** + * Reporting currency. + */ + const char *currency; + /** + * Pointer to the business segment items. + */ + const struct lb_business_segment_history_item_t *business; + /** + * Number of items in `business`. + */ + uintptr_t num_business; + /** + * Pointer to the regional segment items. + */ + const struct lb_business_segment_history_item_t *regionals; + /** + * Number of items in `regionals`. + */ + uintptr_t num_regionals; +} lb_business_segments_historical_item_t; + +/** + * Business segments history response. + */ +typedef struct lb_business_segments_history_t { + /** + * Pointer to the historical snapshots. + */ + const struct lb_business_segments_historical_item_t *historical; + /** + * Number of items in `historical`. + */ + uintptr_t num_historical; +} lb_business_segments_history_t; + +/** + * One historical rating distribution snapshot. + */ +typedef struct lb_institution_rating_view_item_t { + /** + * Date as unix timestamp string. + */ + const char *date; + /** + * Number of Buy ratings. + */ + const char *buy; + /** + * Number of Outperform ratings. + */ + const char *over; + /** + * Number of Hold ratings. + */ + const char *hold; + /** + * Number of Underperform ratings. + */ + const char *under; + /** + * Number of Sell ratings. + */ + const char *sell; + /** + * Total analyst count. + */ + const char *total; +} lb_institution_rating_view_item_t; + +/** + * Institution rating views response. + */ +typedef struct lb_institution_rating_views_t { + /** + * Pointer to the rating view items. + */ + const struct lb_institution_rating_view_item_t *elist; + /** + * Number of items in `elist`. + */ + uintptr_t num_elist; +} lb_institution_rating_views_t; + +/** + * One ranked industry item. + */ +typedef struct lb_industry_rank_item_t { + /** + * Industry / sector name. + */ + const char *name; + /** + * Counter ID of the industry. + */ + const char *counter_id; + /** + * Change percentage. + */ + const char *chg; + /** + * Name of the leading stock. + */ + const char *leading_name; + /** + * Ticker of the leading stock. + */ + const char *leading_ticker; + /** + * Change percentage of the leading stock. + */ + const char *leading_chg; + /** + * Value label name. + */ + const char *value_name; + /** + * Value data. + */ + const char *value_data; +} lb_industry_rank_item_t; + +/** + * A group of ranked industry items. + */ +typedef struct lb_industry_rank_group_t { + /** + * Pointer to the items in this group. + */ + const struct lb_industry_rank_item_t *lists; + /** + * Number of items in `lists`. + */ + uintptr_t num_lists; +} lb_industry_rank_group_t; + +/** + * Industry rank response. + */ +typedef struct lb_industry_rank_response_t { + /** + * Pointer to the grouped rank items. + */ + const struct lb_industry_rank_group_t *items; + /** + * Number of items in `items`. + */ + uintptr_t num_items; +} lb_industry_rank_response_t; + +/** + * Top-level industry info in the peers response. + */ +typedef struct lb_industry_peers_top_t { + /** + * Industry name. + */ + const char *name; + /** + * Market code. + */ + const char *market; +} lb_industry_peers_top_t; + +/** + * A node in the recursive industry peer chain. + * + * `next_json` contains the child nodes serialised as a JSON string. + */ +typedef struct lb_industry_peer_node_t { + /** + * Node name. + */ + const char *name; + /** + * Counter ID. + */ + const char *counter_id; + /** + * Number of stocks in this node. + */ + int32_t stock_num; + /** + * Change percentage. + */ + const char *chg; + /** + * Year-to-date change. + */ + const char *ytd_chg; + /** + * Child nodes as a JSON string. + */ + const char *next_json; +} lb_industry_peer_node_t; + +/** + * Industry peers response. + */ +typedef struct lb_industry_peers_response_t { + /** + * Top-level industry node info. + */ + struct lb_industry_peers_top_t top; + /** + * Root peer chain node, or null if absent. + */ + const struct lb_industry_peer_node_t *chain; +} lb_industry_peers_response_t; + +/** + * A forecast metric in the financial report snapshot. + */ +typedef struct lb_snapshot_forecast_metric_t { + /** + * Actual value. + */ + const char *value; + /** + * Year-over-year change. + */ + const char *yoy; + /** + * Beat/miss description. + */ + const char *cmp_desc; + /** + * Consensus estimate value. + */ + const char *est_value; +} lb_snapshot_forecast_metric_t; + +/** + * A reported metric in the financial report snapshot. + */ +typedef struct lb_snapshot_reported_metric_t { + /** + * Actual value. + */ + const char *value; + /** + * Year-over-year change. + */ + const char *yoy; +} lb_snapshot_reported_metric_t; + +/** + * Financial report snapshot response. + */ +typedef struct lb_financial_report_snapshot_t { + /** + * Company name. + */ + const char *name; + /** + * Ticker code. + */ + const char *ticker; + /** + * Fiscal period start date. + */ + const char *fp_start; + /** + * Fiscal period end date. + */ + const char *fp_end; + /** + * Reporting currency. + */ + const char *currency; + /** + * Report description. + */ + const char *report_desc; + /** + * Forecast revenue, or null. + */ + const struct lb_snapshot_forecast_metric_t *fo_revenue; + /** + * Forecast EBIT, or null. + */ + const struct lb_snapshot_forecast_metric_t *fo_ebit; + /** + * Forecast EPS, or null. + */ + const struct lb_snapshot_forecast_metric_t *fo_eps; + /** + * Reported revenue, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_revenue; + /** + * Reported net profit, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_profit; + /** + * Reported operating cash flow, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_operate_cash; + /** + * Reported investing cash flow, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_invest_cash; + /** + * Reported financing cash flow, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_finance_cash; + /** + * Reported total assets, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_total_assets; + /** + * Reported total liabilities, or null. + */ + const struct lb_snapshot_reported_metric_t *fr_total_liability; + /** + * ROE TTM. + */ + const char *fr_roe_ttm; + /** + * Profit margin. + */ + const char *fr_profit_margin; + /** + * Profit margin TTM. + */ + const char *fr_profit_margin_ttm; + /** + * Asset turnover TTM. + */ + const char *fr_asset_turn_ttm; + /** + * Leverage TTM. + */ + const char *fr_leverage_ttm; + /** + * Debt-to-assets ratio. + */ + const char *fr_debt_assets_ratio; +} lb_financial_report_snapshot_t; + /** * A key-value pair carrying calendar data fields. */ @@ -8484,6 +8894,73 @@ void lb_fundamental_context_ratings(const struct lb_fundamental_context_t *ctx, lb_async_callback_t callback, void *userdata); +/** + * Get business segment breakdowns. Returns `CBusinessSegments`. + */ +void lb_fundamental_context_business_segments(const struct lb_fundamental_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get historical business segment breakdowns. Returns + * `CBusinessSegmentsHistory`. + * + * Pass `NULL` for `report` or `cate` to omit those parameters. + */ +void lb_fundamental_context_business_segments_history(const struct lb_fundamental_context_t *ctx, + const char *symbol, + const char *report, + const char *cate, + lb_async_callback_t callback, + void *userdata); + +/** + * Get historical institutional rating views. Returns + * `CInstitutionRatingViews`. + */ +void lb_fundamental_context_institution_rating_views(const struct lb_fundamental_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get industry rank for a market. Returns `CIndustryRankResponse`. + */ +void lb_fundamental_context_industry_rank(const struct lb_fundamental_context_t *ctx, + const char *market, + const char *indicator, + const char *sort_type, + uint32_t limit, + lb_async_callback_t callback, + void *userdata); + +/** + * Get industry peer chain. Returns `CIndustryPeersResponse`. + * + * Pass `NULL` for `industry_id` to omit it. + */ +void lb_fundamental_context_industry_peers(const struct lb_fundamental_context_t *ctx, + const char *counter_id, + const char *market, + const char *industry_id, + lb_async_callback_t callback, + void *userdata); + +/** + * Get financial report snapshot. Returns `CFinancialReportSnapshot`. + * + * Pass `NULL` for optional parameters to omit them. + * `fiscal_year` is ignored when 0. + */ +void lb_fundamental_context_financial_report_snapshot(const struct lb_fundamental_context_t *ctx, + const char *symbol, + const char *report, + int32_t fiscal_year, + const char *fiscal_period, + lb_async_callback_t callback, + void *userdata); + /** * Create a HTTP client using API Key authentication * diff --git a/c/src/fundamental_context/context.rs b/c/src/fundamental_context/context.rs index bb6801461..65c69f90b 100644 --- a/c/src/fundamental_context/context.rs +++ b/c/src/fundamental_context/context.rs @@ -405,3 +405,200 @@ pub unsafe extern "C" fn lb_fundamental_context_ratings( Ok(resp) }); } + +/// Get business segment breakdowns. Returns `CBusinessSegments`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_fundamental_context_business_segments( + ctx: *const CFundamentalContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new(CBusinessSegmentsOwned::from( + ctx_inner.business_segments(symbol).await?, + )); + Ok(resp) + }); +} + +/// Get historical business segment breakdowns. Returns +/// `CBusinessSegmentsHistory`. +/// +/// Pass `NULL` for `report` or `cate` to omit those parameters. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_fundamental_context_business_segments_history( + ctx: *const CFundamentalContext, + symbol: *const c_char, + report: *const c_char, + cate: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + let report_str = if report.is_null() { + None + } else { + Some(cstr_to_rust(report)) + }; + let cate_opt = if cate.is_null() { + None + } else { + Some(cstr_to_rust(cate)) + }; + let report_static: Option<&'static str> = match report_str.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + execute_async(callback, ctx, userdata, async move { + let resp: CCow = + CCow::new(CBusinessSegmentsHistoryOwned::from( + ctx_inner + .business_segments_history(symbol, report_static, cate_opt) + .await?, + )); + Ok(resp) + }); +} + +/// Get historical institutional rating views. Returns +/// `CInstitutionRatingViews`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_fundamental_context_institution_rating_views( + ctx: *const CFundamentalContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new( + CInstitutionRatingViewsOwned::from(ctx_inner.institution_rating_views(symbol).await?), + ); + Ok(resp) + }); +} + +/// Get industry rank for a market. Returns `CIndustryRankResponse`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_fundamental_context_industry_rank( + ctx: *const CFundamentalContext, + market: *const c_char, + indicator: *const c_char, + sort_type: *const c_char, + limit: u32, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let market = cstr_to_rust(market); + let indicator = cstr_to_rust(indicator); + let sort_type = cstr_to_rust(sort_type); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new(CIndustryRankResponseOwned::from( + ctx_inner + .industry_rank(market, indicator, sort_type, limit) + .await?, + )); + Ok(resp) + }); +} + +/// Get industry peer chain. Returns `CIndustryPeersResponse`. +/// +/// Pass `NULL` for `industry_id` to omit it. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_fundamental_context_industry_peers( + ctx: *const CFundamentalContext, + counter_id: *const c_char, + market: *const c_char, + industry_id: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let counter_id = cstr_to_rust(counter_id); + let market = cstr_to_rust(market); + let industry_id_opt = if industry_id.is_null() { + None + } else { + Some(cstr_to_rust(industry_id)) + }; + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new(CIndustryPeersResponseOwned::from( + ctx_inner + .industry_peers(counter_id, market, industry_id_opt) + .await?, + )); + Ok(resp) + }); +} + +/// Get financial report snapshot. Returns `CFinancialReportSnapshot`. +/// +/// Pass `NULL` for optional parameters to omit them. +/// `fiscal_year` is ignored when 0. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_fundamental_context_financial_report_snapshot( + ctx: *const CFundamentalContext, + symbol: *const c_char, + report: *const c_char, + fiscal_year: i32, + fiscal_period: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + let report_str = if report.is_null() { + None + } else { + Some(cstr_to_rust(report)) + }; + let fiscal_year_opt = if fiscal_year == 0 { + None + } else { + Some(fiscal_year) + }; + let fiscal_period_str = if fiscal_period.is_null() { + None + } else { + Some(cstr_to_rust(fiscal_period)) + }; + let report_static: Option<&'static str> = match report_str.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + let fiscal_period_static: Option<&'static str> = match fiscal_period_str.as_deref() { + Some("q1") => Some("q1"), + Some("q2") => Some("q2"), + Some("q3") => Some("q3"), + Some("q4") => Some("q4"), + Some("fy") => Some("fy"), + Some("h1") => Some("h1"), + Some("h2") => Some("h2"), + _ => None, + }; + execute_async(callback, ctx, userdata, async move { + let resp: CCow = + CCow::new(CFinancialReportSnapshotOwned::from( + ctx_inner + .financial_report_snapshot( + symbol, + report_static, + fiscal_year_opt, + fiscal_period_static, + ) + .await?, + )); + Ok(resp) + }); +} diff --git a/c/src/fundamental_context/types.rs b/c/src/fundamental_context/types.rs index 90cdb0d72..165e7c21a 100644 --- a/c/src/fundamental_context/types.rs +++ b/c/src/fundamental_context/types.rs @@ -2842,3 +2842,763 @@ impl ToFFI for CStockRatingsOwned { } } } + +// ── business_segments ───────────────────────────────────────────── + +/// One business segment item (latest snapshot). +#[repr(C)] +pub struct CBusinessSegmentItem { + /// Segment name. + pub name: *const c_char, + /// Percentage of total revenue. + pub percent: *const c_char, +} + +pub(crate) struct CBusinessSegmentItemOwned { + name: CString, + percent: CString, +} + +impl From for CBusinessSegmentItemOwned { + fn from(v: longbridge::fundamental::BusinessSegmentItem) -> Self { + Self { + name: v.name.into(), + percent: v.percent.into(), + } + } +} + +impl ToFFI for CBusinessSegmentItemOwned { + type FFIType = CBusinessSegmentItem; + fn to_ffi_type(&self) -> Self::FFIType { + CBusinessSegmentItem { + name: self.name.to_ffi_type(), + percent: self.percent.to_ffi_type(), + } + } +} + +/// Business segments response. +#[repr(C)] +pub struct CBusinessSegments { + /// Report date. + pub date: *const c_char, + /// Total revenue. + pub total: *const c_char, + /// Reporting currency. + pub currency: *const c_char, + /// Pointer to the array of business segment items. + pub business: *const CBusinessSegmentItem, + /// Number of items in `business`. + pub num_business: usize, +} + +pub(crate) struct CBusinessSegmentsOwned { + date: CString, + total: CString, + currency: CString, + business: CVec, +} + +impl From for CBusinessSegmentsOwned { + fn from(v: longbridge::fundamental::BusinessSegments) -> Self { + Self { + date: v.date.into(), + total: v.total.into(), + currency: v.currency.into(), + business: v.business.into(), + } + } +} + +impl ToFFI for CBusinessSegmentsOwned { + type FFIType = CBusinessSegments; + fn to_ffi_type(&self) -> Self::FFIType { + CBusinessSegments { + date: self.date.to_ffi_type(), + total: self.total.to_ffi_type(), + currency: self.currency.to_ffi_type(), + business: self.business.to_ffi_type(), + num_business: self.business.len(), + } + } +} + +/// One business/regional segment item in a historical snapshot. +#[repr(C)] +pub struct CBusinessSegmentHistoryItem { + /// Segment name. + pub name: *const c_char, + /// Percentage of total. + pub percent: *const c_char, + /// Absolute value. + pub value: *const c_char, +} + +pub(crate) struct CBusinessSegmentHistoryItemOwned { + name: CString, + percent: CString, + value: CString, +} + +impl From + for CBusinessSegmentHistoryItemOwned +{ + fn from(v: longbridge::fundamental::BusinessSegmentHistoryItem) -> Self { + Self { + name: v.name.into(), + percent: v.percent.into(), + value: v.value.into(), + } + } +} + +impl ToFFI for CBusinessSegmentHistoryItemOwned { + type FFIType = CBusinessSegmentHistoryItem; + fn to_ffi_type(&self) -> Self::FFIType { + CBusinessSegmentHistoryItem { + name: self.name.to_ffi_type(), + percent: self.percent.to_ffi_type(), + value: self.value.to_ffi_type(), + } + } +} + +/// One historical business segments snapshot. +#[repr(C)] +pub struct CBusinessSegmentsHistoricalItem { + /// Report date. + pub date: *const c_char, + /// Total revenue. + pub total: *const c_char, + /// Reporting currency. + pub currency: *const c_char, + /// Pointer to the business segment items. + pub business: *const CBusinessSegmentHistoryItem, + /// Number of items in `business`. + pub num_business: usize, + /// Pointer to the regional segment items. + pub regionals: *const CBusinessSegmentHistoryItem, + /// Number of items in `regionals`. + pub num_regionals: usize, +} + +pub(crate) struct CBusinessSegmentsHistoricalItemOwned { + date: CString, + total: CString, + currency: CString, + business: CVec, + regionals: CVec, +} + +impl From + for CBusinessSegmentsHistoricalItemOwned +{ + fn from(v: longbridge::fundamental::BusinessSegmentsHistoricalItem) -> Self { + Self { + date: v.date.into(), + total: v.total.into(), + currency: v.currency.into(), + business: v.business.into(), + regionals: v.regionals.into(), + } + } +} + +impl ToFFI for CBusinessSegmentsHistoricalItemOwned { + type FFIType = CBusinessSegmentsHistoricalItem; + fn to_ffi_type(&self) -> Self::FFIType { + CBusinessSegmentsHistoricalItem { + date: self.date.to_ffi_type(), + total: self.total.to_ffi_type(), + currency: self.currency.to_ffi_type(), + business: self.business.to_ffi_type(), + num_business: self.business.len(), + regionals: self.regionals.to_ffi_type(), + num_regionals: self.regionals.len(), + } + } +} + +/// Business segments history response. +#[repr(C)] +pub struct CBusinessSegmentsHistory { + /// Pointer to the historical snapshots. + pub historical: *const CBusinessSegmentsHistoricalItem, + /// Number of items in `historical`. + pub num_historical: usize, +} + +pub(crate) struct CBusinessSegmentsHistoryOwned { + historical: CVec, +} + +impl From for CBusinessSegmentsHistoryOwned { + fn from(v: longbridge::fundamental::BusinessSegmentsHistory) -> Self { + Self { + historical: v.historical.into(), + } + } +} + +impl ToFFI for CBusinessSegmentsHistoryOwned { + type FFIType = CBusinessSegmentsHistory; + fn to_ffi_type(&self) -> Self::FFIType { + CBusinessSegmentsHistory { + historical: self.historical.to_ffi_type(), + num_historical: self.historical.len(), + } + } +} + +// ── institution_rating_views ────────────────────────────────────── + +/// One historical rating distribution snapshot. +#[repr(C)] +pub struct CInstitutionRatingViewItem { + /// Date as unix timestamp string. + pub date: *const c_char, + /// Number of Buy ratings. + pub buy: *const c_char, + /// Number of Outperform ratings. + pub over: *const c_char, + /// Number of Hold ratings. + pub hold: *const c_char, + /// Number of Underperform ratings. + pub under: *const c_char, + /// Number of Sell ratings. + pub sell: *const c_char, + /// Total analyst count. + pub total: *const c_char, +} + +pub(crate) struct CInstitutionRatingViewItemOwned { + date: CString, + buy: CString, + over: CString, + hold: CString, + under: CString, + sell: CString, + total: CString, +} + +impl From for CInstitutionRatingViewItemOwned { + fn from(v: longbridge::fundamental::InstitutionRatingViewItem) -> Self { + Self { + date: v.date.into(), + buy: v.buy.into(), + over: v.over.into(), + hold: v.hold.into(), + under: v.under.into(), + sell: v.sell.into(), + total: v.total.into(), + } + } +} + +impl ToFFI for CInstitutionRatingViewItemOwned { + type FFIType = CInstitutionRatingViewItem; + fn to_ffi_type(&self) -> Self::FFIType { + CInstitutionRatingViewItem { + date: self.date.to_ffi_type(), + buy: self.buy.to_ffi_type(), + over: self.over.to_ffi_type(), + hold: self.hold.to_ffi_type(), + under: self.under.to_ffi_type(), + sell: self.sell.to_ffi_type(), + total: self.total.to_ffi_type(), + } + } +} + +/// Institution rating views response. +#[repr(C)] +pub struct CInstitutionRatingViews { + /// Pointer to the rating view items. + pub elist: *const CInstitutionRatingViewItem, + /// Number of items in `elist`. + pub num_elist: usize, +} + +pub(crate) struct CInstitutionRatingViewsOwned { + elist: CVec, +} + +impl From for CInstitutionRatingViewsOwned { + fn from(v: longbridge::fundamental::InstitutionRatingViews) -> Self { + Self { + elist: v.elist.into(), + } + } +} + +impl ToFFI for CInstitutionRatingViewsOwned { + type FFIType = CInstitutionRatingViews; + fn to_ffi_type(&self) -> Self::FFIType { + CInstitutionRatingViews { + elist: self.elist.to_ffi_type(), + num_elist: self.elist.len(), + } + } +} + +// ── industry_rank ───────────────────────────────────────────────── + +/// One ranked industry item. +#[repr(C)] +pub struct CIndustryRankItem { + /// Industry / sector name. + pub name: *const c_char, + /// Counter ID of the industry. + pub counter_id: *const c_char, + /// Change percentage. + pub chg: *const c_char, + /// Name of the leading stock. + pub leading_name: *const c_char, + /// Ticker of the leading stock. + pub leading_ticker: *const c_char, + /// Change percentage of the leading stock. + pub leading_chg: *const c_char, + /// Value label name. + pub value_name: *const c_char, + /// Value data. + pub value_data: *const c_char, +} + +pub(crate) struct CIndustryRankItemOwned { + name: CString, + counter_id: CString, + chg: CString, + leading_name: CString, + leading_ticker: CString, + leading_chg: CString, + value_name: CString, + value_data: CString, +} + +impl From for CIndustryRankItemOwned { + fn from(v: longbridge::fundamental::IndustryRankItem) -> Self { + Self { + name: v.name.into(), + counter_id: v.counter_id.into(), + chg: v.chg.into(), + leading_name: v.leading_name.into(), + leading_ticker: v.leading_ticker.into(), + leading_chg: v.leading_chg.into(), + value_name: v.value_name.into(), + value_data: v.value_data.into(), + } + } +} + +impl ToFFI for CIndustryRankItemOwned { + type FFIType = CIndustryRankItem; + fn to_ffi_type(&self) -> Self::FFIType { + CIndustryRankItem { + name: self.name.to_ffi_type(), + counter_id: self.counter_id.to_ffi_type(), + chg: self.chg.to_ffi_type(), + leading_name: self.leading_name.to_ffi_type(), + leading_ticker: self.leading_ticker.to_ffi_type(), + leading_chg: self.leading_chg.to_ffi_type(), + value_name: self.value_name.to_ffi_type(), + value_data: self.value_data.to_ffi_type(), + } + } +} + +/// A group of ranked industry items. +#[repr(C)] +pub struct CIndustryRankGroup { + /// Pointer to the items in this group. + pub lists: *const CIndustryRankItem, + /// Number of items in `lists`. + pub num_lists: usize, +} + +pub(crate) struct CIndustryRankGroupOwned { + lists: CVec, +} + +impl From for CIndustryRankGroupOwned { + fn from(v: longbridge::fundamental::IndustryRankGroup) -> Self { + Self { + lists: v.lists.into(), + } + } +} + +impl ToFFI for CIndustryRankGroupOwned { + type FFIType = CIndustryRankGroup; + fn to_ffi_type(&self) -> Self::FFIType { + CIndustryRankGroup { + lists: self.lists.to_ffi_type(), + num_lists: self.lists.len(), + } + } +} + +/// Industry rank response. +#[repr(C)] +pub struct CIndustryRankResponse { + /// Pointer to the grouped rank items. + pub items: *const CIndustryRankGroup, + /// Number of items in `items`. + pub num_items: usize, +} + +pub(crate) struct CIndustryRankResponseOwned { + items: CVec, +} + +impl From for CIndustryRankResponseOwned { + fn from(v: longbridge::fundamental::IndustryRankResponse) -> Self { + Self { + items: v.items.into(), + } + } +} + +impl ToFFI for CIndustryRankResponseOwned { + type FFIType = CIndustryRankResponse; + fn to_ffi_type(&self) -> Self::FFIType { + CIndustryRankResponse { + items: self.items.to_ffi_type(), + num_items: self.items.len(), + } + } +} + +// ── industry_peers ──────────────────────────────────────────────── + +/// Top-level industry info in the peers response. +#[repr(C)] +pub struct CIndustryPeersTop { + /// Industry name. + pub name: *const c_char, + /// Market code. + pub market: *const c_char, +} + +pub(crate) struct CIndustryPeersTopOwned { + name: CString, + market: CString, +} + +impl From for CIndustryPeersTopOwned { + fn from(v: longbridge::fundamental::IndustryPeersTop) -> Self { + Self { + name: v.name.into(), + market: v.market.into(), + } + } +} + +impl ToFFI for CIndustryPeersTopOwned { + type FFIType = CIndustryPeersTop; + fn to_ffi_type(&self) -> Self::FFIType { + CIndustryPeersTop { + name: self.name.to_ffi_type(), + market: self.market.to_ffi_type(), + } + } +} + +/// A node in the recursive industry peer chain. +/// +/// `next_json` contains the child nodes serialised as a JSON string. +#[repr(C)] +pub struct CIndustryPeerNode { + /// Node name. + pub name: *const c_char, + /// Counter ID. + pub counter_id: *const c_char, + /// Number of stocks in this node. + pub stock_num: i32, + /// Change percentage. + pub chg: *const c_char, + /// Year-to-date change. + pub ytd_chg: *const c_char, + /// Child nodes as a JSON string. + pub next_json: *const c_char, +} + +pub(crate) struct CIndustryPeerNodeOwned { + name: CString, + counter_id: CString, + stock_num: i32, + chg: CString, + ytd_chg: CString, + next_json: CString, +} + +impl From for CIndustryPeerNodeOwned { + fn from(v: longbridge::fundamental::IndustryPeerNode) -> Self { + Self { + name: v.name.into(), + counter_id: v.counter_id.into(), + stock_num: v.stock_num, + chg: v.chg.into(), + ytd_chg: v.ytd_chg.into(), + next_json: serde_json::to_string(&v.next).unwrap_or_default().into(), + } + } +} + +impl ToFFI for CIndustryPeerNodeOwned { + type FFIType = CIndustryPeerNode; + fn to_ffi_type(&self) -> Self::FFIType { + CIndustryPeerNode { + name: self.name.to_ffi_type(), + counter_id: self.counter_id.to_ffi_type(), + stock_num: self.stock_num, + chg: self.chg.to_ffi_type(), + ytd_chg: self.ytd_chg.to_ffi_type(), + next_json: self.next_json.to_ffi_type(), + } + } +} + +/// Industry peers response. +#[repr(C)] +pub struct CIndustryPeersResponse { + /// Top-level industry node info. + pub top: CIndustryPeersTop, + /// Root peer chain node, or null if absent. + pub chain: *const CIndustryPeerNode, +} + +pub(crate) struct CIndustryPeersResponseOwned { + top: CIndustryPeersTopOwned, + chain: COption, +} + +impl From for CIndustryPeersResponseOwned { + fn from(v: longbridge::fundamental::IndustryPeersResponse) -> Self { + Self { + top: v.top.into(), + chain: v.chain.into(), + } + } +} + +impl ToFFI for CIndustryPeersResponseOwned { + type FFIType = CIndustryPeersResponse; + fn to_ffi_type(&self) -> Self::FFIType { + CIndustryPeersResponse { + top: self.top.to_ffi_type(), + chain: self.chain.to_ffi_type(), + } + } +} + +// ── financial_report_snapshot ───────────────────────────────────── + +/// A forecast metric in the financial report snapshot. +#[repr(C)] +pub struct CSnapshotForecastMetric { + /// Actual value. + pub value: *const c_char, + /// Year-over-year change. + pub yoy: *const c_char, + /// Beat/miss description. + pub cmp_desc: *const c_char, + /// Consensus estimate value. + pub est_value: *const c_char, +} + +pub(crate) struct CSnapshotForecastMetricOwned { + value: CString, + yoy: CString, + cmp_desc: CString, + est_value: CString, +} + +impl From for CSnapshotForecastMetricOwned { + fn from(v: longbridge::fundamental::SnapshotForecastMetric) -> Self { + Self { + value: v.value.into(), + yoy: v.yoy.into(), + cmp_desc: v.cmp_desc.into(), + est_value: v.est_value.into(), + } + } +} + +impl ToFFI for CSnapshotForecastMetricOwned { + type FFIType = CSnapshotForecastMetric; + fn to_ffi_type(&self) -> Self::FFIType { + CSnapshotForecastMetric { + value: self.value.to_ffi_type(), + yoy: self.yoy.to_ffi_type(), + cmp_desc: self.cmp_desc.to_ffi_type(), + est_value: self.est_value.to_ffi_type(), + } + } +} + +/// A reported metric in the financial report snapshot. +#[repr(C)] +pub struct CSnapshotReportedMetric { + /// Actual value. + pub value: *const c_char, + /// Year-over-year change. + pub yoy: *const c_char, +} + +pub(crate) struct CSnapshotReportedMetricOwned { + value: CString, + yoy: CString, +} + +impl From for CSnapshotReportedMetricOwned { + fn from(v: longbridge::fundamental::SnapshotReportedMetric) -> Self { + Self { + value: v.value.into(), + yoy: v.yoy.into(), + } + } +} + +impl ToFFI for CSnapshotReportedMetricOwned { + type FFIType = CSnapshotReportedMetric; + fn to_ffi_type(&self) -> Self::FFIType { + CSnapshotReportedMetric { + value: self.value.to_ffi_type(), + yoy: self.yoy.to_ffi_type(), + } + } +} + +/// Financial report snapshot response. +#[repr(C)] +pub struct CFinancialReportSnapshot { + /// Company name. + pub name: *const c_char, + /// Ticker code. + pub ticker: *const c_char, + /// Fiscal period start date. + pub fp_start: *const c_char, + /// Fiscal period end date. + pub fp_end: *const c_char, + /// Reporting currency. + pub currency: *const c_char, + /// Report description. + pub report_desc: *const c_char, + /// Forecast revenue, or null. + pub fo_revenue: *const CSnapshotForecastMetric, + /// Forecast EBIT, or null. + pub fo_ebit: *const CSnapshotForecastMetric, + /// Forecast EPS, or null. + pub fo_eps: *const CSnapshotForecastMetric, + /// Reported revenue, or null. + pub fr_revenue: *const CSnapshotReportedMetric, + /// Reported net profit, or null. + pub fr_profit: *const CSnapshotReportedMetric, + /// Reported operating cash flow, or null. + pub fr_operate_cash: *const CSnapshotReportedMetric, + /// Reported investing cash flow, or null. + pub fr_invest_cash: *const CSnapshotReportedMetric, + /// Reported financing cash flow, or null. + pub fr_finance_cash: *const CSnapshotReportedMetric, + /// Reported total assets, or null. + pub fr_total_assets: *const CSnapshotReportedMetric, + /// Reported total liabilities, or null. + pub fr_total_liability: *const CSnapshotReportedMetric, + /// ROE TTM. + pub fr_roe_ttm: *const c_char, + /// Profit margin. + pub fr_profit_margin: *const c_char, + /// Profit margin TTM. + pub fr_profit_margin_ttm: *const c_char, + /// Asset turnover TTM. + pub fr_asset_turn_ttm: *const c_char, + /// Leverage TTM. + pub fr_leverage_ttm: *const c_char, + /// Debt-to-assets ratio. + pub fr_debt_assets_ratio: *const c_char, +} + +pub(crate) struct CFinancialReportSnapshotOwned { + name: CString, + ticker: CString, + fp_start: CString, + fp_end: CString, + currency: CString, + report_desc: CString, + fo_revenue: COption, + fo_ebit: COption, + fo_eps: COption, + fr_revenue: COption, + fr_profit: COption, + fr_operate_cash: COption, + fr_invest_cash: COption, + fr_finance_cash: COption, + fr_total_assets: COption, + fr_total_liability: COption, + fr_roe_ttm: CString, + fr_profit_margin: CString, + fr_profit_margin_ttm: CString, + fr_asset_turn_ttm: CString, + fr_leverage_ttm: CString, + fr_debt_assets_ratio: CString, +} + +impl From for CFinancialReportSnapshotOwned { + fn from(v: longbridge::fundamental::FinancialReportSnapshot) -> Self { + Self { + name: v.name.into(), + ticker: v.ticker.into(), + fp_start: v.fp_start.into(), + fp_end: v.fp_end.into(), + currency: v.currency.into(), + report_desc: v.report_desc.into(), + fo_revenue: v.fo_revenue.into(), + fo_ebit: v.fo_ebit.into(), + fo_eps: v.fo_eps.into(), + fr_revenue: v.fr_revenue.into(), + fr_profit: v.fr_profit.into(), + fr_operate_cash: v.fr_operate_cash.into(), + fr_invest_cash: v.fr_invest_cash.into(), + fr_finance_cash: v.fr_finance_cash.into(), + fr_total_assets: v.fr_total_assets.into(), + fr_total_liability: v.fr_total_liability.into(), + fr_roe_ttm: v.fr_roe_ttm.into(), + fr_profit_margin: v.fr_profit_margin.into(), + fr_profit_margin_ttm: v.fr_profit_margin_ttm.into(), + fr_asset_turn_ttm: v.fr_asset_turn_ttm.into(), + fr_leverage_ttm: v.fr_leverage_ttm.into(), + fr_debt_assets_ratio: v.fr_debt_assets_ratio.into(), + } + } +} + +impl ToFFI for CFinancialReportSnapshotOwned { + type FFIType = CFinancialReportSnapshot; + fn to_ffi_type(&self) -> Self::FFIType { + CFinancialReportSnapshot { + name: self.name.to_ffi_type(), + ticker: self.ticker.to_ffi_type(), + fp_start: self.fp_start.to_ffi_type(), + fp_end: self.fp_end.to_ffi_type(), + currency: self.currency.to_ffi_type(), + report_desc: self.report_desc.to_ffi_type(), + fo_revenue: self.fo_revenue.to_ffi_type(), + fo_ebit: self.fo_ebit.to_ffi_type(), + fo_eps: self.fo_eps.to_ffi_type(), + fr_revenue: self.fr_revenue.to_ffi_type(), + fr_profit: self.fr_profit.to_ffi_type(), + fr_operate_cash: self.fr_operate_cash.to_ffi_type(), + fr_invest_cash: self.fr_invest_cash.to_ffi_type(), + fr_finance_cash: self.fr_finance_cash.to_ffi_type(), + fr_total_assets: self.fr_total_assets.to_ffi_type(), + fr_total_liability: self.fr_total_liability.to_ffi_type(), + fr_roe_ttm: self.fr_roe_ttm.to_ffi_type(), + fr_profit_margin: self.fr_profit_margin.to_ffi_type(), + fr_profit_margin_ttm: self.fr_profit_margin_ttm.to_ffi_type(), + fr_asset_turn_ttm: self.fr_asset_turn_ttm.to_ffi_type(), + fr_leverage_ttm: self.fr_leverage_ttm.to_ffi_type(), + fr_debt_assets_ratio: self.fr_debt_assets_ratio.to_ffi_type(), + } + } +} diff --git a/cpp/include/fundamental_context.hpp b/cpp/include/fundamental_context.hpp index 5cf9631cc..6636282e9 100644 --- a/cpp/include/fundamental_context.hpp +++ b/cpp/include/fundamental_context.hpp @@ -123,6 +123,44 @@ class FundamentalContext /// Get stock ratings void ratings(const std::string& symbol, AsyncCallback callback) const; + + /// Get business segment breakdowns (latest snapshot) + void business_segments(const std::string& symbol, + AsyncCallback callback) const; + + /// Get historical business segment breakdowns. + /// Pass nullptr for report/cate to omit them. + void business_segments_history(const std::string& symbol, + const char* report, + const char* cate, + AsyncCallback callback) const; + + /// Get historical institutional rating view time-series + void institution_rating_views(const std::string& symbol, + AsyncCallback callback) const; + + /// Get industry rank for a market. + /// indicator: "0"–"7"; sort_type: "0"=asc, "1"=desc + void industry_rank(const std::string& market, + const std::string& indicator, + const std::string& sort_type, + uint32_t limit, + AsyncCallback callback) const; + + /// Get the industry peer chain. + /// Pass nullptr for industry_id to omit it. + void industry_peers(const std::string& counter_id, + const std::string& market, + const char* industry_id, + AsyncCallback callback) const; + + /// Get a financial report snapshot. + /// Pass nullptr for report/fiscal_period; pass 0 for fiscal_year to omit it. + void financial_report_snapshot(const std::string& symbol, + const char* report, + int32_t fiscal_year, + const char* fiscal_period, + AsyncCallback callback) const; }; } // namespace fundamental diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index 6785b41f7..30459bd22 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -2984,6 +2984,159 @@ struct StockRatings std::vector ratings; }; +/// One business segment item (latest snapshot). +struct BusinessSegmentItem +{ + std::string name; + std::string percent; +}; + +/// Business segments response. +struct BusinessSegments +{ + std::string date; + std::string total; + std::string currency; + std::vector business; +}; + +/// One business/regional segment item in a historical snapshot. +struct BusinessSegmentHistoryItem +{ + std::string name; + std::string percent; + std::string value; +}; + +/// One historical business segments snapshot. +struct BusinessSegmentsHistoricalItem +{ + std::string date; + std::string total; + std::string currency; + std::vector business; + std::vector regionals; +}; + +/// Business segments history response. +struct BusinessSegmentsHistory +{ + std::vector historical; +}; + +/// One historical rating distribution snapshot. +struct InstitutionRatingViewItem +{ + std::string date; + std::string buy; + std::string over; + std::string hold; + std::string under; + std::string sell; + std::string total; +}; + +/// Institution rating views response. +struct InstitutionRatingViews +{ + std::vector elist; +}; + +/// One ranked industry item. +struct IndustryRankItem +{ + std::string name; + std::string counter_id; + std::string chg; + std::string leading_name; + std::string leading_ticker; + std::string leading_chg; + std::string value_name; + std::string value_data; +}; + +/// A group of ranked industry items. +struct IndustryRankGroup +{ + std::vector lists; +}; + +/// Industry rank response. +struct IndustryRankResponse +{ + std::vector items; +}; + +/// Top-level industry info in the peers response. +struct IndustryPeersTop +{ + std::string name; + std::string market; +}; + +/// A node in the recursive industry peer chain. +/// +/// next_json contains the child nodes serialised as a JSON string. +struct IndustryPeerNode +{ + std::string name; + std::string counter_id; + int32_t stock_num; + std::string chg; + std::string ytd_chg; + std::string next_json; +}; + +/// Industry peers response. +struct IndustryPeersResponse +{ + IndustryPeersTop top; + std::optional chain; +}; + +/// A forecast metric in the financial report snapshot. +struct SnapshotForecastMetric +{ + std::string value; + std::string yoy; + std::string cmp_desc; + std::string est_value; +}; + +/// A reported metric in the financial report snapshot. +struct SnapshotReportedMetric +{ + std::string value; + std::string yoy; +}; + +/// Financial report snapshot response. +struct FinancialReportSnapshot +{ + std::string name; + std::string ticker; + std::string fp_start; + std::string fp_end; + std::string currency; + std::string report_desc; + std::optional fo_revenue; + std::optional fo_ebit; + std::optional fo_eps; + std::optional fr_revenue; + std::optional fr_profit; + std::optional fr_operate_cash; + std::optional fr_invest_cash; + std::optional fr_finance_cash; + std::optional fr_total_assets; + std::optional fr_total_liability; + std::string fr_roe_ttm; + std::string fr_profit_margin; + std::string fr_profit_margin_ttm; + std::string fr_asset_turn_ttm; + std::string fr_leverage_ttm; + std::string fr_debt_assets_ratio; +}; + } // namespace fundamental namespace alert { diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index da14fba14..19ea00f02 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -2584,6 +2584,98 @@ inline fundamental::StockRatings convert(const lb_stock_ratings_t* r) { r->industry_mean_score, r->industry_median_score, std::move(ratings) }; } +// ── business_segments conversions ──────────────────────────────── +inline fundamental::BusinessSegmentItem convert(const lb_business_segment_item_t* item) { + return { item->name, item->percent }; +} +inline fundamental::BusinessSegments convert(const lb_business_segments_t* r) { + std::vector business; + for (size_t i = 0; i < r->num_business; ++i) business.push_back(convert(&r->business[i])); + return { r->date, r->total, r->currency, std::move(business) }; +} +inline fundamental::BusinessSegmentHistoryItem convert(const lb_business_segment_history_item_t* item) { + return { item->name, item->percent, item->value }; +} +inline fundamental::BusinessSegmentsHistoricalItem convert(const lb_business_segments_historical_item_t* item) { + std::vector business, regionals; + for (size_t i = 0; i < item->num_business; ++i) business.push_back(convert(&item->business[i])); + for (size_t i = 0; i < item->num_regionals; ++i) regionals.push_back(convert(&item->regionals[i])); + return { item->date, item->total, item->currency, std::move(business), std::move(regionals) }; +} +inline fundamental::BusinessSegmentsHistory convert(const lb_business_segments_history_t* r) { + std::vector hist; + for (size_t i = 0; i < r->num_historical; ++i) hist.push_back(convert(&r->historical[i])); + return { std::move(hist) }; +} + +// ── institution_rating_views conversion ────────────────────────── +inline fundamental::InstitutionRatingViewItem convert(const lb_institution_rating_view_item_t* item) { + return { item->date, item->buy, item->over, item->hold, item->under, item->sell, item->total }; +} +inline fundamental::InstitutionRatingViews convert(const lb_institution_rating_views_t* r) { + std::vector elist; + for (size_t i = 0; i < r->num_elist; ++i) elist.push_back(convert(&r->elist[i])); + return { std::move(elist) }; +} + +// ── industry_rank conversions ───────────────────────────────────── +inline fundamental::IndustryRankItem convert(const lb_industry_rank_item_t* item) { + return { item->name, item->counter_id, item->chg, item->leading_name, item->leading_ticker, + item->leading_chg, item->value_name, item->value_data }; +} +inline fundamental::IndustryRankGroup convert(const lb_industry_rank_group_t* g) { + std::vector lists; + for (size_t i = 0; i < g->num_lists; ++i) lists.push_back(convert(&g->lists[i])); + return { std::move(lists) }; +} +inline fundamental::IndustryRankResponse convert(const lb_industry_rank_response_t* r) { + std::vector items; + for (size_t i = 0; i < r->num_items; ++i) items.push_back(convert(&r->items[i])); + return { std::move(items) }; +} + +// ── industry_peers conversions ──────────────────────────────────── +inline fundamental::IndustryPeerNode convert(const lb_industry_peer_node_t* node) { + return { node->name, node->counter_id, node->stock_num, node->chg, node->ytd_chg, + node->next_json ? node->next_json : "" }; +} +inline fundamental::IndustryPeersResponse convert(const lb_industry_peers_response_t* r) { + fundamental::IndustryPeersTop top{ r->top.name, r->top.market }; + std::optional chain; + if (r->chain) chain = convert(r->chain); + return { std::move(top), std::move(chain) }; +} + +// ── financial_report_snapshot conversions ──────────────────────── +inline fundamental::SnapshotForecastMetric convert(const lb_snapshot_forecast_metric_t* m) { + return { m->value, m->yoy, m->cmp_desc, m->est_value }; +} +inline fundamental::SnapshotReportedMetric convert(const lb_snapshot_reported_metric_t* m) { + return { m->value, m->yoy }; +} +inline fundamental::FinancialReportSnapshot convert(const lb_financial_report_snapshot_t* r) { + std::optional fo_revenue, fo_ebit, fo_eps; + if (r->fo_revenue) fo_revenue = convert(r->fo_revenue); + if (r->fo_ebit) fo_ebit = convert(r->fo_ebit); + if (r->fo_eps) fo_eps = convert(r->fo_eps); + std::optional fr_revenue, fr_profit, fr_operate_cash, + fr_invest_cash, fr_finance_cash, fr_total_assets, fr_total_liability; + if (r->fr_revenue) fr_revenue = convert(r->fr_revenue); + if (r->fr_profit) fr_profit = convert(r->fr_profit); + if (r->fr_operate_cash) fr_operate_cash = convert(r->fr_operate_cash); + if (r->fr_invest_cash) fr_invest_cash = convert(r->fr_invest_cash); + if (r->fr_finance_cash) fr_finance_cash = convert(r->fr_finance_cash); + if (r->fr_total_assets) fr_total_assets = convert(r->fr_total_assets); + if (r->fr_total_liability) fr_total_liability = convert(r->fr_total_liability); + return { r->name, r->ticker, r->fp_start, r->fp_end, r->currency, r->report_desc, + std::move(fo_revenue), std::move(fo_ebit), std::move(fo_eps), + std::move(fr_revenue), std::move(fr_profit), std::move(fr_operate_cash), + std::move(fr_invest_cash), std::move(fr_finance_cash), + std::move(fr_total_assets), std::move(fr_total_liability), + r->fr_roe_ttm, r->fr_profit_margin, r->fr_profit_margin_ttm, + r->fr_asset_turn_ttm, r->fr_leverage_ttm, r->fr_debt_assets_ratio }; +} + // ── Portfolio conversions ───────────────────────────────────────── inline portfolio::ProfitSummaryInfo convert(const lb_profit_summary_info_t* item) { diff --git a/cpp/src/fundamental_context.cpp b/cpp/src/fundamental_context.cpp index c17fadce3..070cf686f 100644 --- a/cpp/src/fundamental_context.cpp +++ b/cpp/src/fundamental_context.cpp @@ -98,6 +98,24 @@ void FundamentalContext::buyback(const std::string& s, AsyncCallback callback) const { F_TYPED(StockRatings, lb_stock_ratings_t, lb_fundamental_context_ratings, ctx_, s.c_str()); } +void FundamentalContext::business_segments(const std::string& s, AsyncCallback callback) const { + F_TYPED(BusinessSegments, lb_business_segments_t, lb_fundamental_context_business_segments, ctx_, s.c_str()); +} +void FundamentalContext::business_segments_history(const std::string& s, const char* report, const char* cate, AsyncCallback callback) const { + F_TYPED(BusinessSegmentsHistory, lb_business_segments_history_t, lb_fundamental_context_business_segments_history, ctx_, s.c_str(), report, cate); +} +void FundamentalContext::institution_rating_views(const std::string& s, AsyncCallback callback) const { + F_TYPED(InstitutionRatingViews, lb_institution_rating_views_t, lb_fundamental_context_institution_rating_views, ctx_, s.c_str()); +} +void FundamentalContext::industry_rank(const std::string& market, const std::string& indicator, const std::string& sort_type, uint32_t limit, AsyncCallback callback) const { + F_TYPED(IndustryRankResponse, lb_industry_rank_response_t, lb_fundamental_context_industry_rank, ctx_, market.c_str(), indicator.c_str(), sort_type.c_str(), limit); +} +void FundamentalContext::industry_peers(const std::string& counter_id, const std::string& market, const char* industry_id, AsyncCallback callback) const { + F_TYPED(IndustryPeersResponse, lb_industry_peers_response_t, lb_fundamental_context_industry_peers, ctx_, counter_id.c_str(), market.c_str(), industry_id); +} +void FundamentalContext::financial_report_snapshot(const std::string& s, const char* report, int32_t fiscal_year, const char* fiscal_period, AsyncCallback callback) const { + F_TYPED(FinancialReportSnapshot, lb_financial_report_snapshot_t, lb_fundamental_context_financial_report_snapshot, ctx_, s.c_str(), report, fiscal_year, fiscal_period); +} #undef F_TYPED #undef F_JSON diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentHistoryItem.java b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentHistoryItem.java new file mode 100644 index 000000000..cbb8d388b --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentHistoryItem.java @@ -0,0 +1,11 @@ +package com.longbridge.fundamental; + +/** One business/regional segment item in a historical snapshot. */ +public class BusinessSegmentHistoryItem { + /** Segment name */ + public String name; + /** Percentage of total */ + public String percent; + /** Absolute value */ + public String value; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentItem.java b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentItem.java new file mode 100644 index 000000000..e3cb5f25e --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentItem.java @@ -0,0 +1,9 @@ +package com.longbridge.fundamental; + +/** One business segment item (latest snapshot). */ +public class BusinessSegmentItem { + /** Segment name */ + public String name; + /** Percentage of total revenue */ + public String percent; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegments.java b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegments.java new file mode 100644 index 000000000..e72b84ef7 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegments.java @@ -0,0 +1,13 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getBusinessSegments}. */ +public class BusinessSegments { + /** Report date */ + public String date; + /** Total revenue */ + public String total; + /** Reporting currency */ + public String currency; + /** Business segment breakdown */ + public BusinessSegmentItem[] business; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoricalItem.java b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoricalItem.java new file mode 100644 index 000000000..f1e208cb3 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoricalItem.java @@ -0,0 +1,15 @@ +package com.longbridge.fundamental; + +/** One historical business segments snapshot. */ +public class BusinessSegmentsHistoricalItem { + /** Report date */ + public String date; + /** Total revenue */ + public String total; + /** Reporting currency */ + public String currency; + /** Business segment breakdown */ + public BusinessSegmentHistoryItem[] business; + /** Regional breakdown */ + public BusinessSegmentHistoryItem[] regionals; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistory.java b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistory.java new file mode 100644 index 000000000..fe0155fb1 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistory.java @@ -0,0 +1,7 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getBusinessSegmentsHistory}. */ +public class BusinessSegmentsHistory { + /** Historical snapshots */ + public BusinessSegmentsHistoricalItem[] historical; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoryOptions.java b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoryOptions.java new file mode 100644 index 000000000..24a6e3a56 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/BusinessSegmentsHistoryOptions.java @@ -0,0 +1,11 @@ +package com.longbridge.fundamental; + +/** Options for {@link FundamentalContext#getBusinessSegmentsHistory}. */ +public class BusinessSegmentsHistoryOptions { + /** Security symbol */ + public String symbol; + /** Report type: "qf", "saf", "af", or null */ + public String report; + /** Category filter, or null */ + public String cate; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshot.java b/java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshot.java new file mode 100644 index 000000000..0e2ada9fa --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshot.java @@ -0,0 +1,49 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getFinancialReportSnapshot}. */ +public class FinancialReportSnapshot { + /** Company name */ + public String name; + /** Ticker code */ + public String ticker; + /** Fiscal period start date */ + public String fpStart; + /** Fiscal period end date */ + public String fpEnd; + /** Reporting currency */ + public String currency; + /** Report description */ + public String reportDesc; + /** Forecast revenue; may be null */ + public SnapshotForecastMetric foRevenue; + /** Forecast EBIT; may be null */ + public SnapshotForecastMetric foEbit; + /** Forecast EPS; may be null */ + public SnapshotForecastMetric foEps; + /** Reported revenue; may be null */ + public SnapshotReportedMetric frRevenue; + /** Reported net profit; may be null */ + public SnapshotReportedMetric frProfit; + /** Reported operating cash flow; may be null */ + public SnapshotReportedMetric frOperateCash; + /** Reported investing cash flow; may be null */ + public SnapshotReportedMetric frInvestCash; + /** Reported financing cash flow; may be null */ + public SnapshotReportedMetric frFinanceCash; + /** Reported total assets; may be null */ + public SnapshotReportedMetric frTotalAssets; + /** Reported total liabilities; may be null */ + public SnapshotReportedMetric frTotalLiability; + /** ROE TTM */ + public String frRoeTtm; + /** Profit margin */ + public String frProfitMargin; + /** Profit margin TTM */ + public String frProfitMarginTtm; + /** Asset turnover TTM */ + public String frAssetTurnTtm; + /** Leverage TTM */ + public String frLeverageTtm; + /** Debt-to-assets ratio */ + public String frDebtAssetsRatio; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshotOptions.java b/java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshotOptions.java new file mode 100644 index 000000000..58dc285db --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/FinancialReportSnapshotOptions.java @@ -0,0 +1,13 @@ +package com.longbridge.fundamental; + +/** Options for {@link FundamentalContext#getFinancialReportSnapshot}. */ +public class FinancialReportSnapshotOptions { + /** Security symbol */ + public String symbol; + /** Report type: "qf", "saf", "af", or null */ + public String report; + /** Fiscal year (e.g. 2023), or null */ + public Integer fiscalYear; + /** Fiscal period string, or null */ + public String fiscalPeriod; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java b/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java index 6d80aef4f..2caee02d9 100644 --- a/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java @@ -265,4 +265,52 @@ public CompletableFuture getRatings(String symbol) throws OpenApiE SdkNative.fundamentalContextGetRatings(raw, symbol, callback); }); } + + /** Get business segment breakdowns (latest snapshot). */ + public CompletableFuture getBusinessSegments(String symbol) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextGetBusinessSegments(raw, symbol, callback); + }); + } + + /** Get historical business segment breakdowns. */ + public CompletableFuture getBusinessSegmentsHistory( + BusinessSegmentsHistoryOptions opts) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextGetBusinessSegmentsHistory(raw, opts, callback); + }); + } + + /** Get historical institutional rating view time-series. */ + public CompletableFuture getInstitutionRatingViews(String symbol) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextGetInstitutionRatingViews(raw, symbol, callback); + }); + } + + /** Get industry rank for a market. */ + public CompletableFuture getIndustryRank(IndustryRankOptions opts) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextGetIndustryRank(raw, opts, callback); + }); + } + + /** Get the industry peer chain for a security or industry. */ + public CompletableFuture getIndustryPeers(IndustryPeersOptions opts) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextGetIndustryPeers(raw, opts, callback); + }); + } + + /** Get a financial report snapshot (earnings snapshot). */ + public CompletableFuture getFinancialReportSnapshot( + FinancialReportSnapshotOptions opts) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextGetFinancialReportSnapshot(raw, opts, callback); + }); + } } diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeerNode.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeerNode.java new file mode 100644 index 000000000..fd1007b54 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeerNode.java @@ -0,0 +1,21 @@ +package com.longbridge.fundamental; + +/** + * A node in the recursive industry peer chain. + * + *

{@code nextJson} contains the child nodes serialised as a JSON string. + */ +public class IndustryPeerNode { + /** Node name */ + public String name; + /** Counter ID */ + public String counterId; + /** Number of stocks in this node */ + public int stockNum; + /** Change percentage */ + public String chg; + /** Year-to-date change */ + public String ytdChg; + /** Child nodes as a JSON string */ + public String nextJson; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersOptions.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersOptions.java new file mode 100644 index 000000000..57cdbb5c4 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersOptions.java @@ -0,0 +1,11 @@ +package com.longbridge.fundamental; + +/** Options for {@link FundamentalContext#getIndustryPeers}. */ +public class IndustryPeersOptions { + /** Symbol (e.g. "AAPL.US") or industry counter ID */ + public String counterId; + /** Market code, e.g. "US" */ + public String market; + /** Industry ID, or null */ + public String industryId; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersResponse.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersResponse.java new file mode 100644 index 000000000..888dfb56d --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersResponse.java @@ -0,0 +1,9 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getIndustryPeers}. */ +public class IndustryPeersResponse { + /** Top-level industry node info */ + public IndustryPeersTop top; + /** Root peer chain node; may be null */ + public IndustryPeerNode chain; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersTop.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersTop.java new file mode 100644 index 000000000..6db713804 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryPeersTop.java @@ -0,0 +1,9 @@ +package com.longbridge.fundamental; + +/** Top-level industry info in the peers response. */ +public class IndustryPeersTop { + /** Industry name */ + public String name; + /** Market code */ + public String market; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankGroup.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankGroup.java new file mode 100644 index 000000000..ea16de381 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankGroup.java @@ -0,0 +1,7 @@ +package com.longbridge.fundamental; + +/** A group of ranked industry items. */ +public class IndustryRankGroup { + /** Items in this group */ + public IndustryRankItem[] lists; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankItem.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankItem.java new file mode 100644 index 000000000..56333cf3f --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankItem.java @@ -0,0 +1,21 @@ +package com.longbridge.fundamental; + +/** One ranked industry item. */ +public class IndustryRankItem { + /** Industry / sector name */ + public String name; + /** Counter ID of the industry */ + public String counterId; + /** Change percentage */ + public String chg; + /** Name of the leading stock */ + public String leadingName; + /** Ticker of the leading stock */ + public String leadingTicker; + /** Change percentage of the leading stock */ + public String leadingChg; + /** Value label name */ + public String valueName; + /** Value data */ + public String valueData; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankOptions.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankOptions.java new file mode 100644 index 000000000..da2d14557 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankOptions.java @@ -0,0 +1,13 @@ +package com.longbridge.fundamental; + +/** Options for {@link FundamentalContext#getIndustryRank}. */ +public class IndustryRankOptions { + /** Market code, e.g. "US" */ + public String market; + /** Indicator (numeric string "0"–"7") */ + public String indicator; + /** Sort type: "0" (ascending) or "1" (descending) */ + public String sortType; + /** Maximum number of results */ + public int limit; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankResponse.java b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankResponse.java new file mode 100644 index 000000000..c4dc1a9de --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/IndustryRankResponse.java @@ -0,0 +1,7 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getIndustryRank}. */ +public class IndustryRankResponse { + /** Grouped rank items */ + public IndustryRankGroup[] items; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViewItem.java b/java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViewItem.java new file mode 100644 index 000000000..4b6488cc1 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViewItem.java @@ -0,0 +1,19 @@ +package com.longbridge.fundamental; + +/** One historical rating distribution snapshot. */ +public class InstitutionRatingViewItem { + /** Date as unix timestamp string */ + public String date; + /** Number of Buy ratings */ + public String buy; + /** Number of Outperform ratings */ + public String over; + /** Number of Hold ratings */ + public String hold; + /** Number of Underperform ratings */ + public String under; + /** Number of Sell ratings */ + public String sell; + /** Total analyst count */ + public String total; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViews.java b/java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViews.java new file mode 100644 index 000000000..4da7c8fbd --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/InstitutionRatingViews.java @@ -0,0 +1,7 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getInstitutionRatingViews}. */ +public class InstitutionRatingViews { + /** Historical rating distribution snapshots */ + public InstitutionRatingViewItem[] elist; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotForecastMetric.java b/java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotForecastMetric.java new file mode 100644 index 000000000..d2089caa4 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotForecastMetric.java @@ -0,0 +1,13 @@ +package com.longbridge.fundamental; + +/** A forecast metric in the financial report snapshot. */ +public class SnapshotForecastMetric { + /** Actual value */ + public String value; + /** Year-over-year change */ + public String yoy; + /** Beat/miss description */ + public String cmpDesc; + /** Consensus estimate value */ + public String estValue; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotReportedMetric.java b/java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotReportedMetric.java new file mode 100644 index 000000000..cf284923e --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/SnapshotReportedMetric.java @@ -0,0 +1,9 @@ +package com.longbridge.fundamental; + +/** A reported metric in the financial report snapshot. */ +public class SnapshotReportedMetric { + /** Actual value */ + public String value; + /** Year-over-year change */ + public String yoy; +} diff --git a/java/src/fundamental_context.rs b/java/src/fundamental_context.rs index d4ba231e4..52caff15b 100644 --- a/java/src/fundamental_context.rs +++ b/java/src/fundamental_context.rs @@ -192,3 +192,162 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGe Ok(()) }) } + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGetBusinessSegments( + mut env: JNIEnv, + _class: JClass, + context: i64, + symbol: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(env, symbol.into())?; + async_util::execute(env, callback, async move { + let resp = context.ctx.business_segments(symbol).await?; + Ok(resp) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGetBusinessSegmentsHistory( + mut env: JNIEnv, + _class: JClass, + context: i64, + opts: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let symbol: String = get_field(env, &opts, "symbol")?; + let report: Option = get_field(env, &opts, "report")?; + let cate: Option = get_field(env, &opts, "cate")?; + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + async_util::execute(env, callback, async move { + let resp = context + .ctx + .business_segments_history(symbol, report_static, cate) + .await?; + Ok(resp) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGetInstitutionRatingViews( + mut env: JNIEnv, + _class: JClass, + context: i64, + symbol: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(env, symbol.into())?; + async_util::execute(env, callback, async move { + let resp = context.ctx.institution_rating_views(symbol).await?; + Ok(resp) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGetIndustryRank( + mut env: JNIEnv, + _class: JClass, + context: i64, + opts: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let market: String = get_field(env, &opts, "market")?; + let indicator: String = get_field(env, &opts, "indicator")?; + let sort_type: String = get_field(env, &opts, "sortType")?; + let limit: i32 = get_field(env, &opts, "limit")?; + let limit = limit.max(0) as u32; + async_util::execute(env, callback, async move { + let resp = context + .ctx + .industry_rank(market, indicator, sort_type, limit) + .await?; + Ok(resp) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGetIndustryPeers( + mut env: JNIEnv, + _class: JClass, + context: i64, + opts: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let counter_id: String = get_field(env, &opts, "counterId")?; + let market: String = get_field(env, &opts, "market")?; + let industry_id: Option = get_field(env, &opts, "industryId")?; + async_util::execute(env, callback, async move { + let resp = context + .ctx + .industry_peers(counter_id, market, industry_id) + .await?; + Ok(resp) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextGetFinancialReportSnapshot( + mut env: JNIEnv, + _class: JClass, + context: i64, + opts: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let symbol: String = get_field(env, &opts, "symbol")?; + let report: Option = get_field(env, &opts, "report")?; + let fiscal_year: Option = get_field(env, &opts, "fiscalYear")?; + let fiscal_period: Option = get_field(env, &opts, "fiscalPeriod")?; + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + let fiscal_period_static: Option<&'static str> = match fiscal_period.as_deref() { + Some("q1") => Some("q1"), + Some("q2") => Some("q2"), + Some("q3") => Some("q3"), + Some("q4") => Some("q4"), + Some("fy") => Some("fy"), + Some("h1") => Some("h1"), + Some("h2") => Some("h2"), + _ => None, + }; + async_util::execute(env, callback, async move { + let resp = context + .ctx + .financial_report_snapshot(symbol, report_static, fiscal_year, fiscal_period_static) + .await?; + Ok(resp) + })?; + Ok(()) + }) +} diff --git a/java/src/init.rs b/java/src/init.rs index aa51e6d34..2fc4dbf2a 100644 --- a/java/src/init.rs +++ b/java/src/init.rs @@ -382,6 +382,23 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init<'a>( longbridge::fundamental::RatingSubIndicatorGroup, longbridge::fundamental::RatingCategory, longbridge::fundamental::StockRatings, + // FundamentalContext: new APIs + longbridge::fundamental::BusinessSegmentItem, + longbridge::fundamental::BusinessSegments, + longbridge::fundamental::BusinessSegmentHistoryItem, + longbridge::fundamental::BusinessSegmentsHistoricalItem, + longbridge::fundamental::BusinessSegmentsHistory, + longbridge::fundamental::InstitutionRatingViewItem, + longbridge::fundamental::InstitutionRatingViews, + longbridge::fundamental::IndustryRankItem, + longbridge::fundamental::IndustryRankGroup, + longbridge::fundamental::IndustryRankResponse, + longbridge::fundamental::IndustryPeersTop, + longbridge::fundamental::IndustryPeerNode, + longbridge::fundamental::IndustryPeersResponse, + longbridge::fundamental::SnapshotForecastMetric, + longbridge::fundamental::SnapshotReportedMetric, + longbridge::fundamental::FinancialReportSnapshot, // PortfolioContext: ProfitAnalysisFlows longbridge::portfolio::FlowItem, longbridge::portfolio::ProfitAnalysisFlows, diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 84c91c0f5..f09a01e89 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -2376,6 +2376,223 @@ impl_java_class!( ] ); +// ── FundamentalContext: new APIs ────────────────────────────────── + +impl_java_class!( + "com/longbridge/fundamental/BusinessSegmentItem", + longbridge::fundamental::BusinessSegmentItem, + [name, percent] +); + +impl_java_class!( + "com/longbridge/fundamental/BusinessSegments", + longbridge::fundamental::BusinessSegments, + [ + date, + total, + currency, + #[java(objarray)] + business + ] +); + +impl_java_class!( + "com/longbridge/fundamental/BusinessSegmentHistoryItem", + longbridge::fundamental::BusinessSegmentHistoryItem, + [name, percent, value] +); + +impl_java_class!( + "com/longbridge/fundamental/BusinessSegmentsHistoricalItem", + longbridge::fundamental::BusinessSegmentsHistoricalItem, + [ + date, + total, + currency, + #[java(objarray)] + business, + #[java(objarray)] + regionals + ] +); + +impl_java_class!( + "com/longbridge/fundamental/BusinessSegmentsHistory", + longbridge::fundamental::BusinessSegmentsHistory, + [ + #[java(objarray)] + historical + ] +); + +impl_java_class!( + "com/longbridge/fundamental/InstitutionRatingViewItem", + longbridge::fundamental::InstitutionRatingViewItem, + [date, buy, over, hold, under, sell, total] +); + +impl_java_class!( + "com/longbridge/fundamental/InstitutionRatingViews", + longbridge::fundamental::InstitutionRatingViews, + [ + #[java(objarray)] + elist + ] +); + +impl_java_class!( + "com/longbridge/fundamental/IndustryRankItem", + longbridge::fundamental::IndustryRankItem, + [ + name, + counter_id, + chg, + leading_name, + leading_ticker, + leading_chg, + value_name, + value_data + ] +); + +impl_java_class!( + "com/longbridge/fundamental/IndustryRankGroup", + longbridge::fundamental::IndustryRankGroup, + [ + #[java(objarray)] + lists + ] +); + +impl_java_class!( + "com/longbridge/fundamental/IndustryRankResponse", + longbridge::fundamental::IndustryRankResponse, + [ + #[java(objarray)] + items + ] +); + +impl_java_class!( + "com/longbridge/fundamental/IndustryPeersTop", + longbridge::fundamental::IndustryPeersTop, + [name, market] +); + +// IndustryPeerNode has a recursive `next` field; we serialize it as nextJson. +// Manual impl (macro can't rename fields). +#[allow(non_upper_case_globals)] +static com_longbridge_fundamental_IndustryPeerNode: std::sync::OnceLock = + std::sync::OnceLock::new(); + +impl crate::types::ClassLoader for longbridge::fundamental::IndustryPeerNode { + fn init(env: &mut jni::JNIEnv) { + let cls = jni::descriptors::Desc::::lookup( + "com/longbridge/fundamental/IndustryPeerNode", + env, + ) + .expect("com/longbridge/fundamental/IndustryPeerNode"); + let _ = com_longbridge_fundamental_IndustryPeerNode.set(env.new_global_ref(&*cls).unwrap()); + } + + fn class_ref() -> jni::objects::GlobalRef { + com_longbridge_fundamental_IndustryPeerNode + .get() + .cloned() + .unwrap() + } +} + +impl crate::types::JSignature for longbridge::fundamental::IndustryPeerNode { + #[inline] + fn signature() -> ::std::borrow::Cow<'static, str> { + "Lcom/longbridge/fundamental/IndustryPeerNode;".into() + } +} + +impl crate::types::IntoJValue for longbridge::fundamental::IndustryPeerNode { + fn into_jvalue<'a>( + self, + env: &mut jni::JNIEnv<'a>, + ) -> jni::errors::Result> { + let longbridge::fundamental::IndustryPeerNode { + name, + counter_id, + stock_num, + chg, + ytd_chg, + next, + } = self; + let next_json = serde_json::to_string(&next).unwrap_or_default(); + let cls = ::class_ref(); + let obj = env.new_object(cls.borrow(), "()V", &[])?; + crate::types::set_field(env, &obj, "name", name)?; + crate::types::set_field(env, &obj, "counterId", counter_id)?; + crate::types::set_field(env, &obj, "stockNum", stock_num)?; + crate::types::set_field(env, &obj, "chg", chg)?; + crate::types::set_field(env, &obj, "ytdChg", ytd_chg)?; + crate::types::set_field(env, &obj, "nextJson", next_json)?; + Ok(obj.into()) + } +} + +impl_java_class!( + "com/longbridge/fundamental/IndustryPeersResponse", + longbridge::fundamental::IndustryPeersResponse, + [top, chain] +); + +impl_java_class!( + "com/longbridge/fundamental/SnapshotForecastMetric", + longbridge::fundamental::SnapshotForecastMetric, + [value, yoy, cmp_desc, est_value] +); + +impl_java_class!( + "com/longbridge/fundamental/SnapshotReportedMetric", + longbridge::fundamental::SnapshotReportedMetric, + [value, yoy] +); + +impl_java_class!( + "com/longbridge/fundamental/FinancialReportSnapshot", + longbridge::fundamental::FinancialReportSnapshot, + [ + name, + ticker, + fp_start, + fp_end, + currency, + report_desc, + #[java(nullable)] + fo_revenue, + #[java(nullable)] + fo_ebit, + #[java(nullable)] + fo_eps, + #[java(nullable)] + fr_revenue, + #[java(nullable)] + fr_profit, + #[java(nullable)] + fr_operate_cash, + #[java(nullable)] + fr_invest_cash, + #[java(nullable)] + fr_finance_cash, + #[java(nullable)] + fr_total_assets, + #[java(nullable)] + fr_total_liability, + fr_roe_ttm, + fr_profit_margin, + fr_profit_margin_ttm, + fr_asset_turn_ttm, + fr_leverage_ttm, + fr_debt_assets_ratio + ] +); + // ── PortfolioContext: ProfitAnalysisFlows and related ───────────── impl_java_class!( diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index 2b0b38b32..d815607a2 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -42,10 +42,14 @@ export declare class AlertContext { * `triggerValue` is a price or percentage string depending on `condition`. */ add(symbol: string, condition: AlertCondition, triggerValue: string, frequency: AlertFrequency): Promise - /** Enable a previously disabled price alert. */ - enable(alertId: string): Promise - /** Disable a price alert without deleting it. */ - disable(alertId: string): Promise + /** + * Update a price alert. + * + * Pass the [`AlertItem`] obtained from [`list`](Self::list). Set + * `item.enabled` to `true` to re-enable or `false` to disable before + * calling this method. + */ + update(item: AlertItem): Promise /** Delete one or more price alerts by ID. */ delete(alertIds: Array): Promise } @@ -603,6 +607,18 @@ export declare class FundamentalContext { buyback(symbol: string): Promise /** Get stock ratings for a security */ ratings(symbol: string): Promise + /** Get business segment breakdowns (latest snapshot) */ + businessSegments(symbol: string): Promise + /** Get historical business segment breakdowns */ + businessSegmentsHistory(symbol: string, report?: string | undefined | null, cate?: string | undefined | null): Promise + /** Get historical institutional rating view time-series */ + institutionRatingViews(symbol: string): Promise + /** Get industry rank for a market */ + industryRank(market: string, indicator: string, sortType: string, limit: number): Promise + /** Get the industry peer chain for a security or industry */ + industryPeers(counterId: string, market: string, industryId?: string | undefined | null): Promise + /** Get a financial report snapshot (earnings snapshot) */ + financialReportSnapshot(symbol: string, report?: string | undefined | null, fiscalYear?: number | undefined | null, fiscalPeriod?: string | undefined | null): Promise } /** Fund position */ @@ -3130,6 +3146,41 @@ export interface BrokerHoldingTop { updatedAt: string } +/** One business/regional segment item in a historical snapshot */ +export interface BusinessSegmentHistoryItem { + name: string + percent: string + value: string +} + +/** One business segment item (latest snapshot) */ +export interface BusinessSegmentItem { + name: string + percent: string +} + +/** Business segments response */ +export interface BusinessSegments { + date: string + total: string + currency: string + business: Array +} + +/** One historical business segments snapshot */ +export interface BusinessSegmentsHistoricalItem { + date: string + total: string + currency: string + business: Array + regionals: Array +} + +/** Business segments history response */ +export interface BusinessSegmentsHistory { + historical: Array +} + /** Buyback data response */ export interface BuybackData { recentBuybacks?: RecentBuybacks @@ -3919,6 +3970,32 @@ export interface FinancialReports { list: any } +/** Financial report snapshot response */ +export interface FinancialReportSnapshot { + name: string + ticker: string + fpStart: string + fpEnd: string + currency: string + reportDesc: string + foRevenue?: SnapshotForecastMetric + foEbit?: SnapshotForecastMetric + foEps?: SnapshotForecastMetric + frRevenue?: SnapshotReportedMetric + frProfit?: SnapshotReportedMetric + frOperateCash?: SnapshotReportedMetric + frInvestCash?: SnapshotReportedMetric + frFinanceCash?: SnapshotReportedMetric + frTotalAssets?: SnapshotReportedMetric + frTotalLiability?: SnapshotReportedMetric + frRoeTtm: string + frProfitMargin: string + frProfitMarginTtm: string + frAssetTurnTtm: string + frLeverageTtm: string + frDebtAssetsRatio: string +} + export declare const enum FlowDirection { /** Unknown */ Unknown = 0, @@ -4107,6 +4184,55 @@ export interface IndexConstituents { stocks: Array } +/** + * A node in the recursive industry peer chain. + * + * `nextJson` contains the child nodes serialised as a JSON string. + */ +export interface IndustryPeerNode { + name: string + counterId: string + stockNum: number + chg: string + ytdChg: string + /** Child nodes as a JSON string */ + nextJson: string +} + +/** Industry peers response */ +export interface IndustryPeersResponse { + top: IndustryPeersTop + chain?: IndustryPeerNode +} + +/** Top-level industry info in the peers response */ +export interface IndustryPeersTop { + name: string + market: string +} + +/** A group of ranked industry items */ +export interface IndustryRankGroup { + lists: Array +} + +/** One ranked industry item */ +export interface IndustryRankItem { + name: string + counterId: string + chg: string + leadingName: string + leadingTicker: string + leadingChg: string + valueName: string + valueData: string +} + +/** Industry rank response */ +export interface IndustryRankResponse { + items: Array +} + /** Industry valuation distribution response */ export interface IndustryValuationDist { /** PE distribution */ @@ -4271,6 +4397,22 @@ export interface InstitutionRatingSummary { updatedAt: string } +/** One historical rating distribution snapshot */ +export interface InstitutionRatingViewItem { + date: string + buy: string + over: string + hold: string + under: string + sell: string + total: string +} + +/** Institution rating views response */ +export interface InstitutionRatingViews { + elist: Array +} + export declare const enum InstitutionRecommend { /** Unknown */ Unknown = 0, @@ -5214,6 +5356,20 @@ export interface ShortPositionsResponse { sources: number } +/** A forecast metric in the financial report snapshot */ +export interface SnapshotForecastMetric { + value: string + yoy: string + cmpDesc: string + estValue: string +} + +/** A reported metric in the financial report snapshot */ +export interface SnapshotReportedMetric { + value: string + yoy: string +} + /** Sort order type */ export declare const enum SortOrderType { /** Ascending */ diff --git a/nodejs/index.js b/nodejs/index.js index ad854d664..3d6beaf8a 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -3,9 +3,6 @@ // @ts-nocheck /* auto-generated by NAPI-RS */ -const { createRequire } = require('node:module') -require = createRequire(__filename) - const { readFileSync } = require('node:fs') let nativeBinding = null const loadErrors = [] @@ -66,7 +63,7 @@ const isMuslFromChildProcess = () => { function requireNative() { if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { try { - nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); + return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); } catch (err) { loadErrors.push(err) } @@ -78,7 +75,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-android-arm64') + const binding = require('longbridge-android-arm64') + const bindingPackageVersion = require('longbridge-android-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -89,7 +91,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-android-arm-eabi') + const binding = require('longbridge-android-arm-eabi') + const bindingPackageVersion = require('longbridge-android-arm-eabi/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -98,16 +105,39 @@ function requireNative() { } } else if (process.platform === 'win32') { if (process.arch === 'x64') { + if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') { + try { + return require('./longbridge.win32-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } try { + const binding = require('longbridge-win32-x64-gnu') + const bindingPackageVersion = require('longbridge-win32-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { return require('./longbridge.win32-x64-msvc.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-win32-x64-msvc') + const binding = require('longbridge-win32-x64-msvc') + const bindingPackageVersion = require('longbridge-win32-x64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } + } } else if (process.arch === 'ia32') { try { return require('./longbridge.win32-ia32-msvc.node') @@ -115,7 +145,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-win32-ia32-msvc') + const binding = require('longbridge-win32-ia32-msvc') + const bindingPackageVersion = require('longbridge-win32-ia32-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -126,7 +161,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-win32-arm64-msvc') + const binding = require('longbridge-win32-arm64-msvc') + const bindingPackageVersion = require('longbridge-win32-arm64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -140,7 +180,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-darwin-universal') + const binding = require('longbridge-darwin-universal') + const bindingPackageVersion = require('longbridge-darwin-universal/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -151,7 +196,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-darwin-x64') + const binding = require('longbridge-darwin-x64') + const bindingPackageVersion = require('longbridge-darwin-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -162,7 +212,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-darwin-arm64') + const binding = require('longbridge-darwin-arm64') + const bindingPackageVersion = require('longbridge-darwin-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -177,7 +232,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-freebsd-x64') + const binding = require('longbridge-freebsd-x64') + const bindingPackageVersion = require('longbridge-freebsd-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -188,7 +248,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-freebsd-arm64') + const binding = require('longbridge-freebsd-arm64') + const bindingPackageVersion = require('longbridge-freebsd-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -204,7 +269,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-x64-musl') + const binding = require('longbridge-linux-x64-musl') + const bindingPackageVersion = require('longbridge-linux-x64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -215,7 +285,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-x64-gnu') + const binding = require('longbridge-linux-x64-gnu') + const bindingPackageVersion = require('longbridge-linux-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -228,7 +303,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm64-musl') + const binding = require('longbridge-linux-arm64-musl') + const bindingPackageVersion = require('longbridge-linux-arm64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -239,7 +319,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm64-gnu') + const binding = require('longbridge-linux-arm64-gnu') + const bindingPackageVersion = require('longbridge-linux-arm64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -252,7 +337,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm-musleabihf') + const binding = require('longbridge-linux-arm-musleabihf') + const bindingPackageVersion = require('longbridge-linux-arm-musleabihf/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -263,7 +353,46 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-arm-gnueabihf') + const binding = require('longbridge-linux-arm-gnueabihf') + const bindingPackageVersion = require('longbridge-linux-arm-gnueabihf/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'loong64') { + if (isMusl()) { + try { + return require('./longbridge.linux-loong64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('longbridge-linux-loong64-musl') + const bindingPackageVersion = require('longbridge-linux-loong64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./longbridge.linux-loong64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('longbridge-linux-loong64-gnu') + const bindingPackageVersion = require('longbridge-linux-loong64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -276,7 +405,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-riscv64-musl') + const binding = require('longbridge-linux-riscv64-musl') + const bindingPackageVersion = require('longbridge-linux-riscv64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -287,7 +421,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-riscv64-gnu') + const binding = require('longbridge-linux-riscv64-gnu') + const bindingPackageVersion = require('longbridge-linux-riscv64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -299,7 +438,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-ppc64-gnu') + const binding = require('longbridge-linux-ppc64-gnu') + const bindingPackageVersion = require('longbridge-linux-ppc64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -310,7 +454,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('longbridge-linux-s390x-gnu') + const binding = require('longbridge-linux-s390x-gnu') + const bindingPackageVersion = require('longbridge-linux-s390x-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -320,34 +469,49 @@ function requireNative() { } else if (process.platform === 'openharmony') { if (process.arch === 'arm64') { try { - return require('./longbridge.linux-arm64-ohos.node') + return require('./longbridge.openharmony-arm64.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-linux-arm64-ohos') + const binding = require('longbridge-openharmony-arm64') + const bindingPackageVersion = require('longbridge-openharmony-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } } else if (process.arch === 'x64') { try { - return require('./longbridge.linux-x64-ohos.node') + return require('./longbridge.openharmony-x64.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-linux-x64-ohos') + const binding = require('longbridge-openharmony-x64') + const bindingPackageVersion = require('longbridge-openharmony-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } } else if (process.arch === 'arm') { try { - return require('./longbridge.linux-arm-ohos.node') + return require('./longbridge.openharmony-arm.node') } catch (e) { loadErrors.push(e) } try { - return require('longbridge-linux-arm-ohos') + const binding = require('longbridge-openharmony-arm') + const bindingPackageVersion = require('longbridge-openharmony-arm/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -362,22 +526,36 @@ function requireNative() { nativeBinding = requireNative() if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + let wasiBinding = null + let wasiBindingError = null try { - nativeBinding = require('./longbridge.wasi.cjs') + wasiBinding = require('./longbridge.wasi.cjs') + nativeBinding = wasiBinding } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { - loadErrors.push(err) + wasiBindingError = err } } - if (!nativeBinding) { + if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { try { - nativeBinding = require('longbridge-wasm32-wasi') + wasiBinding = require('longbridge-wasm32-wasi') + nativeBinding = wasiBinding } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { + if (!wasiBindingError) { + wasiBindingError = err + } else { + wasiBindingError.cause = err + } loadErrors.push(err) } } } + if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { + const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') + error.cause = wasiBindingError + throw error + } } if (!nativeBinding) { @@ -386,7 +564,12 @@ if (!nativeBinding) { `Cannot find native binding. ` + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', - { cause: loadErrors } + { + cause: loadErrors.reduce((err, cur) => { + cur.cause = err + return cur + }), + }, ) } throw new Error(`Failed to load native binding`) diff --git a/nodejs/src/fundamental/context.rs b/nodejs/src/fundamental/context.rs index cdf1d050f..be4d96f5f 100644 --- a/nodejs/src/fundamental/context.rs +++ b/nodejs/src/fundamental/context.rs @@ -233,4 +233,114 @@ impl FundamentalContext { pub async fn ratings(&self, symbol: String) -> Result { Ok(self.ctx.ratings(symbol).await.map_err(ErrorNewType)?.into()) } + + /// Get business segment breakdowns (latest snapshot) + #[napi] + pub async fn business_segments(&self, symbol: String) -> Result { + Ok(self + .ctx + .business_segments(symbol) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical business segment breakdowns + #[napi] + pub async fn business_segments_history( + &self, + symbol: String, + report: Option, + cate: Option, + ) -> Result { + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + Ok(self + .ctx + .business_segments_history(symbol, report_static, cate) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical institutional rating view time-series + #[napi] + pub async fn institution_rating_views(&self, symbol: String) -> Result { + Ok(self + .ctx + .institution_rating_views(symbol) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get industry rank for a market + #[napi] + pub async fn industry_rank( + &self, + market: String, + indicator: String, + sort_type: String, + limit: u32, + ) -> Result { + Ok(self + .ctx + .industry_rank(market, indicator, sort_type, limit) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get the industry peer chain for a security or industry + #[napi] + pub async fn industry_peers( + &self, + counter_id: String, + market: String, + industry_id: Option, + ) -> Result { + Ok(self + .ctx + .industry_peers(counter_id, market, industry_id) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get a financial report snapshot (earnings snapshot) + #[napi] + pub async fn financial_report_snapshot( + &self, + symbol: String, + report: Option, + fiscal_year: Option, + fiscal_period: Option, + ) -> Result { + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + let fiscal_period_static: Option<&'static str> = match fiscal_period.as_deref() { + Some("q1") => Some("q1"), + Some("q2") => Some("q2"), + Some("q3") => Some("q3"), + Some("q4") => Some("q4"), + Some("fy") => Some("fy"), + Some("h1") => Some("h1"), + Some("h2") => Some("h2"), + _ => None, + }; + Ok(self + .ctx + .financial_report_snapshot(symbol, report_static, fiscal_year, fiscal_period_static) + .await + .map_err(ErrorNewType)? + .into()) + } } diff --git a/nodejs/src/fundamental/types.rs b/nodejs/src/fundamental/types.rs index bfa374337..57852c762 100644 --- a/nodejs/src/fundamental/types.rs +++ b/nodejs/src/fundamental/types.rs @@ -1646,3 +1646,366 @@ impl From for StockRatings { } } } + +// ── business_segments ───────────────────────────────────────────── + +/// One business segment item (latest snapshot) +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct BusinessSegmentItem { + pub name: String, + pub percent: String, +} + +impl From for BusinessSegmentItem { + fn from(v: lb::BusinessSegmentItem) -> Self { + Self { + name: v.name, + percent: v.percent, + } + } +} + +/// Business segments response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct BusinessSegments { + pub date: String, + pub total: String, + pub currency: String, + pub business: Vec, +} + +impl From for BusinessSegments { + fn from(v: lb::BusinessSegments) -> Self { + Self { + date: v.date, + total: v.total, + currency: v.currency, + business: v.business.into_iter().map(Into::into).collect(), + } + } +} + +/// One business/regional segment item in a historical snapshot +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct BusinessSegmentHistoryItem { + pub name: String, + pub percent: String, + pub value: String, +} + +impl From for BusinessSegmentHistoryItem { + fn from(v: lb::BusinessSegmentHistoryItem) -> Self { + Self { + name: v.name, + percent: v.percent, + value: v.value, + } + } +} + +/// One historical business segments snapshot +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct BusinessSegmentsHistoricalItem { + pub date: String, + pub total: String, + pub currency: String, + pub business: Vec, + pub regionals: Vec, +} + +impl From for BusinessSegmentsHistoricalItem { + fn from(v: lb::BusinessSegmentsHistoricalItem) -> Self { + Self { + date: v.date, + total: v.total, + currency: v.currency, + business: v.business.into_iter().map(Into::into).collect(), + regionals: v.regionals.into_iter().map(Into::into).collect(), + } + } +} + +/// Business segments history response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct BusinessSegmentsHistory { + pub historical: Vec, +} + +impl From for BusinessSegmentsHistory { + fn from(v: lb::BusinessSegmentsHistory) -> Self { + Self { + historical: v.historical.into_iter().map(Into::into).collect(), + } + } +} + +// ── institution_rating_views ────────────────────────────────────── + +/// One historical rating distribution snapshot +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct InstitutionRatingViewItem { + pub date: String, + pub buy: String, + pub over: String, + pub hold: String, + pub under: String, + pub sell: String, + pub total: String, +} + +impl From for InstitutionRatingViewItem { + fn from(v: lb::InstitutionRatingViewItem) -> Self { + Self { + date: v.date, + buy: v.buy, + over: v.over, + hold: v.hold, + under: v.under, + sell: v.sell, + total: v.total, + } + } +} + +/// Institution rating views response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct InstitutionRatingViews { + pub elist: Vec, +} + +impl From for InstitutionRatingViews { + fn from(v: lb::InstitutionRatingViews) -> Self { + Self { + elist: v.elist.into_iter().map(Into::into).collect(), + } + } +} + +// ── industry_rank ───────────────────────────────────────────────── + +/// One ranked industry item +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct IndustryRankItem { + pub name: String, + pub counter_id: String, + pub chg: String, + pub leading_name: String, + pub leading_ticker: String, + pub leading_chg: String, + pub value_name: String, + pub value_data: String, +} + +impl From for IndustryRankItem { + fn from(v: lb::IndustryRankItem) -> Self { + Self { + name: v.name, + counter_id: v.counter_id, + chg: v.chg, + leading_name: v.leading_name, + leading_ticker: v.leading_ticker, + leading_chg: v.leading_chg, + value_name: v.value_name, + value_data: v.value_data, + } + } +} + +/// A group of ranked industry items +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct IndustryRankGroup { + pub lists: Vec, +} + +impl From for IndustryRankGroup { + fn from(v: lb::IndustryRankGroup) -> Self { + Self { + lists: v.lists.into_iter().map(Into::into).collect(), + } + } +} + +/// Industry rank response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct IndustryRankResponse { + pub items: Vec, +} + +impl From for IndustryRankResponse { + fn from(v: lb::IndustryRankResponse) -> Self { + Self { + items: v.items.into_iter().map(Into::into).collect(), + } + } +} + +// ── industry_peers ──────────────────────────────────────────────── + +/// Top-level industry info in the peers response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct IndustryPeersTop { + pub name: String, + pub market: String, +} + +impl From for IndustryPeersTop { + fn from(v: lb::IndustryPeersTop) -> Self { + Self { + name: v.name, + market: v.market, + } + } +} + +/// A node in the recursive industry peer chain. +/// +/// `nextJson` contains the child nodes serialised as a JSON string. +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct IndustryPeerNode { + pub name: String, + pub counter_id: String, + pub stock_num: i32, + pub chg: String, + pub ytd_chg: String, + /// Child nodes as a JSON string + pub next_json: String, +} + +impl From for IndustryPeerNode { + fn from(v: lb::IndustryPeerNode) -> Self { + Self { + name: v.name, + counter_id: v.counter_id, + stock_num: v.stock_num, + chg: v.chg, + ytd_chg: v.ytd_chg, + next_json: serde_json::to_string(&v.next).unwrap_or_default(), + } + } +} + +/// Industry peers response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct IndustryPeersResponse { + pub top: IndustryPeersTop, + pub chain: Option, +} + +impl From for IndustryPeersResponse { + fn from(v: lb::IndustryPeersResponse) -> Self { + Self { + top: v.top.into(), + chain: v.chain.map(Into::into), + } + } +} + +// ── financial_report_snapshot ───────────────────────────────────── + +/// A forecast metric in the financial report snapshot +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct SnapshotForecastMetric { + pub value: String, + pub yoy: String, + pub cmp_desc: String, + pub est_value: String, +} + +impl From for SnapshotForecastMetric { + fn from(v: lb::SnapshotForecastMetric) -> Self { + Self { + value: v.value, + yoy: v.yoy, + cmp_desc: v.cmp_desc, + est_value: v.est_value, + } + } +} + +/// A reported metric in the financial report snapshot +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct SnapshotReportedMetric { + pub value: String, + pub yoy: String, +} + +impl From for SnapshotReportedMetric { + fn from(v: lb::SnapshotReportedMetric) -> Self { + Self { + value: v.value, + yoy: v.yoy, + } + } +} + +/// Financial report snapshot response +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct FinancialReportSnapshot { + pub name: String, + pub ticker: String, + pub fp_start: String, + pub fp_end: String, + pub currency: String, + pub report_desc: String, + pub fo_revenue: Option, + pub fo_ebit: Option, + pub fo_eps: Option, + pub fr_revenue: Option, + pub fr_profit: Option, + pub fr_operate_cash: Option, + pub fr_invest_cash: Option, + pub fr_finance_cash: Option, + pub fr_total_assets: Option, + pub fr_total_liability: Option, + pub fr_roe_ttm: String, + pub fr_profit_margin: String, + pub fr_profit_margin_ttm: String, + pub fr_asset_turn_ttm: String, + pub fr_leverage_ttm: String, + pub fr_debt_assets_ratio: String, +} + +impl From for FinancialReportSnapshot { + fn from(v: lb::FinancialReportSnapshot) -> Self { + Self { + name: v.name, + ticker: v.ticker, + fp_start: v.fp_start, + fp_end: v.fp_end, + currency: v.currency, + report_desc: v.report_desc, + fo_revenue: v.fo_revenue.map(Into::into), + fo_ebit: v.fo_ebit.map(Into::into), + fo_eps: v.fo_eps.map(Into::into), + fr_revenue: v.fr_revenue.map(Into::into), + fr_profit: v.fr_profit.map(Into::into), + fr_operate_cash: v.fr_operate_cash.map(Into::into), + fr_invest_cash: v.fr_invest_cash.map(Into::into), + fr_finance_cash: v.fr_finance_cash.map(Into::into), + fr_total_assets: v.fr_total_assets.map(Into::into), + fr_total_liability: v.fr_total_liability.map(Into::into), + fr_roe_ttm: v.fr_roe_ttm, + fr_profit_margin: v.fr_profit_margin, + fr_profit_margin_ttm: v.fr_profit_margin_ttm, + fr_asset_turn_ttm: v.fr_asset_turn_ttm, + fr_leverage_ttm: v.fr_leverage_ttm, + fr_debt_assets_ratio: v.fr_debt_assets_ratio, + } + } +} diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 115271ebc..6da9ddeb6 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -9614,6 +9614,228 @@ class StockRatings: """Full ratings array as a JSON string""" +class BusinessSegmentItem: + """One business segment item (latest snapshot).""" + + name: str + """Segment name""" + percent: str + """Percentage of total revenue""" + + +class BusinessSegments: + """Response for :meth:`FundamentalContext.business_segments`.""" + + date: str + """Report date""" + total: str + """Total revenue""" + currency: str + """Reporting currency""" + business: list[BusinessSegmentItem] + """Business segment breakdown""" + + +class BusinessSegmentHistoryItem: + """One business/regional segment item in a historical snapshot.""" + + name: str + """Segment name""" + percent: str + """Percentage of total""" + value: str + """Absolute value""" + + +class BusinessSegmentsHistoricalItem: + """One historical business segments snapshot.""" + + date: str + """Report date""" + total: str + """Total revenue""" + currency: str + """Reporting currency""" + business: list[BusinessSegmentHistoryItem] + """Business segment breakdown""" + regionals: list[BusinessSegmentHistoryItem] + """Regional breakdown""" + + +class BusinessSegmentsHistory: + """Response for :meth:`FundamentalContext.business_segments_history`.""" + + historical: list[BusinessSegmentsHistoricalItem] + """Historical snapshots""" + + +class InstitutionRatingViewItem: + """One historical rating distribution snapshot.""" + + date: str + """Date as unix timestamp string""" + buy: str + """Number of Buy ratings""" + over: str + """Number of Outperform ratings""" + hold: str + """Number of Hold ratings""" + under: str + """Number of Underperform ratings""" + sell: str + """Number of Sell ratings""" + total: str + """Total analyst count""" + + +class InstitutionRatingViews: + """Response for :meth:`FundamentalContext.institution_rating_views`.""" + + elist: list[InstitutionRatingViewItem] + """Historical rating distribution snapshots""" + + +class IndustryRankItem: + """One ranked industry item.""" + + name: str + """Industry / sector name""" + counter_id: str + """Counter ID of the industry""" + chg: str + """Change percentage""" + leading_name: str + """Name of the leading stock""" + leading_ticker: str + """Ticker of the leading stock""" + leading_chg: str + """Change percentage of the leading stock""" + value_name: str + """Value label name""" + value_data: str + """Value data""" + + +class IndustryRankGroup: + """A group of ranked industry items.""" + + lists: list[IndustryRankItem] + """Items in this group""" + + +class IndustryRankResponse: + """Response for :meth:`FundamentalContext.industry_rank`.""" + + items: list[IndustryRankGroup] + """Grouped rank items""" + + +class IndustryPeersTop: + """Top-level industry info in the peers response.""" + + name: str + """Industry name""" + market: str + """Market code""" + + +class IndustryPeerNode: + """A node in the recursive industry peer chain.""" + + name: str + """Node name""" + counter_id: str + """Counter ID""" + stock_num: int + """Number of stocks in this node""" + chg: str + """Change percentage""" + ytd_chg: str + """Year-to-date change""" + next_json: str + """Child nodes as a JSON string""" + + +class IndustryPeersResponse: + """Response for :meth:`FundamentalContext.industry_peers`.""" + + top: IndustryPeersTop + """Top-level industry node info""" + chain: "IndustryPeerNode | None" + """Root peer chain node""" + + +class SnapshotForecastMetric: + """A forecast metric in the financial report snapshot.""" + + value: str + """Actual value""" + yoy: str + """Year-over-year change""" + cmp_desc: str + """Beat/miss description""" + est_value: str + """Consensus estimate value""" + + +class SnapshotReportedMetric: + """A reported metric in the financial report snapshot.""" + + value: str + """Actual value""" + yoy: str + """Year-over-year change""" + + +class FinancialReportSnapshot: + """Response for :meth:`FundamentalContext.financial_report_snapshot`.""" + + name: str + """Company name""" + ticker: str + """Ticker code""" + fp_start: str + """Fiscal period start date""" + fp_end: str + """Fiscal period end date""" + currency: str + """Reporting currency""" + report_desc: str + """Report description""" + fo_revenue: "SnapshotForecastMetric | None" + """Forecast revenue""" + fo_ebit: "SnapshotForecastMetric | None" + """Forecast EBIT""" + fo_eps: "SnapshotForecastMetric | None" + """Forecast EPS""" + fr_revenue: "SnapshotReportedMetric | None" + """Reported revenue""" + fr_profit: "SnapshotReportedMetric | None" + """Reported net profit""" + fr_operate_cash: "SnapshotReportedMetric | None" + """Reported operating cash flow""" + fr_invest_cash: "SnapshotReportedMetric | None" + """Reported investing cash flow""" + fr_finance_cash: "SnapshotReportedMetric | None" + """Reported financing cash flow""" + fr_total_assets: "SnapshotReportedMetric | None" + """Reported total assets""" + fr_total_liability: "SnapshotReportedMetric | None" + """Reported total liabilities""" + fr_roe_ttm: str + """ROE TTM""" + fr_profit_margin: str + """Profit margin""" + fr_profit_margin_ttm: str + """Profit margin TTM""" + fr_asset_turn_ttm: str + """Asset turnover TTM""" + fr_leverage_ttm: str + """Leverage TTM""" + fr_debt_assets_ratio: str + """Debt-to-assets ratio""" + + class FinancialReportKind: """Financial report kind.""" @@ -9792,6 +10014,110 @@ class FundamentalContext: """ ... + def business_segments(self, symbol: str) -> "BusinessSegments": + """ + Get business segment breakdowns (latest snapshot). + + Args: + symbol: Security symbol, e.g. ``"AAPL.US"`` + + Returns: + :class:`BusinessSegments` + """ + ... + + def business_segments_history( + self, + symbol: str, + report: "str | None" = None, + cate: "str | None" = None, + ) -> "BusinessSegmentsHistory": + """ + Get historical business segment breakdowns. + + Args: + symbol: Security symbol + report: Report type (``"qf"``, ``"saf"``, ``"af"``) or ``None`` + cate: Category filter or ``None`` + + Returns: + :class:`BusinessSegmentsHistory` + """ + ... + + def institution_rating_views(self, symbol: str) -> "InstitutionRatingViews": + """ + Get historical institutional rating view time-series. + + Args: + symbol: Security symbol + + Returns: + :class:`InstitutionRatingViews` + """ + ... + + def industry_rank( + self, + market: str, + indicator: str, + sort_type: str, + limit: int, + ) -> "IndustryRankResponse": + """ + Get industry rank for a market. + + Args: + market: Market code, e.g. ``"US"`` + indicator: Numeric string ``"0"``–``"7"`` + sort_type: ``"0"`` (ascending) or ``"1"`` (descending) + limit: Maximum number of results + + Returns: + :class:`IndustryRankResponse` + """ + ... + + def industry_peers( + self, + counter_id: str, + market: str, + industry_id: "str | None" = None, + ) -> "IndustryPeersResponse": + """ + Get the industry peer chain for a security or industry. + + Args: + counter_id: Symbol (e.g. ``"AAPL.US"``) or industry counter ID + market: Market code, e.g. ``"US"`` + industry_id: Industry ID or ``None`` + + Returns: + :class:`IndustryPeersResponse` + """ + ... + + def financial_report_snapshot( + self, + symbol: str, + report: "str | None" = None, + fiscal_year: "int | None" = None, + fiscal_period: "str | None" = None, + ) -> "FinancialReportSnapshot": + """ + Get a financial report snapshot (earnings snapshot). + + Args: + symbol: Security symbol + report: Report type (``"qf"``, ``"saf"``, ``"af"``) or ``None`` + fiscal_year: Fiscal year (e.g. ``2023``) or ``None`` + fiscal_period: Fiscal period string or ``None`` + + Returns: + :class:`FinancialReportSnapshot` + """ + ... + # ── MarketContext ───────────────────────────────────────────────── diff --git a/python/src/fundamental/context.rs b/python/src/fundamental/context.rs index d4d8a8e39..3f7e26e05 100644 --- a/python/src/fundamental/context.rs +++ b/python/src/fundamental/context.rs @@ -161,4 +161,105 @@ impl FundamentalContext { fn ratings(&self, symbol: String) -> PyResult { Ok(self.ctx.ratings(symbol).map_err(ErrorNewType)?.into()) } + + /// Get business segment breakdowns (latest snapshot). + fn business_segments(&self, symbol: String) -> PyResult { + Ok(self + .ctx + .business_segments(symbol) + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical business segment breakdowns. + #[pyo3(signature = (symbol, report = None, cate = None))] + fn business_segments_history( + &self, + symbol: String, + report: Option, + cate: Option, + ) -> PyResult { + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + Ok(self + .ctx + .business_segments_history(symbol, report_static, cate) + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical institutional rating view time-series. + fn institution_rating_views(&self, symbol: String) -> PyResult { + Ok(self + .ctx + .institution_rating_views(symbol) + .map_err(ErrorNewType)? + .into()) + } + + /// Get industry rank for a market. + fn industry_rank( + &self, + market: String, + indicator: String, + sort_type: String, + limit: u32, + ) -> PyResult { + Ok(self + .ctx + .industry_rank(market, indicator, sort_type, limit) + .map_err(ErrorNewType)? + .into()) + } + + /// Get the industry peer chain for a security or industry. + #[pyo3(signature = (counter_id, market, industry_id = None))] + fn industry_peers( + &self, + counter_id: String, + market: String, + industry_id: Option, + ) -> PyResult { + Ok(self + .ctx + .industry_peers(counter_id, market, industry_id) + .map_err(ErrorNewType)? + .into()) + } + + /// Get a financial report snapshot (earnings snapshot). + #[pyo3(signature = (symbol, report = None, fiscal_year = None, fiscal_period = None))] + fn financial_report_snapshot( + &self, + symbol: String, + report: Option, + fiscal_year: Option, + fiscal_period: Option, + ) -> PyResult { + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + let fiscal_period_static: Option<&'static str> = match fiscal_period.as_deref() { + Some("q1") => Some("q1"), + Some("q2") => Some("q2"), + Some("q3") => Some("q3"), + Some("q4") => Some("q4"), + Some("fy") => Some("fy"), + Some("h1") => Some("h1"), + Some("h2") => Some("h2"), + _ => None, + }; + Ok(self + .ctx + .financial_report_snapshot(symbol, report_static, fiscal_year, fiscal_period_static) + .map_err(ErrorNewType)? + .into()) + } } diff --git a/python/src/fundamental/context_async.rs b/python/src/fundamental/context_async.rs index 9361bd5d3..2a929dfcf 100644 --- a/python/src/fundamental/context_async.rs +++ b/python/src/fundamental/context_async.rs @@ -253,4 +253,137 @@ impl AsyncFundamentalContext { }) .map(|b| b.unbind()) } + + /// Get business segment breakdowns. Returns awaitable. + fn business_segments(&self, py: Python<'_>, symbol: String) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(BusinessSegments::from( + ctx.business_segments(symbol).await.map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get historical business segment breakdowns. Returns awaitable. + #[pyo3(signature = (symbol, report = None, cate = None))] + fn business_segments_history( + &self, + py: Python<'_>, + symbol: String, + report: Option, + cate: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(BusinessSegmentsHistory::from( + ctx.business_segments_history(symbol, report_static, cate) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get historical institutional rating view time-series. Returns awaitable. + fn institution_rating_views(&self, py: Python<'_>, symbol: String) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(InstitutionRatingViews::from( + ctx.institution_rating_views(symbol) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get industry rank for a market. Returns awaitable. + fn industry_rank( + &self, + py: Python<'_>, + market: String, + indicator: String, + sort_type: String, + limit: u32, + ) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(IndustryRankResponse::from( + ctx.industry_rank(market, indicator, sort_type, limit) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get the industry peer chain for a security or industry. Returns + /// awaitable. + #[pyo3(signature = (counter_id, market, industry_id = None))] + fn industry_peers( + &self, + py: Python<'_>, + counter_id: String, + market: String, + industry_id: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(IndustryPeersResponse::from( + ctx.industry_peers(counter_id, market, industry_id) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get a financial report snapshot. Returns awaitable. + #[pyo3(signature = (symbol, report = None, fiscal_year = None, fiscal_period = None))] + fn financial_report_snapshot( + &self, + py: Python<'_>, + symbol: String, + report: Option, + fiscal_year: Option, + fiscal_period: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + let report_static: Option<&'static str> = match report.as_deref() { + Some("qf") => Some("qf"), + Some("saf") => Some("saf"), + Some("af") => Some("af"), + _ => None, + }; + let fiscal_period_static: Option<&'static str> = match fiscal_period.as_deref() { + Some("q1") => Some("q1"), + Some("q2") => Some("q2"), + Some("q3") => Some("q3"), + Some("q4") => Some("q4"), + Some("fy") => Some("fy"), + Some("h1") => Some("h1"), + Some("h2") => Some("h2"), + _ => None, + }; + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(FinancialReportSnapshot::from( + ctx.financial_report_snapshot( + symbol, + report_static, + fiscal_year, + fiscal_period_static, + ) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } } diff --git a/python/src/fundamental/types.rs b/python/src/fundamental/types.rs index 462458662..4ce28bd32 100644 --- a/python/src/fundamental/types.rs +++ b/python/src/fundamental/types.rs @@ -1703,3 +1703,366 @@ impl From for lb::FinancialReportPeriod { } } } + +// ── business_segments ───────────────────────────────────────────── + +/// One business segment item (latest snapshot) +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct BusinessSegmentItem { + pub name: String, + pub percent: String, +} + +impl From for BusinessSegmentItem { + fn from(v: lb::BusinessSegmentItem) -> Self { + Self { + name: v.name, + percent: v.percent, + } + } +} + +/// Business segments response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct BusinessSegments { + pub date: String, + pub total: String, + pub currency: String, + pub business: Vec, +} + +impl From for BusinessSegments { + fn from(v: lb::BusinessSegments) -> Self { + Self { + date: v.date, + total: v.total, + currency: v.currency, + business: v.business.into_iter().map(Into::into).collect(), + } + } +} + +/// One business/regional segment item in a historical snapshot +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct BusinessSegmentHistoryItem { + pub name: String, + pub percent: String, + pub value: String, +} + +impl From for BusinessSegmentHistoryItem { + fn from(v: lb::BusinessSegmentHistoryItem) -> Self { + Self { + name: v.name, + percent: v.percent, + value: v.value, + } + } +} + +/// One historical business segments snapshot +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct BusinessSegmentsHistoricalItem { + pub date: String, + pub total: String, + pub currency: String, + pub business: Vec, + pub regionals: Vec, +} + +impl From for BusinessSegmentsHistoricalItem { + fn from(v: lb::BusinessSegmentsHistoricalItem) -> Self { + Self { + date: v.date, + total: v.total, + currency: v.currency, + business: v.business.into_iter().map(Into::into).collect(), + regionals: v.regionals.into_iter().map(Into::into).collect(), + } + } +} + +/// Business segments history response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct BusinessSegmentsHistory { + pub historical: Vec, +} + +impl From for BusinessSegmentsHistory { + fn from(v: lb::BusinessSegmentsHistory) -> Self { + Self { + historical: v.historical.into_iter().map(Into::into).collect(), + } + } +} + +// ── institution_rating_views ────────────────────────────────────── + +/// One historical rating distribution snapshot +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct InstitutionRatingViewItem { + pub date: String, + pub buy: String, + pub over: String, + pub hold: String, + pub under: String, + pub sell: String, + pub total: String, +} + +impl From for InstitutionRatingViewItem { + fn from(v: lb::InstitutionRatingViewItem) -> Self { + Self { + date: v.date, + buy: v.buy, + over: v.over, + hold: v.hold, + under: v.under, + sell: v.sell, + total: v.total, + } + } +} + +/// Institution rating views response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct InstitutionRatingViews { + pub elist: Vec, +} + +impl From for InstitutionRatingViews { + fn from(v: lb::InstitutionRatingViews) -> Self { + Self { + elist: v.elist.into_iter().map(Into::into).collect(), + } + } +} + +// ── industry_rank ───────────────────────────────────────────────── + +/// One ranked industry item +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct IndustryRankItem { + pub name: String, + pub counter_id: String, + pub chg: String, + pub leading_name: String, + pub leading_ticker: String, + pub leading_chg: String, + pub value_name: String, + pub value_data: String, +} + +impl From for IndustryRankItem { + fn from(v: lb::IndustryRankItem) -> Self { + Self { + name: v.name, + counter_id: v.counter_id, + chg: v.chg, + leading_name: v.leading_name, + leading_ticker: v.leading_ticker, + leading_chg: v.leading_chg, + value_name: v.value_name, + value_data: v.value_data, + } + } +} + +/// A group of ranked industry items +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct IndustryRankGroup { + pub lists: Vec, +} + +impl From for IndustryRankGroup { + fn from(v: lb::IndustryRankGroup) -> Self { + Self { + lists: v.lists.into_iter().map(Into::into).collect(), + } + } +} + +/// Industry rank response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct IndustryRankResponse { + pub items: Vec, +} + +impl From for IndustryRankResponse { + fn from(v: lb::IndustryRankResponse) -> Self { + Self { + items: v.items.into_iter().map(Into::into).collect(), + } + } +} + +// ── industry_peers ──────────────────────────────────────────────── + +/// Top-level industry info in the peers response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct IndustryPeersTop { + pub name: String, + pub market: String, +} + +impl From for IndustryPeersTop { + fn from(v: lb::IndustryPeersTop) -> Self { + Self { + name: v.name, + market: v.market, + } + } +} + +/// A node in the recursive industry peer chain. +/// +/// `next_json` contains the child nodes serialised as a JSON string. +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct IndustryPeerNode { + pub name: String, + pub counter_id: String, + pub stock_num: i32, + pub chg: String, + pub ytd_chg: String, + /// Child nodes as a JSON string + pub next_json: String, +} + +impl From for IndustryPeerNode { + fn from(v: lb::IndustryPeerNode) -> Self { + Self { + name: v.name, + counter_id: v.counter_id, + stock_num: v.stock_num, + chg: v.chg, + ytd_chg: v.ytd_chg, + next_json: serde_json::to_string(&v.next).unwrap_or_default(), + } + } +} + +/// Industry peers response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct IndustryPeersResponse { + pub top: IndustryPeersTop, + pub chain: Option, +} + +impl From for IndustryPeersResponse { + fn from(v: lb::IndustryPeersResponse) -> Self { + Self { + top: v.top.into(), + chain: v.chain.map(Into::into), + } + } +} + +// ── financial_report_snapshot ───────────────────────────────────── + +/// A forecast metric in the financial report snapshot +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct SnapshotForecastMetric { + pub value: String, + pub yoy: String, + pub cmp_desc: String, + pub est_value: String, +} + +impl From for SnapshotForecastMetric { + fn from(v: lb::SnapshotForecastMetric) -> Self { + Self { + value: v.value, + yoy: v.yoy, + cmp_desc: v.cmp_desc, + est_value: v.est_value, + } + } +} + +/// A reported metric in the financial report snapshot +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct SnapshotReportedMetric { + pub value: String, + pub yoy: String, +} + +impl From for SnapshotReportedMetric { + fn from(v: lb::SnapshotReportedMetric) -> Self { + Self { + value: v.value, + yoy: v.yoy, + } + } +} + +/// Financial report snapshot response +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct FinancialReportSnapshot { + pub name: String, + pub ticker: String, + pub fp_start: String, + pub fp_end: String, + pub currency: String, + pub report_desc: String, + pub fo_revenue: Option, + pub fo_ebit: Option, + pub fo_eps: Option, + pub fr_revenue: Option, + pub fr_profit: Option, + pub fr_operate_cash: Option, + pub fr_invest_cash: Option, + pub fr_finance_cash: Option, + pub fr_total_assets: Option, + pub fr_total_liability: Option, + pub fr_roe_ttm: String, + pub fr_profit_margin: String, + pub fr_profit_margin_ttm: String, + pub fr_asset_turn_ttm: String, + pub fr_leverage_ttm: String, + pub fr_debt_assets_ratio: String, +} + +impl From for FinancialReportSnapshot { + fn from(v: lb::FinancialReportSnapshot) -> Self { + Self { + name: v.name, + ticker: v.ticker, + fp_start: v.fp_start, + fp_end: v.fp_end, + currency: v.currency, + report_desc: v.report_desc, + fo_revenue: v.fo_revenue.map(Into::into), + fo_ebit: v.fo_ebit.map(Into::into), + fo_eps: v.fo_eps.map(Into::into), + fr_revenue: v.fr_revenue.map(Into::into), + fr_profit: v.fr_profit.map(Into::into), + fr_operate_cash: v.fr_operate_cash.map(Into::into), + fr_invest_cash: v.fr_invest_cash.map(Into::into), + fr_finance_cash: v.fr_finance_cash.map(Into::into), + fr_total_assets: v.fr_total_assets.map(Into::into), + fr_total_liability: v.fr_total_liability.map(Into::into), + fr_roe_ttm: v.fr_roe_ttm, + fr_profit_margin: v.fr_profit_margin, + fr_profit_margin_ttm: v.fr_profit_margin_ttm, + fr_asset_turn_ttm: v.fr_asset_turn_ttm, + fr_leverage_ttm: v.fr_leverage_ttm, + fr_debt_assets_ratio: v.fr_debt_assets_ratio, + } + } +} From fefe0a9b554cf45ba8064469a7c9704a2680622b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Mon, 18 May 2026 18:06:56 +0800 Subject: [PATCH 4/6] fix(java): add missing native declarations in SdkNative for new fundamental APIs SdkNative.java was missing the 6 native method declarations that FundamentalContext.java calls, causing Java compilation failure. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../main/java/com/longbridge/SdkNative.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 1c3759544..5958eaf1f 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -390,6 +390,27 @@ public static native void fundamentalContextGetBuyback(long context, String symb public static native void fundamentalContextGetRatings(long context, String symbol, AsyncCallback callback); + public static native void fundamentalContextGetBusinessSegments(long context, String symbol, + AsyncCallback callback); + + public static native void fundamentalContextGetBusinessSegmentsHistory(long context, + Object opts, + AsyncCallback callback); + + public static native void fundamentalContextGetInstitutionRatingViews(long context, + String symbol, + AsyncCallback callback); + + public static native void fundamentalContextGetIndustryRank(long context, Object opts, + AsyncCallback callback); + + public static native void fundamentalContextGetIndustryPeers(long context, Object opts, + AsyncCallback callback); + + public static native void fundamentalContextGetFinancialReportSnapshot(long context, + Object opts, + AsyncCallback callback); + public static native void portfolioContextProfitAnalysisFlows(long context, Object opts, AsyncCallback callback); From 6358621c89fce72c896c502d783f5ed9fd662860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Mon, 18 May 2026 19:12:59 +0800 Subject: [PATCH 5/6] chore: simplify CHANGELOG [Unreleased] entry --- CHANGELOG.md | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2290229..fcdf768d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,21 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **Rust:** Six new `FundamentalContext` methods: - - `business_segments` — GET `/v1/quote/fundamentals/business-segments`: latest business segment breakdown (name, percent). - - `business_segments_history` — GET `/v1/quote/fundamentals/business-segments/history`: historical business and regional segment breakdowns with optional `report` and `cate` filters. - - `institution_rating_views` — GET `/v1/quote/ratings/institutional`: historical rating distribution time-series (buy/over/hold/under/sell/total per date). - - `industry_rank` — GET `/v1/quote/industry/rank`: industry leaderboard for a market with configurable indicator and sort direction. - - `industry_peers` — GET `/v1/quote/industries/peers`: recursive industry peer chain; accepts both symbol-style (`AAPL.US`) and raw counter IDs (`BK/US/123`). - - `financial_report_snapshot` — GET `/v1/quote/financials/earnings-snapshot`: earnings snapshot with forecast (revenue/EBIT/EPS) and reported (P&L, cash-flows, balance-sheet ratios) metrics. -- All new methods are also available on `FundamentalContextSync` (blocking API). -- **Python, Node.js, Java, C, C++:** All six new `FundamentalContext` methods ported to every non-Rust SDK, including: - - New response types: `BusinessSegmentItem`, `BusinessSegments`, `BusinessSegmentHistoryItem`, `BusinessSegmentsHistoricalItem`, `BusinessSegmentsHistory`, `InstitutionRatingViewItem`, `InstitutionRatingViews`, `IndustryRankItem`, `IndustryRankGroup`, `IndustryRankResponse`, `IndustryPeersTop`, `IndustryPeerNode` (with `next_json` serialising the recursive child list), `IndustryPeersResponse`, `SnapshotForecastMetric`, `SnapshotReportedMetric`, `FinancialReportSnapshot`. - - **Python:** Sync (`FundamentalContext`) and async (`AsyncFundamentalContext`) variants; full type stubs in `openapi.pyi`. - - **Node.js:** `index.d.ts` regenerated with all new types and methods. - - **Java:** JNI bindings, Java source classes, and options POJOs; new methods on `FundamentalContext`. - - **C:** New C structs and functions in `longbridge.h` (regenerated via cbindgen). - - **C++:** New struct types in `types.hpp`, convert functions in `convert.hpp`, method declarations in `fundamental_context.hpp`, and implementations in `fundamental_context.cpp`. +- **All languages:** Six new `FundamentalContext` methods (Rust/Python/Node.js/Java/C/C++): + - `business_segments` — GET `/v1/quote/fundamentals/business-segments` + - `business_segments_history` — GET `/v1/quote/fundamentals/business-segments/history` + - `institution_rating_views` — GET `/v1/quote/ratings/institutional` + - `industry_rank` — GET `/v1/quote/industry/rank` + - `industry_peers` — GET `/v1/quote/industries/peers` + - `financial_report_snapshot` — GET `/v1/quote/financials/earnings-snapshot` # [4.1.0] From e42ead88f9d359b6d52451bb94429b511a57ba1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Mon, 18 May 2026 19:14:03 +0800 Subject: [PATCH 6/6] chore: update CHANGELOG [Unreleased] with final method descriptions --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcdf768d1..6a2d484c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **All languages:** Six new `FundamentalContext` methods (Rust/Python/Node.js/Java/C/C++): - - `business_segments` — GET `/v1/quote/fundamentals/business-segments` - - `business_segments_history` — GET `/v1/quote/fundamentals/business-segments/history` - - `institution_rating_views` — GET `/v1/quote/ratings/institutional` - - `industry_rank` — GET `/v1/quote/industry/rank` - - `industry_peers` — GET `/v1/quote/industries/peers` - - `financial_report_snapshot` — GET `/v1/quote/financials/earnings-snapshot` +- **All languages (Rust/Python/Node.js/Java/C/C++):** Six new `FundamentalContext` methods: + - `BusinessSegments` — GET `/v1/quote/fundamentals/business-segments`: latest business segment breakdown. + - `BusinessSegmentsHistory` — GET `/v1/quote/fundamentals/business-segments/history`: historical business and regional segment breakdowns with optional `report` and `cate` filters. + - `InstitutionRatingViews` — GET `/v1/quote/ratings/institutional`: historical rating distribution time-series (buy/over/hold/under/sell per date). + - `IndustryRank` — GET `/v1/quote/industry/rank`: industry leaderboard; exposes `IndustryRankIndicator` and `IndustryRankSortType` enum constants. + - `IndustryPeers` — GET `/v1/quote/industries/peers`: recursive industry peer chain; accepts both symbol-style (`AAPL.US`) and raw counter IDs (`BK/US/123`). + - `FinancialReportSnapshot` — GET `/v1/quote/financials/earnings-snapshot`: earnings snapshot with forecast and reported metrics. # [4.1.0]