Skip to main content

futu_mcp/handlers/reference/company/
financials.rs

1use std::sync::Arc;
2
3use anyhow::{Result, anyhow, bail};
4use futu_net::client::FutuClient;
5use prost::Message;
6use serde::Serialize;
7
8use crate::state::parse_symbol;
9
10pub async fn get_financials_earnings_price_move(
11    client: &Arc<FutuClient>,
12    symbol: &str,
13    period_count: Option<i32>,
14) -> Result<String> {
15    let sec = parse_symbol(symbol)?;
16    let req = futu_proto::qot_get_financials_earnings_price_move::Request {
17        c2s: futu_proto::qot_get_financials_earnings_price_move::C2s {
18            security: futu_proto::qot_common::Security {
19                market: sec.market as i32,
20                code: sec.code.clone(),
21            },
22            period_count,
23        },
24    };
25    let body = req.encode_to_vec();
26    let frame = client
27        .request(
28            futu_core::proto_id::QOT_GET_FINANCIALS_EARNINGS_PRICE_MOVE,
29            body,
30        )
31        .await?;
32    let resp =
33        futu_proto::qot_get_financials_earnings_price_move::Response::decode(frame.body.as_ref())
34            .map_err(|e| anyhow!("decode financials_earnings_price_move: {e}"))?;
35    if resp.ret_type != 0 {
36        bail!(
37            "financials_earnings_price_move ret_type={} msg={:?}",
38            resp.ret_type,
39            resp.ret_msg
40        );
41    }
42    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
43    Ok(serde_json::to_string_pretty(&serde_json::json!({
44        "symbol": symbol,
45        "detail_list": s2c.detail_list,
46    }))?)
47}
48
49pub async fn get_financials_earnings_price_history(
50    client: &Arc<FutuClient>,
51    symbol: &str,
52) -> Result<String> {
53    let sec = parse_symbol(symbol)?;
54    let req = futu_proto::qot_get_financials_earnings_price_history::Request {
55        c2s: futu_proto::qot_get_financials_earnings_price_history::C2s {
56            security: futu_proto::qot_common::Security {
57                market: sec.market as i32,
58                code: sec.code.clone(),
59            },
60        },
61    };
62    let body = req.encode_to_vec();
63    let frame = client
64        .request(
65            futu_core::proto_id::QOT_GET_FINANCIALS_EARNINGS_PRICE_HISTORY,
66            body,
67        )
68        .await?;
69    let resp = futu_proto::qot_get_financials_earnings_price_history::Response::decode(
70        frame.body.as_ref(),
71    )
72    .map_err(|e| anyhow!("decode financials_earnings_price_history: {e}"))?;
73    if resp.ret_type != 0 {
74        bail!(
75            "financials_earnings_price_history ret_type={} msg={:?}",
76            resp.ret_type,
77            resp.ret_msg
78        );
79    }
80    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
81    Ok(serde_json::to_string_pretty(&serde_json::json!({
82        "symbol": symbol,
83        "detail_list": s2c.detail_list,
84    }))?)
85}
86
87pub async fn get_financial_calendar(
88    client: &Arc<FutuClient>,
89    req: futu_backend::proto_internal::financial_calendar::GetFinancialStatementCalendarViewReq,
90) -> Result<String> {
91    let body = req.encode_to_vec();
92    let frame = client
93        .request(
94            futu_core::proto_id::QOT_GET_FINANCIAL_CALENDAR_VIEW_INTERNAL,
95            body,
96        )
97        .await?;
98    let resp =
99        futu_backend::proto_internal::financial_calendar::GetFinancialStatementCalendarViewRsp::decode(
100            frame.body.as_ref(),
101        )
102        .map_err(|e| anyhow!("decode financial_calendar: {e}"))?;
103    if resp.ret_code.unwrap_or(0) != 0 {
104        bail!(
105            "financial_calendar ret_code={:?} message={:?}",
106            resp.ret_code,
107            resp.message
108        );
109    }
110    Ok(serde_json::to_string_pretty(&resp)?)
111}
112
113pub async fn search_target_financial_calendar(
114    client: &Arc<FutuClient>,
115    req: futu_backend::proto_internal::financial_calendar::SearchTargetStockFinancialCalendarReq,
116) -> Result<String> {
117    let body = req.encode_to_vec();
118    let frame = client
119        .request(
120            futu_core::proto_id::QOT_SEARCH_TARGET_FINANCIAL_CALENDAR,
121            body,
122        )
123        .await?;
124    let resp =
125        futu_backend::proto_internal::financial_calendar::SearchTargetStockFinancialCalendarRsp::decode(
126            frame.body.as_ref(),
127        )
128        .map_err(|e| anyhow!("decode target_financial_calendar: {e}"))?;
129    if resp.code.unwrap_or(0) != 0 {
130        bail!(
131            "target_financial_calendar code={:?} message={:?}",
132            resp.code,
133            resp.message
134        );
135    }
136    Ok(serde_json::to_string_pretty(&resp)?)
137}
138
139#[derive(Serialize)]
140struct FinancialsStatementsOut {
141    symbol: String,
142    structure_list: Vec<futu_proto::qot_get_financials_statements::FinancialFieldInfo>,
143    report_list: Vec<futu_proto::qot_get_financials_statements::FinancialReport>,
144    next_key: Option<String>,
145}
146
147pub async fn get_financials_statements(
148    client: &Arc<FutuClient>,
149    symbol: &str,
150    statement_type: Option<i32>,
151    financial_type: Option<i32>,
152    currency_code: Option<&str>,
153    next_key: Option<&str>,
154    num: Option<i32>,
155) -> Result<String> {
156    let sec = parse_symbol(symbol)?;
157    let req = futu_proto::qot_get_financials_statements::Request {
158        c2s: futu_proto::qot_get_financials_statements::C2s {
159            security: futu_proto::qot_common::Security {
160                market: sec.market as i32,
161                code: sec.code.clone(),
162            },
163            statement_type,
164            financial_type,
165            currency_code: currency_code.map(str::to_string),
166            next_key: next_key.map(str::to_string),
167            num,
168        },
169    };
170    let body = req.encode_to_vec();
171    let frame = client
172        .request(futu_core::proto_id::QOT_GET_FINANCIALS_STATEMENTS, body)
173        .await?;
174    let resp = futu_proto::qot_get_financials_statements::Response::decode(frame.body.as_ref())
175        .map_err(|e| anyhow!("decode financials_statements: {e}"))?;
176    if resp.ret_type != 0 {
177        bail!(
178            "financials_statements ret_type={} msg={:?}",
179            resp.ret_type,
180            resp.ret_msg
181        );
182    }
183    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
184    let out = FinancialsStatementsOut {
185        symbol: symbol.to_string(),
186        structure_list: s2c.structure_list,
187        report_list: s2c.report_list,
188        next_key: s2c.next_key,
189    };
190    Ok(serde_json::to_string_pretty(&out)?)
191}
192
193#[derive(Serialize)]
194struct FinancialsRevenueBreakdownOut {
195    symbol: String,
196    period: Option<String>,
197    breakdown_list: Vec<futu_proto::qot_get_financials_revenue_breakdown::RevenueBreakdownGroup>,
198    currency_code: Option<String>,
199    screen_date_list: Vec<futu_proto::qot_get_financials_revenue_breakdown::ScreenDate>,
200}
201
202pub async fn get_financials_revenue_breakdown(
203    client: &Arc<FutuClient>,
204    symbol: &str,
205    date: Option<u32>,
206    financial_type: Option<i32>,
207    currency_code: Option<&str>,
208) -> Result<String> {
209    let sec = parse_symbol(symbol)?;
210    let req = futu_proto::qot_get_financials_revenue_breakdown::Request {
211        c2s: futu_proto::qot_get_financials_revenue_breakdown::C2s {
212            security: futu_proto::qot_common::Security {
213                market: sec.market as i32,
214                code: sec.code.clone(),
215            },
216            date,
217            financial_type,
218            currency_code: currency_code.map(str::to_string),
219        },
220    };
221    let body = req.encode_to_vec();
222    let frame = client
223        .request(
224            futu_core::proto_id::QOT_GET_FINANCIALS_REVENUE_BREAKDOWN,
225            body,
226        )
227        .await?;
228    let resp =
229        futu_proto::qot_get_financials_revenue_breakdown::Response::decode(frame.body.as_ref())
230            .map_err(|e| anyhow!("decode financials_revenue_breakdown: {e}"))?;
231    if resp.ret_type != 0 {
232        bail!(
233            "financials_revenue_breakdown ret_type={} msg={:?}",
234            resp.ret_type,
235            resp.ret_msg
236        );
237    }
238    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
239    let out = FinancialsRevenueBreakdownOut {
240        symbol: symbol.to_string(),
241        period: s2c.period,
242        breakdown_list: s2c.breakdown_list,
243        currency_code: s2c.currency_code,
244        screen_date_list: s2c.screen_date_list,
245    };
246    Ok(serde_json::to_string_pretty(&out)?)
247}