diff --git a/CHANGELOG.md b/CHANGELOG.md index 707c9ed47..a1329525e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ 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). +## [4.3.1] - 2026-06-12 + +### Added + +- **All languages:** `FundamentalContext` gains `macroeconomic_indicators(country, offset, limit)` — list macroeconomic indicators via `GET /v1/quote/macrodata`; filter by country (`MacroeconomicCountry::HongKong / China / UnitedStates / EuroZone / Japan / Singapore`); response includes `count` (total matching) +- **All languages:** `FundamentalContext` gains `macroeconomic(indicator_code, start_date, end_date, offset, limit)` — historical data for a specific indicator via `GET /v1/quote/macrodata/{indicator_code}`; `start_date` / `end_date` accept `"YYYY-MM-DD"` strings; response includes `count` (total data points) +- New types: `MultiLanguageText`, `MacroeconomicCountry`, `MacroeconomicImportance`, `MacroeconomicIndicator`, `MacroeconomicIndicatorListResponse`, `Macroeconomic`, `MacroeconomicResponse` + +### Fixed + +- `MacroeconomicIndicator.describe` / `name` / `MacroeconomicResponse.info`: handle `null` responses from API without deserializing error + ## [4.3.0] ### Added diff --git a/Cargo.toml b/Cargo.toml index 815991451..b21cdf2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["rust", "python", "nodejs", "java", "c"] [workspace.package] -version = "4.3.0" +version = "4.3.1" edition = "2024" [profile.release] diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 4ed90791a..dadca70c6 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -451,6 +451,14 @@ public static native void fundamentalContextGetFinancialReportSnapshot(long cont Object opts, AsyncCallback callback); + public static native void fundamentalContextMacroeconomicIndicators(long context, + Object country, Object offset, Object limit, + AsyncCallback callback); + + public static native void fundamentalContextMacroeconomic(long context, + Object indicatorCode, Object startTime, Object endTime, Object offset, Object limit, + AsyncCallback callback); + public static native void portfolioContextProfitAnalysisFlows(long context, Object opts, AsyncCallback callback); 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 8c0a7299a..4c22e256a 100644 --- a/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java @@ -334,4 +334,24 @@ public CompletableFuture getValuationComparison(Val SdkNative.fundamentalContextValuationComparison(raw, opts, callback); }); } + + /** + * List macroeconomic indicators. + * country: ISO country code string (e.g. "US", "CN", "EU"); pass null for all countries. + */ + public CompletableFuture getMacroeconomicIndicators(String country, Integer offset, Integer limit) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextMacroeconomicIndicators(raw, country, offset, limit, callback); + }); + } + + /** + * Get historical data for a macroeconomic indicator. + * startDate and endDate are date strings in "YYYY-MM-DD" format. + */ + public CompletableFuture getMacroeconomic(String indicatorCode, String startDate, String endDate, Integer offset, Integer limit) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextMacroeconomic(raw, indicatorCode, startDate, endDate, offset, limit, callback); + }); + } } diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java b/java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java new file mode 100644 index 000000000..caca0ccda --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java @@ -0,0 +1,15 @@ +package com.longbridge.fundamental; + +/** One historical data point for a macroeconomic indicator. */ +public class Macroeconomic { + /** Statistical period (e.g. 2024-Q1, 2024-03). */ + public String period; + public String releaseAt; + public String actualValue; + public String previousValue; + public String forecastValue; + public String revisedValue; + public String nextReleaseAt; + public MultiLanguageText unit; + public MultiLanguageText unitPrefix; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java new file mode 100644 index 000000000..c83d4fd5a --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java @@ -0,0 +1,19 @@ +package com.longbridge.fundamental; + +/** Metadata for one macroeconomic indicator. */ +public class MacroeconomicIndicator { + /** External vendor code (input to getEconomicIndicator). */ + public String indicatorCode; + public String sourceOrg; + public String country; + public MultiLanguageText name; + public String adjustmentFactor; + /** Release periodicity (e.g. monthly / quarterly). */ + public String periodicity; + public String category; + public MultiLanguageText describe; + /** Importance — higher is more important. */ + public int importance; + /** Start date of data coverage (unix timestamp string). */ + public String startDate; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java new file mode 100644 index 000000000..b62e637c6 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java @@ -0,0 +1,8 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getMacroeconomicIndicators}. */ +public class MacroeconomicIndicatorListResponse { + public MacroeconomicIndicator[] data; + /** Total number of indicators matching the query. */ + public int count; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java new file mode 100644 index 000000000..e84c46569 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java @@ -0,0 +1,9 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getMacroeconomic}. */ +public class MacroeconomicResponse { + public MacroeconomicIndicator info; + public Macroeconomic[] data; + /** Total number of historical data points. */ + public int count; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java new file mode 100644 index 000000000..2bb9bf361 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java @@ -0,0 +1,8 @@ +package com.longbridge.fundamental; + +/** Localized text in simplified Chinese, traditional Chinese, and English. */ +public class MultiLanguageText { + public String english; + public String simplifiedChinese; + public String traditionalChinese; +} diff --git a/java/src/fundamental_context.rs b/java/src/fundamental_context.rs index 95c39a995..163b2fd45 100644 --- a/java/src/fundamental_context.rs +++ b/java/src/fundamental_context.rs @@ -262,3 +262,69 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextVa Ok(()) }) } + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextMacroeconomicIndicators( + mut env: JNIEnv, + _class: JClass, + context: i64, + country: JObject, + offset: JObject, + limit: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let country: Option = FromJValue::from_jvalue(env, country.into())?; + let country = country.and_then(|s| { + use longbridge::fundamental::MacroeconomicCountry::*; + match s.as_str() { + "HK" | "Hong Kong SAR China" => Some(HongKong), + "CN" | "China (Mainland)" => Some(China), + "US" | "United States" => Some(UnitedStates), + "EU" | "Euro Zone" => Some(EuroZone), + "JP" | "Japan" => Some(Japan), + "SG" | "Singapore" => Some(Singapore), + _ => None, + } + }); + let offset: Option = FromJValue::from_jvalue(env, offset.into())?; + let limit: Option = FromJValue::from_jvalue(env, limit.into())?; + async_util::execute(env, callback, async move { + Ok(context + .ctx + .macroeconomic_indicators(country, offset, limit) + .await?) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextMacroeconomic( + mut env: JNIEnv, + _class: JClass, + context: i64, + indicator_code: JObject, + start_time: JObject, + end_time: JObject, + offset: JObject, + limit: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let indicator_code: String = FromJValue::from_jvalue(env, indicator_code.into())?; + let start_date: Option = FromJValue::from_jvalue(env, start_time.into())?; + let end_date: Option = FromJValue::from_jvalue(env, end_time.into())?; + let offset: Option = FromJValue::from_jvalue(env, offset.into())?; + let limit: Option = FromJValue::from_jvalue(env, limit.into())?; + async_util::execute(env, callback, async move { + Ok(context + .ctx + .macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await?) + })?; + Ok(()) + }) +} diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 141d5f839..285f3611c 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -2698,6 +2698,66 @@ impl_java_class!( ] ); +impl_java_class!( + "com/longbridge/fundamental/MultiLanguageText", + longbridge::fundamental::MultiLanguageText, + [english, simplified_chinese, traditional_chinese] +); + +impl_java_class!( + "com/longbridge/fundamental/MacroeconomicIndicator", + longbridge::fundamental::MacroeconomicIndicator, + [ + indicator_code, + source_org, + country, + name, + adjustment_factor, + periodicity, + category, + describe, + importance, + start_date + ] +); + +impl_java_class!( + "com/longbridge/fundamental/Macroeconomic", + longbridge::fundamental::Macroeconomic, + [ + period, + release_at, + actual_value, + previous_value, + forecast_value, + revised_value, + next_release_at, + unit, + unit_prefix + ] +); + +impl_java_class!( + "com/longbridge/fundamental/MacroeconomicIndicatorListResponse", + longbridge::fundamental::MacroeconomicIndicatorListResponse, + [ + #[java(objarray)] + data, + count + ] +); + +impl_java_class!( + "com/longbridge/fundamental/MacroeconomicResponse", + longbridge::fundamental::MacroeconomicResponse, + [ + info, + #[java(objarray)] + data, + count + ] +); + // ── MarketContext: top movers / rank ────────────────────────────── impl_java_class!( diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index c7f7b1eb4..56b157e18 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -618,6 +618,10 @@ export declare class FundamentalContext { * industry) */ etfAssetAllocation(symbol: string): Promise + /** List macroeconomic indicators */ + macroeconomicIndicators(country?: MacroeconomicCountry | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise + /** Get historical data for a macroeconomic indicator */ + macroeconomic(indicatorCode: string, startDate?: string | undefined | null, endDate?: string | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise } /** Fund position */ @@ -4473,6 +4477,65 @@ export declare const enum Language { EN = 2 } +/** One historical data point for a macroeconomic indicator */ +export interface Macroeconomic { + period: string + /** Release datetime (unix timestamp in seconds; null if unset) */ + releaseAt?: number + actualValue: string + previousValue: string + forecastValue: string + revisedValue: string + /** Next release datetime (unix timestamp in seconds; null if unset) */ + nextReleaseAt?: number + unit: MultiLanguageText + unitPrefix: MultiLanguageText +} + +/** Country code for filtering macroeconomic indicators */ +export declare const enum MacroeconomicCountry { + /** Hong Kong SAR China */ + HongKong = 0, + /** China (Mainland) */ + China = 1, + /** United States */ + UnitedStates = 2, + /** Euro Zone */ + EuroZone = 3, + /** Japan */ + Japan = 4, + /** Singapore */ + Singapore = 5 +} + +/** Metadata for one macroeconomic indicator */ +export interface MacroeconomicIndicator { + indicatorCode: string + sourceOrg: string + country: string + name: MultiLanguageText + adjustmentFactor: string + periodicity: string + category: string + describe: MultiLanguageText + importance: number + /** Start date of data coverage (unix timestamp in seconds; null if unset) */ + startDate?: number +} + +/** Response for macroeconomic_indicators */ +export interface MacroeconomicIndicatorListResponse { + data: Array + count: number +} + +/** Response for macroeconomic */ +export interface MacroeconomicResponse { + info: MacroeconomicIndicator + data: Array + count: number +} + export declare const enum Market { /** Unknown */ Unknown = 0, @@ -4515,6 +4578,13 @@ export interface MarketTimeItem { delaySubStatus: number } +/** Localized text in simplified Chinese, traditional Chinese, and English */ +export interface MultiLanguageText { + english: string + simplifiedChinese: string + traditionalChinese: string +} + /** Options for listing topics created by the current authenticated user */ export interface MyTopicsRequest { /** Page number (default 1) */ diff --git a/nodejs/index.js b/nodejs/index.js index a8cb5ec7a..fa943da93 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -689,6 +689,7 @@ module.exports.FlowDirection = nativeBinding.FlowDirection module.exports.Granularity = nativeBinding.Granularity module.exports.InstitutionRecommend = nativeBinding.InstitutionRecommend module.exports.Language = nativeBinding.Language +module.exports.MacrodataCountry = nativeBinding.MacrodataCountry module.exports.Market = nativeBinding.Market module.exports.OptionDirection = nativeBinding.OptionDirection module.exports.OptionType = nativeBinding.OptionType diff --git a/nodejs/src/fundamental/context.rs b/nodejs/src/fundamental/context.rs index ca1cc3528..3fdc33587 100644 --- a/nodejs/src/fundamental/context.rs +++ b/nodejs/src/fundamental/context.rs @@ -287,4 +287,38 @@ impl FundamentalContext { .map_err(ErrorNewType)? .into()) } + + /// List macroeconomic indicators + #[napi] + pub async fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> Result { + Ok(self + .ctx + .macroeconomic_indicators(country.map(Into::into), offset, limit) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical data for a macroeconomic indicator + #[napi] + pub async fn macroeconomic( + &self, + indicator_code: String, + start_date: Option, + end_date: Option, + offset: Option, + limit: Option, + ) -> Result { + Ok(self + .ctx + .macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await + .map_err(ErrorNewType)? + .into()) + } } diff --git a/nodejs/src/fundamental/types.rs b/nodejs/src/fundamental/types.rs index 37664aba5..e66916ff5 100644 --- a/nodejs/src/fundamental/types.rs +++ b/nodejs/src/fundamental/types.rs @@ -1902,3 +1902,158 @@ impl From for AssetAllocationResponse { } } } + +// ── economic_indicator ───────────────────────────────────────────────────── + +/// Localized text in simplified Chinese, traditional Chinese, and English +#[napi_derive::napi(object)] +#[derive(Debug, Clone, Default)] +pub struct MultiLanguageText { + pub english: String, + pub simplified_chinese: String, + pub traditional_chinese: String, +} + +impl From for MultiLanguageText { + fn from(v: lb::MultiLanguageText) -> Self { + Self { + english: v.english, + simplified_chinese: v.simplified_chinese, + traditional_chinese: v.traditional_chinese, + } + } +} + +/// Country code for filtering macroeconomic indicators +#[napi_derive::napi] +#[derive(Debug, Copy, Clone)] +pub enum MacroeconomicCountry { + /// Hong Kong SAR China + HongKong, + /// China (Mainland) + China, + /// United States + UnitedStates, + /// Euro Zone + EuroZone, + /// Japan + Japan, + /// Singapore + Singapore, +} + +impl From for lb::MacroeconomicCountry { + fn from(v: MacroeconomicCountry) -> Self { + match v { + MacroeconomicCountry::HongKong => lb::MacroeconomicCountry::HongKong, + MacroeconomicCountry::China => lb::MacroeconomicCountry::China, + MacroeconomicCountry::UnitedStates => lb::MacroeconomicCountry::UnitedStates, + MacroeconomicCountry::EuroZone => lb::MacroeconomicCountry::EuroZone, + MacroeconomicCountry::Japan => lb::MacroeconomicCountry::Japan, + MacroeconomicCountry::Singapore => lb::MacroeconomicCountry::Singapore, + } + } +} + +/// Response for macroeconomic_indicators +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct MacroeconomicIndicatorListResponse { + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicIndicatorListResponse { + fn from(v: lb::MacroeconomicIndicatorListResponse) -> Self { + Self { + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} + +/// Metadata for one macroeconomic indicator +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct MacroeconomicIndicator { + pub indicator_code: String, + pub source_org: String, + pub country: String, + pub name: MultiLanguageText, + pub adjustment_factor: String, + pub periodicity: String, + pub category: String, + pub describe: MultiLanguageText, + pub importance: i32, + /// Start date of data coverage (unix timestamp in seconds; null if unset) + pub start_date: Option, +} + +impl From for MacroeconomicIndicator { + fn from(v: lb::MacroeconomicIndicator) -> Self { + Self { + indicator_code: v.indicator_code, + source_org: v.source_org, + country: v.country, + name: v.name.into(), + adjustment_factor: v.adjustment_factor, + periodicity: v.periodicity, + category: v.category, + describe: v.describe.into(), + importance: v.importance, + start_date: v.start_date.map(|dt| dt.unix_timestamp()), + } + } +} + +/// One historical data point for a macroeconomic indicator +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct Macroeconomic { + pub period: String, + /// Release datetime (unix timestamp in seconds; null if unset) + pub release_at: Option, + pub actual_value: String, + pub previous_value: String, + pub forecast_value: String, + pub revised_value: String, + /// Next release datetime (unix timestamp in seconds; null if unset) + pub next_release_at: Option, + pub unit: MultiLanguageText, + pub unit_prefix: MultiLanguageText, +} + +impl From for Macroeconomic { + fn from(v: lb::Macroeconomic) -> Self { + Self { + period: v.period, + release_at: v.release_at.map(|dt| dt.unix_timestamp()), + actual_value: v.actual_value, + previous_value: v.previous_value, + forecast_value: v.forecast_value, + revised_value: v.revised_value, + next_release_at: v.next_release_at.map(|dt| dt.unix_timestamp()), + unit: v.unit.into(), + unit_prefix: v.unit_prefix.into(), + } + } +} + +/// Response for macroeconomic +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct MacroeconomicResponse { + pub info: MacroeconomicIndicator, + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicResponse { + fn from(v: lb::MacroeconomicResponse) -> Self { + Self { + info: v.info.into(), + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 90bc788e3..8186b1c8f 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -9922,6 +9922,44 @@ class FundamentalContext: """ ... + def macroeconomic_indicators( + self, + offset: int | None = None, + limit: int | None = None, + ) -> list["MacroeconomicIndicator"]: + """ + List macroeconomic indicators. + + Args: + offset: Pagination offset (default 0) + limit: Page size (default 100, max 1000) + + Returns: + List of :class:`MacroeconomicIndicator` + """ + ... + + def macroeconomic( + self, + indicator_code: str, + start_date: str | None = None, + end_date: str | None = None, + limit: int | None = None, + ) -> "MacroeconomicResponse": + """ + Get historical data for a macroeconomic indicator. + + Args: + indicator_code: External vendor code from ``macroeconomic_indicators`` + start_date: Start date in ``"YYYY-MM-DD"`` format (optional) + end_date: End date in ``"YYYY-MM-DD"`` format (optional) + limit: Max records to return (default 100, max 100) + + Returns: + :class:`MacroeconomicResponse` + """ + ... + # ── FundamentalContext new response types ───────────────────────── @@ -10065,6 +10103,54 @@ class AssetAllocationResponse: """Asset allocation groups""" +class MultiLanguageText: + """Localized text in simplified Chinese, traditional Chinese, and English.""" + + english: str + simplified_chinese: str + traditional_chinese: str + + +class MacroeconomicIndicator: + """Metadata for one macroeconomic indicator.""" + + indicator_code: str + """External vendor code (input to macroeconomic)""" + source_org: str + country: str + name: MultiLanguageText + adjustment_factor: str + periodicity: str + """Release periodicity (e.g. monthly / quarterly)""" + category: str + describe: MultiLanguageText + importance: int + start_date: datetime | None + """Start date of data coverage""" + + +class Macroeconomic: + """One historical data point for a macroeconomic indicator.""" + + period: str + """Statistical period (e.g. 2024-Q1, 2024-03)""" + release_at: datetime | None + actual_value: str + previous_value: str + forecast_value: str + revised_value: str + next_release_at: datetime | None + unit: MultiLanguageText + unit_prefix: MultiLanguageText + + +class MacroeconomicResponse: + """Response for macroeconomic.""" + + info: MacroeconomicIndicator + data: list[Macroeconomic] + + # ── MarketContext ───────────────────────────────────────────────── class MarketTimeItem: diff --git a/python/src/fundamental/context.rs b/python/src/fundamental/context.rs index e65d93fd2..5d4dd8639 100644 --- a/python/src/fundamental/context.rs +++ b/python/src/fundamental/context.rs @@ -208,4 +208,34 @@ impl FundamentalContext { .map_err(ErrorNewType)? .into()) } + + /// List macroeconomic indicators. + fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> PyResult { + Ok(self + .ctx + .macroeconomic_indicators(country.map(Into::into), offset, limit) + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical data for a macroeconomic indicator. + fn macroeconomic( + &self, + indicator_code: String, + start_date: Option, + end_date: Option, + offset: Option, + limit: Option, + ) -> PyResult { + Ok(self + .ctx + .macroeconomic(indicator_code, start_date, end_date, offset, limit) + .map_err(ErrorNewType)? + .into()) + } } diff --git a/python/src/fundamental/context_async.rs b/python/src/fundamental/context_async.rs index eed4d6142..1fe4f9d5c 100644 --- a/python/src/fundamental/context_async.rs +++ b/python/src/fundamental/context_async.rs @@ -317,4 +317,44 @@ impl AsyncFundamentalContext { }) .map(|b| b.unbind()) } + + /// List macroeconomic indicators. Returns awaitable. + fn macroeconomic_indicators( + &self, + py: Python<'_>, + country: Option, + offset: Option, + limit: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(MacroeconomicIndicatorListResponse::from( + ctx.macroeconomic_indicators(country.map(Into::into), offset, limit) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get historical data for a macroeconomic indicator. Returns awaitable. + fn macroeconomic( + &self, + py: Python<'_>, + indicator_code: String, + start_date: Option, + end_date: Option, + offset: Option, + limit: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(MacroeconomicResponse::from( + ctx.macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } } diff --git a/python/src/fundamental/mod.rs b/python/src/fundamental/mod.rs index a788e712e..810101751 100644 --- a/python/src/fundamental/mod.rs +++ b/python/src/fundamental/mod.rs @@ -74,6 +74,12 @@ pub(crate) fn register_types(parent: &Bound) -> PyResult<()> { parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; Ok(()) diff --git a/python/src/fundamental/types.rs b/python/src/fundamental/types.rs index 3a8f6ed59..c541cb7c9 100644 --- a/python/src/fundamental/types.rs +++ b/python/src/fundamental/types.rs @@ -1965,3 +1965,149 @@ impl From for AssetAllocationResponse { } } } + +// ── economic_indicator ───────────────────────────────────────────────────── + +/// Localized text in simplified Chinese, traditional Chinese, and English +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone, Default)] +pub(crate) struct MultiLanguageText { + pub english: String, + pub simplified_chinese: String, + pub traditional_chinese: String, +} + +impl From for MultiLanguageText { + fn from(v: lb::MultiLanguageText) -> Self { + Self { + english: v.english, + simplified_chinese: v.simplified_chinese, + traditional_chinese: v.traditional_chinese, + } + } +} + +/// Country code for filtering macroeconomic indicators +#[pyclass] +#[derive(Debug, Copy, Clone)] +pub(crate) enum MacroeconomicCountry { + HongKong, + China, + UnitedStates, + EuroZone, + Japan, + Singapore, +} + +impl From for lb::MacroeconomicCountry { + fn from(v: MacroeconomicCountry) -> Self { + match v { + MacroeconomicCountry::HongKong => lb::MacroeconomicCountry::HongKong, + MacroeconomicCountry::China => lb::MacroeconomicCountry::China, + MacroeconomicCountry::UnitedStates => lb::MacroeconomicCountry::UnitedStates, + MacroeconomicCountry::EuroZone => lb::MacroeconomicCountry::EuroZone, + MacroeconomicCountry::Japan => lb::MacroeconomicCountry::Japan, + MacroeconomicCountry::Singapore => lb::MacroeconomicCountry::Singapore, + } + } +} + +/// Response for macroeconomic_indicators +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct MacroeconomicIndicatorListResponse { + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicIndicatorListResponse { + fn from(v: lb::MacroeconomicIndicatorListResponse) -> Self { + Self { + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} + +/// Metadata for one macroeconomic indicator +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct MacroeconomicIndicator { + pub indicator_code: String, + pub source_org: String, + pub country: String, + pub name: MultiLanguageText, + pub adjustment_factor: String, + pub periodicity: String, + pub category: String, + pub describe: MultiLanguageText, + pub importance: i32, + pub start_date: Option, +} + +impl From for MacroeconomicIndicator { + fn from(v: lb::MacroeconomicIndicator) -> Self { + Self { + indicator_code: v.indicator_code, + source_org: v.source_org, + country: v.country, + name: v.name.into(), + adjustment_factor: v.adjustment_factor, + periodicity: v.periodicity, + category: v.category, + describe: v.describe.into(), + importance: v.importance, + start_date: v.start_date.map(crate::time::PyOffsetDateTimeWrapper), + } + } +} + +/// One historical data point for a macroeconomic indicator +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct Macroeconomic { + pub period: String, + pub release_at: Option, + pub actual_value: String, + pub previous_value: String, + pub forecast_value: String, + pub revised_value: String, + pub next_release_at: Option, + pub unit: MultiLanguageText, + pub unit_prefix: MultiLanguageText, +} + +impl From for Macroeconomic { + fn from(v: lb::Macroeconomic) -> Self { + Self { + period: v.period, + release_at: v.release_at.map(crate::time::PyOffsetDateTimeWrapper), + actual_value: v.actual_value, + previous_value: v.previous_value, + forecast_value: v.forecast_value, + revised_value: v.revised_value, + next_release_at: v.next_release_at.map(crate::time::PyOffsetDateTimeWrapper), + unit: v.unit.into(), + unit_prefix: v.unit_prefix.into(), + } + } +} + +/// Response for macroeconomic +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct MacroeconomicResponse { + pub info: MacroeconomicIndicator, + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicResponse { + fn from(v: lb::MacroeconomicResponse) -> Self { + Self { + info: v.info.into(), + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} diff --git a/rust/src/blocking/fundamental.rs b/rust/src/blocking/fundamental.rs index e2fb48d04..d580a0484 100644 --- a/rust/src/blocking/fundamental.rs +++ b/rust/src/blocking/fundamental.rs @@ -290,4 +290,31 @@ impl FundamentalContextSync { self.rt .call(move |ctx| async move { ctx.etf_asset_allocation(symbol).await }) } + + /// List macroeconomic indicators + pub fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> Result { + self.rt.call(move |ctx| async move { + ctx.macroeconomic_indicators(country, offset, limit).await + }) + } + + /// Get historical data for a macroeconomic indicator + pub fn macroeconomic( + &self, + indicator_code: impl Into + Send + 'static, + start_date: Option + Send + 'static>, + end_date: Option + Send + 'static>, + offset: Option, + limit: Option, + ) -> Result { + self.rt.call(move |ctx| async move { + ctx.macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await + }) + } } diff --git a/rust/src/fundamental/context.rs b/rust/src/fundamental/context.rs index a5d9e64b5..be0212c9c 100644 --- a/rust/src/fundamental/context.rs +++ b/rust/src/fundamental/context.rs @@ -829,4 +829,93 @@ impl FundamentalContext { ) .await } + + // ── macroeconomic ──────────────────────────────────────────────── + + /// List macroeconomic indicators. + /// + /// Pass `country` to filter by country code (e.g. + /// `MacroeconomicCountry::UnitedStates`). + /// + /// Path: `GET /v1/quote/macrodata` + pub async fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> Result { + #[derive(Serialize)] + struct Query { + #[serde(skip_serializing_if = "Option::is_none")] + country: Option, + #[serde(skip_serializing_if = "Option::is_none")] + offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option, + } + let country_str = country.map(|c| { + match c { + MacroeconomicCountry::HongKong => "Hong Kong SAR China", + MacroeconomicCountry::China => "China (Mainland)", + MacroeconomicCountry::UnitedStates => "United States", + MacroeconomicCountry::EuroZone => "Euro Zone", + MacroeconomicCountry::Japan => "Japan", + MacroeconomicCountry::Singapore => "Singapore", + } + .to_string() + }); + self.get( + "/v1/quote/macrodata", + Query { + country: country_str, + offset, + limit, + }, + ) + .await + } + + /// Get historical data for a macroeconomic indicator. + /// + /// `start_date` and `end_date` are date strings in `"YYYY-MM-DD"` format. + /// `start_date` is sent as `YYYY-MM-DDT00:00:00Z`; `end_date` is sent as + /// `YYYY-MM-DDT23:59:59Z`. + /// + /// Path: `GET /v1/quote/macrodata/{indicator_code}` + pub async fn macroeconomic( + &self, + indicator_code: impl Into, + start_date: Option>, + end_date: Option>, + offset: Option, + limit: Option, + ) -> Result { + #[derive(Serialize)] + struct Query { + #[serde(skip_serializing_if = "Option::is_none")] + start_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + end_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option, + } + let path = format!("/v1/quote/macrodata/{}", indicator_code.into()); + Ok(self + .0 + .http_cli + .request(Method::GET, path) + .query_params(Query { + start_time: start_date.map(|d| format!("{}T00:00:00Z", d.into())), + end_time: end_date.map(|d| format!("{}T23:59:59Z", d.into())), + offset, + limit, + }) + .response::>() + .send() + .with_subscriber(self.0.log_subscriber.clone()) + .await? + .0) + } } diff --git a/rust/src/fundamental/types.rs b/rust/src/fundamental/types.rs index 929f8ce98..9bee91e22 100644 --- a/rust/src/fundamental/types.rs +++ b/rust/src/fundamental/types.rs @@ -1562,3 +1562,160 @@ pub struct AssetAllocationResponse { #[serde(default)] pub info: Vec, } + +// ── macroeconomic ───────────────────────────────────────────────────── + +/// Country for filtering macroeconomic indicators +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum MacroeconomicCountry { + /// Hong Kong SAR China + #[serde(rename = "Hong Kong SAR China")] + HongKong, + /// China (Mainland) + #[serde(rename = "China (Mainland)")] + China, + /// United States + #[serde(rename = "United States")] + UnitedStates, + /// Euro Zone + #[serde(rename = "Euro Zone")] + EuroZone, + /// Japan + #[serde(rename = "Japan")] + Japan, + /// Singapore + #[serde(rename = "Singapore")] + Singapore, +} + +/// Importance level of a macroeconomic indicator +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum MacroeconomicImportance { + /// Low importance + Low = 1, + /// Medium importance + Medium = 2, + /// High importance + High = 3, +} + +impl MacroeconomicImportance { + /// Convert from raw API integer value + pub fn from_i32(v: i32) -> Option { + match v { + 1 => Some(Self::Low), + 2 => Some(Self::Medium), + 3 => Some(Self::High), + _ => None, + } + } +} + +/// Localized text in simplified Chinese, traditional Chinese, and English +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct MultiLanguageText { + /// English + #[serde(default)] + pub english: String, + /// Simplified Chinese + #[serde(default)] + pub simplified_chinese: String, + /// Traditional Chinese + #[serde(default)] + pub traditional_chinese: String, +} + +/// Metadata for one macroeconomic indicator +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct MacroeconomicIndicator { + /// External vendor code (used as input to `macroeconomic`) + pub indicator_code: String, + /// Publishing organisation + #[serde(default)] + pub source_org: String, + /// Country + #[serde(default)] + pub country: String, + /// Indicator name (multilingual) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub name: MultiLanguageText, + /// Adjustment factor + #[serde(default)] + pub adjustment_factor: String, + /// Release periodicity (e.g. `monthly` / `quarterly`) + #[serde(default)] + pub periodicity: String, + /// Indicator category + #[serde(default)] + pub category: String, + /// Description (multilingual) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub describe: MultiLanguageText, + /// Importance — higher is more important + #[serde(default)] + pub importance: i32, + /// Start date of data coverage + #[serde( + default, + with = "crate::serde_utils::rfc3339_opt", + rename = "start_date" + )] + pub start_date: Option, +} + +/// Response for [`crate::FundamentalContext::macroeconomic_indicators`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MacroeconomicIndicatorListResponse { + /// Indicator list + #[serde(default, rename = "list")] + pub data: Vec, + /// Total number of indicators matching the query + #[serde(default)] + pub count: i32, +} + +/// One historical data point for a macroeconomic indicator +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Macroeconomic { + /// Statistical period (e.g. `2024-Q1`, `2024-03`) + #[serde(default)] + pub period: String, + /// Release datetime + #[serde(default, with = "crate::serde_utils::rfc3339_opt")] + pub release_at: Option, + /// Actual value + #[serde(default)] + pub actual_value: String, + /// Previous value + #[serde(default)] + pub previous_value: String, + /// Forecast value (market consensus) + #[serde(default)] + pub forecast_value: String, + /// Revised value + #[serde(default)] + pub revised_value: String, + /// Next release datetime + #[serde(default, with = "crate::serde_utils::rfc3339_opt")] + pub next_release_at: Option, + /// Unit (multilingual) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub unit: MultiLanguageText, + /// Unit prefix / data scale (multilingual, e.g. millions / billions) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub unit_prefix: MultiLanguageText, +} + +/// Response for [`crate::FundamentalContext::macroeconomic`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MacroeconomicResponse { + /// Indicator metadata + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub info: MacroeconomicIndicator, + /// Historical data points + #[serde(default)] + pub data: Vec, + /// Total number of historical data points + #[serde(default)] + pub count: i32, +} diff --git a/rust/src/serde_utils.rs b/rust/src/serde_utils.rs index 85858134b..21512bf95 100644 --- a/rust/src/serde_utils.rs +++ b/rust/src/serde_utils.rs @@ -462,3 +462,12 @@ where StringOrInt::String(s) => s.parse::().map_err(serde::de::Error::custom), } } + +/// Deserializer that maps a JSON `null` to the type's `Default` value. +pub(crate) fn null_as_default<'de, D, T>(d: D) -> Result +where + D: Deserializer<'de>, + T: Deserialize<'de> + Default, +{ + Ok(Option::::deserialize(d)?.unwrap_or_default()) +}