Skip to main content

futu_mcp/handlers/reference/
company.rs

1//! mcp/handlers/reference/company — v10.6 company profile / executives adapters.
2
3use std::sync::Arc;
4
5use anyhow::{Result, anyhow, bail};
6use futu_net::client::FutuClient;
7use prost::Message;
8use serde::Serialize;
9
10use crate::state::parse_symbol;
11
12mod financials;
13pub use financials::*;
14
15#[derive(Serialize)]
16struct CompanyProfileOut {
17    symbol: String,
18    item_list: Vec<CompanyProfileItemOut>,
19}
20
21#[derive(Serialize)]
22struct CompanyProfileItemOut {
23    name: Option<String>,
24    value: Option<String>,
25    field_type: Option<i32>,
26}
27
28pub async fn get_company_profile(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
29    let sec = parse_symbol(symbol)?;
30    let req = futu_proto::qot_get_company_profile::Request {
31        c2s: futu_proto::qot_get_company_profile::C2s {
32            security: futu_proto::qot_common::Security {
33                market: sec.market as i32,
34                code: sec.code.clone(),
35            },
36        },
37    };
38    let body = req.encode_to_vec();
39    let frame = client
40        .request(futu_core::proto_id::QOT_GET_COMPANY_PROFILE, body)
41        .await?;
42    let resp = futu_proto::qot_get_company_profile::Response::decode(frame.body.as_ref())
43        .map_err(|e| anyhow!("decode company_profile: {e}"))?;
44    if resp.ret_type != 0 {
45        bail!(
46            "company_profile ret_type={} msg={:?}",
47            resp.ret_type,
48            resp.ret_msg
49        );
50    }
51    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
52    let out = CompanyProfileOut {
53        symbol: symbol.to_string(),
54        item_list: s2c
55            .item_list
56            .iter()
57            .map(|item| CompanyProfileItemOut {
58                name: item.name.clone(),
59                value: item.value.clone(),
60                field_type: item.field_type,
61            })
62            .collect(),
63    };
64    Ok(serde_json::to_string_pretty(&out)?)
65}
66
67#[derive(Serialize)]
68struct CompanyExecutivesOut {
69    symbol: String,
70    director_list: Vec<DirectorOut>,
71}
72
73#[derive(Serialize)]
74struct DirectorOut {
75    display_leader_name: Option<String>,
76    leader_name: Option<String>,
77    position_name: Option<String>,
78    begin_date: Option<u64>,
79    begin_date_str: Option<String>,
80    leader_gender: Option<String>,
81    leader_age: Option<String>,
82    highest_education: Option<String>,
83    annual_salary: Option<u64>,
84    issue_date: Option<u64>,
85    issue_date_str: Option<String>,
86}
87
88pub async fn get_company_executives(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
89    let sec = parse_symbol(symbol)?;
90    let req = futu_proto::qot_get_company_executives::Request {
91        c2s: futu_proto::qot_get_company_executives::C2s {
92            security: futu_proto::qot_common::Security {
93                market: sec.market as i32,
94                code: sec.code.clone(),
95            },
96        },
97    };
98    let body = req.encode_to_vec();
99    let frame = client
100        .request(futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVES, body)
101        .await?;
102    let resp = futu_proto::qot_get_company_executives::Response::decode(frame.body.as_ref())
103        .map_err(|e| anyhow!("decode company_executives: {e}"))?;
104    if resp.ret_type != 0 {
105        bail!(
106            "company_executives ret_type={} msg={:?}",
107            resp.ret_type,
108            resp.ret_msg
109        );
110    }
111    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
112    let out = CompanyExecutivesOut {
113        symbol: symbol.to_string(),
114        director_list: s2c
115            .director_list
116            .iter()
117            .map(|item| DirectorOut {
118                display_leader_name: item.display_leader_name.clone(),
119                leader_name: item.leader_name.clone(),
120                position_name: item.position_name.clone(),
121                begin_date: item.begin_date,
122                begin_date_str: item.begin_date_str.clone(),
123                leader_gender: item.leader_gender.clone(),
124                leader_age: item.leader_age.clone(),
125                highest_education: item.highest_education.clone(),
126                annual_salary: item.annual_salary,
127                issue_date: item.issue_date,
128                issue_date_str: item.issue_date_str.clone(),
129            })
130            .collect(),
131    };
132    Ok(serde_json::to_string_pretty(&out)?)
133}
134
135#[derive(Serialize)]
136struct CompanyExecutiveBackgroundOut {
137    symbol: String,
138    leader_name: String,
139    brief_background: Option<String>,
140}
141
142pub async fn get_company_executive_background(
143    client: &Arc<FutuClient>,
144    symbol: &str,
145    leader_name: &str,
146) -> Result<String> {
147    let sec = parse_symbol(symbol)?;
148    let req = futu_proto::qot_get_company_executive_background::Request {
149        c2s: futu_proto::qot_get_company_executive_background::C2s {
150            security: futu_proto::qot_common::Security {
151                market: sec.market as i32,
152                code: sec.code.clone(),
153            },
154            leader_name: Some(leader_name.to_string()),
155        },
156    };
157    let body = req.encode_to_vec();
158    let frame = client
159        .request(
160            futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVE_BACKGROUND,
161            body,
162        )
163        .await?;
164    let resp =
165        futu_proto::qot_get_company_executive_background::Response::decode(frame.body.as_ref())
166            .map_err(|e| anyhow!("decode company_executive_background: {e}"))?;
167    if resp.ret_type != 0 {
168        bail!(
169            "company_executive_background ret_type={} msg={:?}",
170            resp.ret_type,
171            resp.ret_msg
172        );
173    }
174    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
175    let out = CompanyExecutiveBackgroundOut {
176        symbol: symbol.to_string(),
177        leader_name: leader_name.to_string(),
178        brief_background: s2c.brief_background,
179    };
180    Ok(serde_json::to_string_pretty(&out)?)
181}
182
183#[derive(Serialize)]
184struct CompanyOperationalEfficiencyOut {
185    symbol: String,
186    item_list: Vec<OperationalEfficiencyItemOut>,
187    next_key: Option<String>,
188    currency_code: Option<String>,
189}
190
191#[derive(Serialize)]
192struct OperationalEfficiencyItemOut {
193    fiscal_year: Option<i32>,
194    financial_type: Option<i32>,
195    period_text: Option<String>,
196    end_date: Option<i64>,
197    end_date_str: Option<String>,
198    employee_num: Option<i64>,
199    employee_num_yoy: Option<f64>,
200    income_per_capita: Option<f64>,
201    income_per_capita_yoy: Option<f64>,
202    profit_per_capita: Option<f64>,
203    profit_per_capita_yoy: Option<f64>,
204    net_profit_per_capita: Option<f64>,
205    net_profit_per_capita_yoy: Option<f64>,
206}
207
208pub async fn get_company_operational_efficiency(
209    client: &Arc<FutuClient>,
210    symbol: &str,
211    next_key: Option<&str>,
212    num: Option<i32>,
213    currency_code: Option<&str>,
214    financial_type: Option<i32>,
215) -> Result<String> {
216    let sec = parse_symbol(symbol)?;
217    let req = futu_proto::qot_get_company_operational_efficiency::Request {
218        c2s: futu_proto::qot_get_company_operational_efficiency::C2s {
219            security: futu_proto::qot_common::Security {
220                market: sec.market as i32,
221                code: sec.code.clone(),
222            },
223            next_key: next_key.map(str::to_string),
224            num,
225            currency_code: currency_code.map(str::to_string),
226            financial_type,
227        },
228    };
229    let body = req.encode_to_vec();
230    let frame = client
231        .request(
232            futu_core::proto_id::QOT_GET_COMPANY_OPERATIONAL_EFFICIENCY,
233            body,
234        )
235        .await?;
236    let resp =
237        futu_proto::qot_get_company_operational_efficiency::Response::decode(frame.body.as_ref())
238            .map_err(|e| anyhow!("decode company_operational_efficiency: {e}"))?;
239    if resp.ret_type != 0 {
240        bail!(
241            "company_operational_efficiency ret_type={} msg={:?}",
242            resp.ret_type,
243            resp.ret_msg
244        );
245    }
246    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
247    let out = CompanyOperationalEfficiencyOut {
248        symbol: symbol.to_string(),
249        item_list: s2c
250            .item_list
251            .iter()
252            .map(|item| OperationalEfficiencyItemOut {
253                fiscal_year: item.fiscal_year,
254                financial_type: item.financial_type,
255                period_text: item.period_text.clone(),
256                end_date: item.end_date,
257                end_date_str: item.end_date_str.clone(),
258                employee_num: item.employee_num,
259                employee_num_yoy: item.employee_num_yoy,
260                income_per_capita: item.income_per_capita,
261                income_per_capita_yoy: item.income_per_capita_yoy,
262                profit_per_capita: item.profit_per_capita,
263                profit_per_capita_yoy: item.profit_per_capita_yoy,
264                net_profit_per_capita: item.net_profit_per_capita,
265                net_profit_per_capita_yoy: item.net_profit_per_capita_yoy,
266            })
267            .collect(),
268        next_key: s2c.next_key,
269        currency_code: s2c.currency_code,
270    };
271    Ok(serde_json::to_string_pretty(&out)?)
272}
273
274#[derive(Serialize)]
275struct ResearchAnalystConsensusOut {
276    symbol: String,
277    highest: Option<f64>,
278    average: Option<f64>,
279    lowest: Option<f64>,
280    rating: Option<i32>,
281    total: Option<i32>,
282    update_time: Option<i64>,
283    update_time_str: Option<String>,
284    buy: Option<f64>,
285    hold: Option<f64>,
286    sell: Option<f64>,
287    strong_buy: Option<f64>,
288    underperform: Option<f64>,
289}
290
291pub async fn get_research_analyst_consensus(
292    client: &Arc<FutuClient>,
293    symbol: &str,
294) -> Result<String> {
295    let sec = parse_symbol(symbol)?;
296    let req = futu_proto::qot_get_research_analyst_consensus::Request {
297        c2s: futu_proto::qot_get_research_analyst_consensus::C2s {
298            security: futu_proto::qot_common::Security {
299                market: sec.market as i32,
300                code: sec.code.clone(),
301            },
302        },
303    };
304    let body = req.encode_to_vec();
305    let frame = client
306        .request(
307            futu_core::proto_id::QOT_GET_RESEARCH_ANALYST_CONSENSUS,
308            body,
309        )
310        .await?;
311    let resp =
312        futu_proto::qot_get_research_analyst_consensus::Response::decode(frame.body.as_ref())
313            .map_err(|e| anyhow!("decode research_analyst_consensus: {e}"))?;
314    if resp.ret_type != 0 {
315        bail!(
316            "research_analyst_consensus ret_type={} msg={:?}",
317            resp.ret_type,
318            resp.ret_msg
319        );
320    }
321    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
322    let out = ResearchAnalystConsensusOut {
323        symbol: symbol.to_string(),
324        highest: s2c.highest,
325        average: s2c.average,
326        lowest: s2c.lowest,
327        rating: s2c.rating,
328        total: s2c.total,
329        update_time: s2c.update_time,
330        update_time_str: s2c.update_time_str,
331        buy: s2c.buy,
332        hold: s2c.hold,
333        sell: s2c.sell,
334        strong_buy: s2c.strong_buy,
335        underperform: s2c.underperform,
336    };
337    Ok(serde_json::to_string_pretty(&out)?)
338}
339
340#[derive(Serialize)]
341struct ResearchRatingSummaryOut {
342    symbol: String,
343    s2c: futu_proto::qot_get_research_rating_summary::S2c,
344}
345
346pub async fn get_research_rating_summary(
347    client: &Arc<FutuClient>,
348    symbol: &str,
349    rating_dimension_type: Option<i32>,
350    uid: Option<&str>,
351    next_key: Option<&str>,
352    num: Option<i32>,
353) -> Result<String> {
354    let sec = parse_symbol(symbol)?;
355    let req = futu_proto::qot_get_research_rating_summary::Request {
356        c2s: futu_proto::qot_get_research_rating_summary::C2s {
357            security: futu_proto::qot_common::Security {
358                market: sec.market as i32,
359                code: sec.code.clone(),
360            },
361            rating_dimension_type,
362            uid: uid.map(str::to_string),
363            next_key: next_key.map(str::to_string),
364            num,
365        },
366    };
367    let body = req.encode_to_vec();
368    let frame = client
369        .request(futu_core::proto_id::QOT_GET_RESEARCH_RATING_SUMMARY, body)
370        .await?;
371    let resp = futu_proto::qot_get_research_rating_summary::Response::decode(frame.body.as_ref())
372        .map_err(|e| anyhow!("decode research_rating_summary: {e}"))?;
373    if resp.ret_type != 0 {
374        bail!(
375            "research_rating_summary ret_type={} msg={:?}",
376            resp.ret_type,
377            resp.ret_msg
378        );
379    }
380    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
381    let out = ResearchRatingSummaryOut {
382        symbol: symbol.to_string(),
383        s2c,
384    };
385    Ok(serde_json::to_string_pretty(&out)?)
386}
387
388#[derive(Serialize)]
389struct ResearchMorningstarReportOut {
390    symbol: String,
391    s2c: futu_proto::qot_get_research_morningstar_report::S2c,
392}
393
394pub async fn get_research_morningstar_report(
395    client: &Arc<FutuClient>,
396    symbol: &str,
397) -> Result<String> {
398    let sec = parse_symbol(symbol)?;
399    let req = futu_proto::qot_get_research_morningstar_report::Request {
400        c2s: futu_proto::qot_get_research_morningstar_report::C2s {
401            security: futu_proto::qot_common::Security {
402                market: sec.market as i32,
403                code: sec.code.clone(),
404            },
405        },
406    };
407    let body = req.encode_to_vec();
408    let frame = client
409        .request(
410            futu_core::proto_id::QOT_GET_RESEARCH_MORNINGSTAR_REPORT,
411            body,
412        )
413        .await?;
414    let resp =
415        futu_proto::qot_get_research_morningstar_report::Response::decode(frame.body.as_ref())
416            .map_err(|e| anyhow!("decode research_morningstar_report: {e}"))?;
417    if resp.ret_type != 0 {
418        bail!(
419            "research_morningstar_report ret_type={} msg={:?}",
420            resp.ret_type,
421            resp.ret_msg
422        );
423    }
424    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
425    let out = ResearchMorningstarReportOut {
426        symbol: symbol.to_string(),
427        s2c,
428    };
429    Ok(serde_json::to_string_pretty(&out)?)
430}
431
432#[derive(Serialize)]
433struct ValuationDetailOut {
434    symbol: String,
435    s2c: futu_proto::qot_get_valuation_detail::S2c,
436}
437
438pub async fn get_valuation_detail(
439    client: &Arc<FutuClient>,
440    symbol: &str,
441    valuation_type: Option<i32>,
442    interval_type: Option<i32>,
443) -> Result<String> {
444    let sec = parse_symbol(symbol)?;
445    let req = futu_proto::qot_get_valuation_detail::Request {
446        c2s: futu_proto::qot_get_valuation_detail::C2s {
447            security: futu_proto::qot_common::Security {
448                market: sec.market as i32,
449                code: sec.code.clone(),
450            },
451            valuation_type,
452            interval_type,
453        },
454    };
455    let body = req.encode_to_vec();
456    let frame = client
457        .request(futu_core::proto_id::QOT_GET_VALUATION_DETAIL, body)
458        .await?;
459    let resp = futu_proto::qot_get_valuation_detail::Response::decode(frame.body.as_ref())
460        .map_err(|e| anyhow!("decode valuation_detail: {e}"))?;
461    if resp.ret_type != 0 {
462        bail!(
463            "valuation_detail ret_type={} msg={:?}",
464            resp.ret_type,
465            resp.ret_msg
466        );
467    }
468    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
469    let out = ValuationDetailOut {
470        symbol: symbol.to_string(),
471        s2c,
472    };
473    Ok(serde_json::to_string_pretty(&out)?)
474}
475
476#[derive(Serialize)]
477struct ValuationPlateStockListOut {
478    symbol: String,
479    s2c: futu_proto::qot_get_valuation_plate_stock_list::S2c,
480}
481
482pub async fn get_valuation_plate_stock_list(
483    client: &Arc<FutuClient>,
484    symbol: &str,
485    valuation_type: Option<i32>,
486    next_key: Option<&str>,
487    num: Option<i32>,
488    sort_type: Option<i32>,
489    sort_id: Option<i32>,
490    filter_security: Option<&str>,
491) -> Result<String> {
492    let sec = parse_symbol(symbol)?;
493    let filter_security = match filter_security {
494        Some(symbol) if !symbol.trim().is_empty() => {
495            let sec = parse_symbol(symbol)?;
496            Some(futu_proto::qot_common::Security {
497                market: sec.market as i32,
498                code: sec.code,
499            })
500        }
501        _ => None,
502    };
503    let req = futu_proto::qot_get_valuation_plate_stock_list::Request {
504        c2s: futu_proto::qot_get_valuation_plate_stock_list::C2s {
505            security: futu_proto::qot_common::Security {
506                market: sec.market as i32,
507                code: sec.code.clone(),
508            },
509            valuation_type,
510            next_key: next_key.map(str::to_string),
511            num,
512            sort_type,
513            sort_id,
514            filter_security,
515        },
516    };
517    let body = req.encode_to_vec();
518    let frame = client
519        .request(
520            futu_core::proto_id::QOT_GET_VALUATION_PLATE_STOCK_LIST,
521            body,
522        )
523        .await?;
524    let resp =
525        futu_proto::qot_get_valuation_plate_stock_list::Response::decode(frame.body.as_ref())
526            .map_err(|e| anyhow!("decode valuation_plate_stock_list: {e}"))?;
527    if resp.ret_type != 0 {
528        bail!(
529            "valuation_plate_stock_list ret_type={} msg={:?}",
530            resp.ret_type,
531            resp.ret_msg
532        );
533    }
534    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
535    let out = ValuationPlateStockListOut {
536        symbol: symbol.to_string(),
537        s2c,
538    };
539    Ok(serde_json::to_string_pretty(&out)?)
540}