futu_mcp/handlers/reference/company/
financials.rs1use 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}