1use std::sync::Arc;
2
3use longbridge_httpcli::{DcRegion, HttpClient, Json, Method};
4use serde::{Serialize, de::DeserializeOwned};
5use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
6
7use crate::{Config, Market, Result, fundamental::types::*};
8
9fn unix_secs_str_to_rfc3339(s: &str) -> String {
11 s.parse::<i64>()
12 .ok()
13 .and_then(|ts| time::OffsetDateTime::from_unix_timestamp(ts).ok())
14 .map(|dt| {
15 use time::format_description::well_known::Rfc3339;
16 dt.format(&Rfc3339).unwrap_or_default()
17 })
18 .unwrap_or_else(|| s.to_string())
19}
20
21struct InnerFundamentalContext {
22 http_cli: HttpClient,
23 log_subscriber: Arc<dyn Subscriber + Send + Sync>,
24}
25
26impl Drop for InnerFundamentalContext {
27 fn drop(&mut self) {
28 dispatcher::with_default(&self.log_subscriber.clone().into(), || {
29 tracing::info!("fundamental context dropped");
30 });
31 }
32}
33
34#[derive(Clone)]
37pub struct FundamentalContext(Arc<InnerFundamentalContext>);
38
39impl FundamentalContext {
40 pub fn new(config: Arc<Config>) -> Self {
42 let log_subscriber = config.create_log_subscriber("fundamental");
43 dispatcher::with_default(&log_subscriber.clone().into(), || {
44 tracing::info!(language = ?config.language, "creating fundamental context");
45 });
46 let ctx = Self(Arc::new(InnerFundamentalContext {
47 http_cli: config.create_http_client(),
48 log_subscriber,
49 }));
50 dispatcher::with_default(&ctx.0.log_subscriber.clone().into(), || {
51 tracing::info!("fundamental context created");
52 });
53 ctx
54 }
55
56 #[inline]
58 pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
59 self.0.log_subscriber.clone()
60 }
61
62 async fn get<R, Q>(&self, path: &'static str, query: Q) -> Result<R>
63 where
64 R: DeserializeOwned + Send + Sync + 'static,
65 Q: Serialize + Send + Sync,
66 {
67 Ok(self
68 .0
69 .http_cli
70 .request(Method::GET, path)
71 .query_params(query)
72 .response::<Json<R>>()
73 .send()
74 .with_subscriber(self.0.log_subscriber.clone())
75 .await?
76 .0)
77 }
78
79 async fn get_dc<R, Q>(&self, path: &'static str, query: Q, dc_restrict: DcRegion) -> Result<R>
82 where
83 R: DeserializeOwned + Send + Sync + 'static,
84 Q: Serialize + Send + Sync,
85 {
86 Ok(self
87 .0
88 .http_cli
89 .request(Method::GET, path)
90 .dc_restrict(dc_restrict)
91 .query_params(query)
92 .response::<Json<R>>()
93 .send()
94 .with_subscriber(self.0.log_subscriber.clone())
95 .await?
96 .0)
97 }
98
99 pub async fn financial_report(
105 &self,
106 symbol: impl Into<String>,
107 kind: FinancialReportKind,
108 period: Option<FinancialReportPeriod>,
109 ) -> Result<FinancialReports> {
110 let kind_str = match kind {
111 FinancialReportKind::IncomeStatement => "IS",
112 FinancialReportKind::BalanceSheet => "BS",
113 FinancialReportKind::CashFlow => "CF",
114 FinancialReportKind::All => "ALL",
115 };
116 let period_str = period.map(|p| match p {
117 FinancialReportPeriod::Annual => "af",
118 FinancialReportPeriod::SemiAnnual => "saf",
119 FinancialReportPeriod::Q1 => "q1",
120 FinancialReportPeriod::Q2 => "q2",
121 FinancialReportPeriod::Q3 => "q3",
122 FinancialReportPeriod::QuarterlyFull => "qf",
123 FinancialReportPeriod::ThreeQ => "3q",
124 });
125 #[derive(Serialize)]
126 struct Query {
127 symbol: String,
128 kind: &'static str,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 report: Option<&'static str>,
131 }
132 self.get(
133 "/v1/quote/financial-reports",
134 Query {
135 symbol: symbol.into(),
136 kind: kind_str,
137 report: period_str,
138 },
139 )
140 .await
141 }
142
143 pub async fn institution_rating(&self, symbol: impl Into<String>) -> Result<InstitutionRating> {
150 #[derive(Serialize)]
151 struct Query {
152 symbol: String,
153 }
154 let sym = symbol.into();
155 let (latest, summary) = tokio::join!(
156 self.get::<InstitutionRatingLatest, _>(
157 "/v1/quote/institution-rating-latest",
158 Query {
159 symbol: sym.clone()
160 }
161 ),
162 self.get::<InstitutionRatingSummary, _>(
163 "/v1/quote/institution-ratings",
164 Query {
165 symbol: sym.clone()
166 }
167 ),
168 );
169 Ok(InstitutionRating {
170 latest: latest?,
171 summary: summary?,
172 })
173 }
174
175 pub async fn institution_rating_detail(
179 &self,
180 symbol: impl Into<String>,
181 ) -> Result<InstitutionRatingDetail> {
182 #[derive(Serialize)]
183 struct Query {
184 symbol: String,
185 }
186 self.get(
187 "/v1/quote/institution-ratings/detail",
188 Query {
189 symbol: symbol.into(),
190 },
191 )
192 .await
193 }
194
195 pub async fn dividend(&self, symbol: impl Into<String>) -> Result<DividendList> {
201 #[derive(Serialize)]
202 struct Query {
203 symbol: String,
204 }
205 self.get(
206 "/v1/quote/dividends",
207 Query {
208 symbol: symbol.into(),
209 },
210 )
211 .await
212 }
213
214 pub async fn dividend_detail(&self, symbol: impl Into<String>) -> Result<DividendList> {
218 #[derive(Serialize)]
219 struct Query {
220 symbol: String,
221 }
222 self.get(
223 "/v1/quote/dividends/details",
224 Query {
225 symbol: symbol.into(),
226 },
227 )
228 .await
229 }
230
231 pub async fn forecast_eps(&self, symbol: impl Into<String>) -> Result<ForecastEps> {
237 #[derive(Serialize)]
238 struct Query {
239 symbol: String,
240 }
241 self.get(
242 "/v1/quote/forecast-eps",
243 Query {
244 symbol: symbol.into(),
245 },
246 )
247 .await
248 }
249
250 pub async fn consensus(&self, symbol: impl Into<String>) -> Result<FinancialConsensus> {
256 #[derive(Serialize)]
257 struct Query {
258 symbol: String,
259 }
260 self.get(
261 "/v1/quote/financial-consensus-detail",
262 Query {
263 symbol: symbol.into(),
264 },
265 )
266 .await
267 }
268
269 pub async fn valuation(&self, symbol: impl Into<String>) -> Result<ValuationData> {
275 #[derive(Serialize)]
276 struct Query {
277 symbol: String,
278 indicator: &'static str,
279 range: &'static str,
280 }
281 self.get(
282 "/v1/quote/valuation",
283 Query {
284 symbol: symbol.into(),
285 indicator: "pe",
286 range: "1",
287 },
288 )
289 .await
290 }
291
292 pub async fn valuation_history(
296 &self,
297 symbol: impl Into<String>,
298 ) -> Result<ValuationHistoryResponse> {
299 #[derive(Serialize)]
300 struct Query {
301 symbol: String,
302 }
303 self.get(
304 "/v1/quote/valuation/detail",
305 Query {
306 symbol: symbol.into(),
307 },
308 )
309 .await
310 }
311
312 pub async fn industry_valuation(
318 &self,
319 symbol: impl Into<String>,
320 ) -> Result<IndustryValuationList> {
321 #[derive(Serialize)]
322 struct Query {
323 symbol: String,
324 }
325 self.get(
326 "/v1/quote/industry-valuation-comparison",
327 Query {
328 symbol: symbol.into(),
329 },
330 )
331 .await
332 }
333
334 pub async fn industry_valuation_dist(
338 &self,
339 symbol: impl Into<String>,
340 ) -> Result<IndustryValuationDist> {
341 #[derive(Serialize)]
342 struct Query {
343 symbol: String,
344 }
345 self.get(
346 "/v1/quote/industry-valuation-distribution",
347 Query {
348 symbol: symbol.into(),
349 },
350 )
351 .await
352 }
353
354 pub async fn company(&self, symbol: impl Into<String>) -> Result<CompanyOverview> {
360 #[derive(Serialize)]
361 struct Query {
362 symbol: String,
363 }
364 self.get(
365 "/v1/quote/comp-overview",
366 Query {
367 symbol: symbol.into(),
368 },
369 )
370 .await
371 }
372
373 pub async fn executive(&self, symbol: impl Into<String>) -> Result<ExecutiveList> {
379 #[derive(Serialize)]
380 struct Query {
381 symbols: String,
384 }
385 self.get(
386 "/v1/quote/company-professionals",
387 Query {
388 symbols: symbol.into(),
389 },
390 )
391 .await
392 }
393
394 pub async fn shareholder(&self, symbol: impl Into<String>) -> Result<ShareholderList> {
400 #[derive(Serialize)]
401 struct Query {
402 symbol: String,
403 }
404 self.get(
405 "/v1/quote/shareholders",
406 Query {
407 symbol: symbol.into(),
408 },
409 )
410 .await
411 }
412
413 pub async fn fund_holder(&self, symbol: impl Into<String>) -> Result<FundHolders> {
419 #[derive(Serialize)]
420 struct Query {
421 symbol: String,
422 }
423 self.get(
424 "/v1/quote/fund-holders",
425 Query {
426 symbol: symbol.into(),
427 },
428 )
429 .await
430 }
431
432 pub async fn corp_action(&self, symbol: impl Into<String>) -> Result<CorpActions> {
438 #[derive(Serialize)]
439 struct Query {
440 symbol: String,
441 req_type: &'static str,
442 version: &'static str,
443 }
444 self.get(
445 "/v1/quote/company-act",
446 Query {
447 symbol: symbol.into(),
448 req_type: "1",
449 version: "3",
450 },
451 )
452 .await
453 }
454
455 pub async fn invest_relation(&self, symbol: impl Into<String>) -> Result<InvestRelations> {
461 #[derive(Serialize)]
462 struct Query {
463 symbol: String,
464 count: &'static str,
465 }
466 self.get(
467 "/v1/quote/invest-relations",
468 Query {
469 symbol: symbol.into(),
470 count: "0",
471 },
472 )
473 .await
474 }
475
476 pub async fn operating(&self, symbol: impl Into<String>) -> Result<OperatingList> {
482 #[derive(Serialize)]
483 struct Query {
484 symbol: String,
485 }
486 self.get_dc(
487 "/v1/quote/operatings",
488 Query {
489 symbol: symbol.into(),
490 },
491 DcRegion::Ap,
492 )
493 .await
494 }
495
496 pub async fn buyback(&self, symbol: impl Into<String>) -> Result<BuybackData> {
502 #[derive(Serialize)]
503 struct Query {
504 symbol: String,
505 }
506 self.get(
507 "/v1/quote/buy-backs",
508 Query {
509 symbol: symbol.into(),
510 },
511 )
512 .await
513 }
514
515 pub async fn business_segments(&self, symbol: impl Into<String>) -> Result<BusinessSegments> {
541 #[derive(Serialize)]
542 struct Query {
543 symbol: String,
544 }
545 self.get(
546 "/v1/quote/fundamentals/business-segments",
547 Query {
548 symbol: symbol.into(),
549 },
550 )
551 .await
552 }
553
554 pub async fn business_segments_history(
558 &self,
559 symbol: impl Into<String>,
560 report: Option<&'static str>,
561 cate: Option<String>,
562 ) -> Result<BusinessSegmentsHistory> {
563 #[derive(Serialize)]
564 struct Query {
565 symbol: String,
566 #[serde(skip_serializing_if = "Option::is_none")]
567 report: Option<&'static str>,
568 #[serde(skip_serializing_if = "Option::is_none")]
569 cate: Option<String>,
570 }
571 self.get(
572 "/v1/quote/fundamentals/business-segments/history",
573 Query {
574 symbol: symbol.into(),
575 report,
576 cate,
577 },
578 )
579 .await
580 }
581
582 pub async fn shareholder_top(
588 &self,
589 symbol: impl Into<String>,
590 ) -> Result<ShareholderTopResponse> {
591 #[derive(Serialize)]
592 struct Query {
593 symbol: String,
594 }
595 let raw: serde_json::Value = self
596 .get(
597 "/v1/quote/shareholders/top",
598 Query {
599 symbol: symbol.into(),
600 },
601 )
602 .await?;
603 Ok(ShareholderTopResponse { data: raw })
604 }
605
606 pub async fn institution_rating_views(
612 &self,
613 symbol: impl Into<String>,
614 ) -> Result<InstitutionRatingViews> {
615 #[derive(Serialize)]
616 struct Query {
617 symbol: String,
618 }
619 self.get(
620 "/v1/quote/ratings/institutional",
621 Query {
622 symbol: symbol.into(),
623 },
624 )
625 .await
626 }
627
628 pub async fn shareholder_detail(
634 &self,
635 symbol: impl Into<String>,
636 object_id: i64,
637 ) -> Result<ShareholderDetailResponse> {
638 #[derive(Serialize)]
639 struct Query {
640 symbol: String,
641 object_id: String,
642 }
643 let raw: serde_json::Value = self
644 .get(
645 "/v1/quote/shareholders/holding",
646 Query {
647 symbol: symbol.into(),
648 object_id: object_id.to_string(),
649 },
650 )
651 .await?;
652 Ok(ShareholderDetailResponse { data: raw })
653 }
654
655 pub async fn industry_rank(
664 &self,
665 market: Market,
666 indicator: IndustryRankIndicator,
667 sort_type: IndustryRankSortType,
668 limit: u32,
669 ) -> Result<IndustryRankResponse> {
670 #[derive(Serialize)]
671 struct Query {
672 market: Market,
673 indicator: &'static str,
674 sort_type: &'static str,
675 #[serde(skip_serializing_if = "is_zero")]
676 limit: u32,
677 }
678 fn is_zero(v: &u32) -> bool {
679 *v == 0
680 }
681 self.get(
682 "/v1/quote/industry/rank",
683 Query {
684 market,
685 indicator: indicator.as_str(),
686 sort_type: sort_type.as_str(),
687 limit,
688 },
689 )
690 .await
691 }
692
693 pub async fn industry_peers(
699 &self,
700 symbol: impl Into<String>,
701 market: impl Into<String>,
702 industry_id: Option<String>,
703 ) -> Result<IndustryPeersResponse> {
704 let sym = symbol.into();
705 #[derive(Serialize)]
706 struct Query {
707 #[serde(rename = "type")]
708 kind: &'static str,
709 market: String,
710 industry_id: String,
711 symbol: String,
712 }
713 self.get(
714 "/v1/quote/industries/peers",
715 Query {
716 kind: "1",
717 market: market.into(),
718 industry_id: industry_id.unwrap_or_default(),
719 symbol: sym,
720 },
721 )
722 .await
723 }
724
725 pub async fn financial_report_snapshot(
731 &self,
732 symbol: impl Into<String>,
733 report: Option<&'static str>,
734 fiscal_year: Option<i32>,
735 fiscal_period: Option<&'static str>,
736 ) -> Result<FinancialReportSnapshot> {
737 #[derive(Serialize)]
738 struct Query {
739 symbol: String,
740 #[serde(skip_serializing_if = "Option::is_none")]
741 report: Option<&'static str>,
742 #[serde(skip_serializing_if = "Option::is_none")]
743 fiscal_year: Option<i32>,
744 #[serde(skip_serializing_if = "Option::is_none")]
745 fiscal_period: Option<&'static str>,
746 }
747 self.get(
748 "/v1/quote/financials/earnings-snapshot",
749 Query {
750 symbol: symbol.into(),
751 report,
752 fiscal_year,
753 fiscal_period,
754 },
755 )
756 .await
757 }
758
759 pub async fn valuation_comparison(
765 &self,
766 symbol: impl Into<String>,
767 currency: impl Into<String>,
768 comparison_symbols: Option<Vec<String>>,
769 ) -> Result<ValuationComparisonResponse> {
770 #[derive(Serialize)]
771 struct Query {
772 symbol: String,
773 currency: String,
774 #[serde(skip_serializing_if = "Option::is_none")]
775 comparison_symbols: Option<Vec<String>>,
776 }
777 let raw: serde_json::Value = self
778 .get(
779 "/v1/quote/compare/valuation",
780 Query {
781 symbol: symbol.into(),
782 currency: currency.into(),
783 comparison_symbols,
784 },
785 )
786 .await?;
787 let list = raw["list"]
788 .as_array()
789 .cloned()
790 .unwrap_or_default()
791 .into_iter()
792 .map(|item| {
793 let history = item["history"]
794 .as_array()
795 .cloned()
796 .unwrap_or_default()
797 .into_iter()
798 .map(|h| ValuationHistoryPoint {
799 date: unix_secs_str_to_rfc3339(h["date"].as_str().unwrap_or("")),
800 pe: h["pe"].as_str().unwrap_or("").to_string(),
801 pb: h["pb"].as_str().unwrap_or("").to_string(),
802 ps: h["ps"].as_str().unwrap_or("").to_string(),
803 })
804 .collect();
805 ValuationComparisonItem {
806 symbol: item["symbol"].as_str().unwrap_or("").to_string(),
807 name: item["name"].as_str().unwrap_or("").to_string(),
808 currency: item["currency"].as_str().unwrap_or("").to_string(),
809 market_value: item["market_value"].as_str().unwrap_or("").to_string(),
810 price_close: item["price_close"].as_str().unwrap_or("").to_string(),
811 pe: item["pe"].as_str().unwrap_or("").to_string(),
812 pb: item["pb"].as_str().unwrap_or("").to_string(),
813 ps: item["ps"].as_str().unwrap_or("").to_string(),
814 roe: item["roe"].as_str().unwrap_or("").to_string(),
815 eps: item["eps"].as_str().unwrap_or("").to_string(),
816 bps: item["bps"].as_str().unwrap_or("").to_string(),
817 dps: item["dps"].as_str().unwrap_or("").to_string(),
818 div_yld: item["div_yld"].as_str().unwrap_or("").to_string(),
819 assets: item["assets"].as_str().unwrap_or("").to_string(),
820 history,
821 }
822 })
823 .collect();
824 Ok(ValuationComparisonResponse { list })
825 }
826
827 pub async fn etf_asset_allocation(
834 &self,
835 symbol: impl Into<String>,
836 ) -> Result<AssetAllocationResponse> {
837 #[derive(Serialize)]
838 struct Query {
839 symbol: String,
840 }
841 self.get(
842 "/v1/quote/etf-asset-allocation",
843 Query {
844 symbol: symbol.into(),
845 },
846 )
847 .await
848 }
849
850 pub async fn macroeconomic_indicators(
861 &self,
862 country: Option<MacroeconomicCountry>,
863 keyword: Option<impl Into<String>>,
864 offset: Option<i32>,
865 limit: Option<i32>,
866 ) -> Result<MacroeconomicIndicatorListResponse> {
867 self.macroeconomic_indicators_v2(country, keyword, offset, limit)
868 .await
869 }
870
871 pub(crate) async fn macroeconomic_indicators_v2(
875 &self,
876 country: Option<MacroeconomicCountry>,
877 keyword: Option<impl Into<String>>,
878 offset: Option<i32>,
879 limit: Option<i32>,
880 ) -> Result<MacroeconomicIndicatorListResponse> {
881 #[derive(Serialize)]
882 struct Query {
883 market: String,
884 #[serde(skip_serializing_if = "Option::is_none")]
885 keyword: Option<String>,
886 #[serde(skip_serializing_if = "Option::is_none")]
887 offset: Option<i32>,
888 #[serde(skip_serializing_if = "Option::is_none")]
889 limit: Option<i32>,
890 }
891 let market = country
892 .map(|c| match c {
893 MacroeconomicCountry::HongKong => "HK",
894 MacroeconomicCountry::China => "CN",
895 MacroeconomicCountry::UnitedStates => "US",
896 MacroeconomicCountry::EuroZone => "EU",
897 MacroeconomicCountry::Japan => "JP",
898 MacroeconomicCountry::Singapore => "SG",
899 })
900 .unwrap_or("ALL")
901 .to_string();
902
903 let raw: V2MacroIndicatorListResponse = self
904 .get(
905 "/v2/quote/macrodata",
906 Query {
907 market,
908 keyword: keyword.map(|k| k.into()),
909 offset,
910 limit,
911 },
912 )
913 .await?;
914
915 let total = raw.total;
916 let data = raw
917 .indicator_list
918 .into_iter()
919 .map(|ind| MacroeconomicIndicator {
920 indicator_code: ind.indicator_id.to_string(),
921 country: ind.market,
922 name: ind.indicator_name,
923 periodicity: ind.frequence,
924 describe: ind.description,
925 importance: ind.importance,
926 ..Default::default()
927 })
928 .collect::<Vec<_>>();
929 let count = if total > 0 { total } else { data.len() as i32 };
930 Ok(MacroeconomicIndicatorListResponse { data, count })
931 }
932
933 pub async fn macroeconomic(
941 &self,
942 indicator_code: impl Into<String>,
943 start_date: Option<impl Into<String>>,
944 end_date: Option<impl Into<String>>,
945 offset: Option<i32>,
946 limit: Option<i32>,
947 ) -> Result<MacroeconomicResponse> {
948 self.macroeconomic_v2(
949 indicator_code,
950 start_date,
951 end_date,
952 offset,
953 limit,
954 None::<String>,
955 )
956 .await
957 }
958
959 pub(crate) async fn macroeconomic_v2(
964 &self,
965 indicator_code: impl Into<String>,
966 start_date: Option<impl Into<String>>,
967 end_date: Option<impl Into<String>>,
968 offset: Option<i32>,
969 limit: Option<i32>,
970 sort: Option<impl Into<String>>,
971 ) -> Result<MacroeconomicResponse> {
972 #[derive(Serialize)]
973 struct Query {
974 #[serde(skip_serializing_if = "Option::is_none")]
975 start_date: Option<String>,
976 #[serde(skip_serializing_if = "Option::is_none")]
977 end_date: Option<String>,
978 #[serde(skip_serializing_if = "Option::is_none")]
979 offset: Option<i32>,
980 #[serde(skip_serializing_if = "Option::is_none")]
981 limit: Option<i32>,
982 #[serde(skip_serializing_if = "Option::is_none")]
983 sort: Option<String>,
984 }
985 let path = format!("/v2/quote/macrodata/{}", indicator_code.into());
986 let raw: V2MacroIndicatorDataResponse = self
987 .0
988 .http_cli
989 .request(Method::GET, path)
990 .query_params(Query {
991 start_date: start_date.map(|d| d.into()),
992 end_date: end_date.map(|d| d.into()),
993 offset,
994 limit,
995 sort: Some(sort.map(|s| s.into()).unwrap_or_else(|| "desc".to_string())),
996 })
997 .response::<Json<V2MacroIndicatorDataResponse>>()
998 .send()
999 .with_subscriber(self.0.log_subscriber.clone())
1000 .await?
1001 .0;
1002
1003 let total = raw.total;
1004 let detail = raw.indicator;
1005 let unit_english = detail.unit.clone();
1006 let count = detail.indicator_data.len() as i32;
1007
1008 let info = MacroeconomicIndicator {
1009 indicator_code: detail.indicator_id.to_string(),
1010 country: detail.market,
1011 name: detail.indicator_name,
1012 describe: detail.description,
1013 periodicity: detail.frequence,
1014 importance: detail.importance,
1015 ..Default::default()
1016 };
1017
1018 let data = detail
1019 .indicator_data
1020 .into_iter()
1021 .map(|d| {
1022 use time::format_description::well_known::Rfc3339;
1023 let release_at = time::OffsetDateTime::parse(&d.published_time, &Rfc3339)
1024 .ok()
1025 .or_else(|| {
1026 time::PrimitiveDateTime::parse(
1028 &d.published_time,
1029 &time::macros::format_description!(
1030 "[year]-[month]-[day]T[hour]:[minute]:[second]"
1031 ),
1032 )
1033 .ok()
1034 .map(|dt| dt.assume_utc())
1035 });
1036 Macroeconomic {
1037 period: d.observation_date,
1038 release_at,
1039 actual_value: d.actual_data,
1040 previous_value: d.previous_data,
1041 forecast_value: d.estimated_data,
1042 unit: unit_english.clone(),
1043 ..Default::default()
1044 }
1045 })
1046 .collect();
1047
1048 let count = if total > 0 { total } else { count };
1049 Ok(MacroeconomicResponse { info, data, count })
1050 }
1051
1052 pub async fn us_company_overview(
1063 &self,
1064 symbol: impl Into<String>,
1065 ) -> Result<USCompanyOverview> {
1066 #[derive(Serialize)]
1067 struct Query {
1068 symbol: String,
1069 }
1070 self.get_dc(
1071 "/v1/us/stock-info/company-overview",
1072 Query {
1073 symbol: symbol.into(),
1074 },
1075 DcRegion::Us,
1076 )
1077 .await
1078 }
1079
1080 pub async fn us_valuation_overview(
1086 &self,
1087 symbol: impl Into<String>,
1088 ) -> Result<USValuationOverview> {
1089 #[derive(Serialize)]
1090 struct Query {
1091 symbol: String,
1092 }
1093 self.get_dc(
1094 "/v1/us/stock-info/valuation-overview",
1095 Query {
1096 symbol: symbol.into(),
1097 },
1098 DcRegion::Us,
1099 )
1100 .await
1101 }
1102
1103 pub async fn us_financial_overview(
1111 &self,
1112 symbol: impl Into<String>,
1113 report: impl Into<String>,
1114 ) -> Result<USFinancialOverview> {
1115 #[derive(Serialize)]
1116 struct Query {
1117 symbol: String,
1118 report: String,
1119 }
1120 self.get_dc(
1121 "/v1/us/stock-info/finn-overview",
1122 Query {
1123 symbol: symbol.into(),
1124 report: report.into(),
1125 },
1126 DcRegion::Us,
1127 )
1128 .await
1129 }
1130
1131 pub async fn us_financial_statement(
1141 &self,
1142 symbol: impl Into<String>,
1143 kind: FinancialStatementKind,
1144 report: impl Into<String>,
1145 ) -> Result<USFinancialStatement> {
1146 #[derive(Serialize)]
1147 struct Query {
1148 symbol: String,
1149 kind: &'static str,
1150 report: String,
1151 }
1152 self.get_dc(
1153 "/v1/us/quote/financials/statements",
1154 Query {
1155 symbol: symbol.into(),
1156 kind: kind.as_str(),
1157 report: report.into(),
1158 },
1159 DcRegion::Us,
1160 )
1161 .await
1162 }
1163
1164 pub async fn us_key_financial_metrics(
1172 &self,
1173 symbol: impl Into<String>,
1174 report: impl Into<String>,
1175 ) -> Result<USKeyFinancialMetrics> {
1176 #[derive(Serialize)]
1177 struct Query {
1178 symbol: String,
1179 report: String,
1180 }
1181 self.get_dc(
1182 "/v1/us/stock-info/fin-keyfactor",
1183 Query {
1184 symbol: symbol.into(),
1185 report: report.into(),
1186 },
1187 DcRegion::Us,
1188 )
1189 .await
1190 }
1191
1192 pub async fn us_analyst_consensus(
1200 &self,
1201 symbol: impl Into<String>,
1202 report: impl Into<String>,
1203 ) -> Result<USAnalystConsensus> {
1204 #[derive(Serialize)]
1205 struct Query {
1206 symbol: String,
1207 report: String,
1208 }
1209 self.get_dc(
1210 "/v1/us/stock-info/fin-consensus",
1211 Query {
1212 symbol: symbol.into(),
1213 report: report.into(),
1214 },
1215 DcRegion::Us,
1216 )
1217 .await
1218 }
1219
1220 pub async fn us_etf_dividend_info(
1226 &self,
1227 symbol: impl Into<String>,
1228 ) -> Result<USETFDividendInfo> {
1229 #[derive(Serialize)]
1230 struct Query {
1231 symbol: String,
1232 }
1233 self.get_dc(
1234 "/v1/us/stock-info/etf-dividend-info",
1235 Query {
1236 symbol: symbol.into(),
1237 },
1238 DcRegion::Us,
1239 )
1240 .await
1241 }
1242
1243 pub async fn us_company_dividends(
1249 &self,
1250 symbol: impl Into<String>,
1251 ) -> Result<USCompanyDividends> {
1252 #[derive(Serialize)]
1253 struct Query {
1254 symbol: String,
1255 }
1256 self.get_dc(
1257 "/v1/us/stock-info/company-dividends",
1258 Query {
1259 symbol: symbol.into(),
1260 },
1261 DcRegion::Us,
1262 )
1263 .await
1264 }
1265
1266 pub async fn us_etf_files(
1275 &self,
1276 symbol: impl Into<String>,
1277 size: Option<u32>,
1278 ) -> Result<USETFFilesResponse> {
1279 #[derive(Serialize)]
1280 struct Query {
1281 symbol: String,
1282 #[serde(skip_serializing_if = "Option::is_none")]
1283 size: Option<u32>,
1284 }
1285 self.get_dc(
1286 "/v1/us/stock-info/etf-files",
1287 Query {
1288 symbol: symbol.into(),
1289 size,
1290 },
1291 DcRegion::Us,
1292 )
1293 .await
1294 }
1295}