Fundamental_Analysis/services/finnhub-provider-service/src/finnhub.rs
Lv, Qi 5327e76aaa chore: 提交本轮 Rust 架构迁移相关改动
- docker-compose: 下线 Python backend/config-service,切换至 config-service-rs
- archive: 归档 legacy Python 目录至 archive/python/*
- services: 新增/更新 common-contracts、api-gateway、各 provider、report-generator-service、config-service-rs
- data-persistence-service: API/system 模块与模型/DTO 调整
- frontend: 更新 useApi 与 API 路由
- docs: 更新路线图并勾选光荣退役
- cleanup: 移除 data-distance-service 占位测试
2025-11-16 20:55:46 +08:00

105 lines
2.9 KiB
Rust

use serde::Deserialize;
use crate::{
error::AppError,
fh_client::FinnhubClient,
mapping::{map_financial_dtos, map_profile_dto},
};
use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto};
use tokio;
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FinnhubProfile {
pub country: Option<String>,
pub currency: Option<String>,
pub exchange: Option<String>,
pub name: Option<String>,
pub ticker: Option<String>,
pub ipo: Option<String>,
pub market_capitalization: Option<f64>,
pub share_outstanding: Option<f64>,
pub logo: Option<String>,
pub phone: Option<String>,
pub weburl: Option<String>,
pub finnhub_industry: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FinnhubFinancialsReported {
pub data: Vec<AnnualReport>,
pub symbol: String,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AnnualReport {
pub year: u16,
pub start_date: String,
pub end_date: String,
pub report: Report,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Report {
pub bs: Vec<ReportItem>,
pub ic: Vec<ReportItem>,
pub cf: Vec<ReportItem>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ReportItem {
pub value: f64,
pub concept: String,
pub label: String,
}
pub struct FinnhubDataProvider {
client: FinnhubClient,
}
impl FinnhubDataProvider {
pub fn new(api_url: String, api_token: String) -> Self {
Self {
client: FinnhubClient::new(api_url, api_token).expect("Failed to create Finnhub client"),
}
}
pub async fn fetch_all_data(
&self,
symbol: &str,
) -> Result<(CompanyProfileDto, Vec<TimeSeriesFinancialDto>), AppError> {
let (profile_raw, financials_raw) = self.fetch_raw_data(symbol).await?;
// 1. Build CompanyProfileDto
let profile = map_profile_dto(&profile_raw, symbol)?;
// 2. Build TimeSeriesFinancialDto list
let financials = map_financial_dtos(&financials_raw, symbol)?;
Ok((profile, financials))
}
async fn fetch_raw_data(
&self,
symbol: &str,
) -> Result<(FinnhubProfile, FinnhubFinancialsReported), AppError> {
let params_profile = vec![("symbol".to_string(), symbol.to_string())];
let params_financials = vec![
("symbol".to_string(), symbol.to_string()),
("freq".to_string(), "annual".to_string()),
];
let profile_task = self.client.get::<FinnhubProfile>("/stock/profile2", params_profile);
let financials_task =
self.client
.get::<FinnhubFinancialsReported>("/stock/financials-reported", params_financials);
let (profile_res, financials_res) = tokio::try_join!(profile_task, financials_task)?;
Ok((profile_res, financials_res))
}
}