1use 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
12#[derive(Serialize)]
13struct CompanyProfileOut {
14 symbol: String,
15 item_list: Vec<CompanyProfileItemOut>,
16}
17
18#[derive(Serialize)]
19struct CompanyProfileItemOut {
20 name: Option<String>,
21 value: Option<String>,
22 field_type: Option<i32>,
23}
24
25pub async fn get_company_profile(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
26 let sec = parse_symbol(symbol)?;
27 let req = futu_proto::qot_get_company_profile::Request {
28 c2s: futu_proto::qot_get_company_profile::C2s {
29 security: futu_proto::qot_common::Security {
30 market: sec.market as i32,
31 code: sec.code.clone(),
32 },
33 },
34 };
35 let body = req.encode_to_vec();
36 let frame = client
37 .request(futu_core::proto_id::QOT_GET_COMPANY_PROFILE, body)
38 .await?;
39 let resp = futu_proto::qot_get_company_profile::Response::decode(frame.body.as_ref())
40 .map_err(|e| anyhow!("decode company_profile: {e}"))?;
41 if resp.ret_type != 0 {
42 bail!(
43 "company_profile ret_type={} msg={:?}",
44 resp.ret_type,
45 resp.ret_msg
46 );
47 }
48 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
49 let out = CompanyProfileOut {
50 symbol: symbol.to_string(),
51 item_list: s2c
52 .item_list
53 .iter()
54 .map(|item| CompanyProfileItemOut {
55 name: item.name.clone(),
56 value: item.value.clone(),
57 field_type: item.field_type,
58 })
59 .collect(),
60 };
61 Ok(serde_json::to_string_pretty(&out)?)
62}
63
64#[derive(Serialize)]
65struct CompanyExecutivesOut {
66 symbol: String,
67 director_list: Vec<DirectorOut>,
68}
69
70#[derive(Serialize)]
71struct DirectorOut {
72 display_leader_name: Option<String>,
73 leader_name: Option<String>,
74 position_name: Option<String>,
75 begin_date: Option<u64>,
76 begin_date_str: Option<String>,
77 leader_gender: Option<String>,
78 leader_age: Option<String>,
79 highest_education: Option<String>,
80 annual_salary: Option<u64>,
81 issue_date: Option<u64>,
82 issue_date_str: Option<String>,
83}
84
85pub async fn get_company_executives(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
86 let sec = parse_symbol(symbol)?;
87 let req = futu_proto::qot_get_company_executives::Request {
88 c2s: futu_proto::qot_get_company_executives::C2s {
89 security: futu_proto::qot_common::Security {
90 market: sec.market as i32,
91 code: sec.code.clone(),
92 },
93 },
94 };
95 let body = req.encode_to_vec();
96 let frame = client
97 .request(futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVES, body)
98 .await?;
99 let resp = futu_proto::qot_get_company_executives::Response::decode(frame.body.as_ref())
100 .map_err(|e| anyhow!("decode company_executives: {e}"))?;
101 if resp.ret_type != 0 {
102 bail!(
103 "company_executives ret_type={} msg={:?}",
104 resp.ret_type,
105 resp.ret_msg
106 );
107 }
108 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
109 let out = CompanyExecutivesOut {
110 symbol: symbol.to_string(),
111 director_list: s2c
112 .director_list
113 .iter()
114 .map(|item| DirectorOut {
115 display_leader_name: item.display_leader_name.clone(),
116 leader_name: item.leader_name.clone(),
117 position_name: item.position_name.clone(),
118 begin_date: item.begin_date,
119 begin_date_str: item.begin_date_str.clone(),
120 leader_gender: item.leader_gender.clone(),
121 leader_age: item.leader_age.clone(),
122 highest_education: item.highest_education.clone(),
123 annual_salary: item.annual_salary,
124 issue_date: item.issue_date,
125 issue_date_str: item.issue_date_str.clone(),
126 })
127 .collect(),
128 };
129 Ok(serde_json::to_string_pretty(&out)?)
130}
131
132#[derive(Serialize)]
133struct CompanyExecutiveBackgroundOut {
134 symbol: String,
135 leader_name: String,
136 brief_background: Option<String>,
137}
138
139pub async fn get_company_executive_background(
140 client: &Arc<FutuClient>,
141 symbol: &str,
142 leader_name: &str,
143) -> Result<String> {
144 let sec = parse_symbol(symbol)?;
145 let req = futu_proto::qot_get_company_executive_background::Request {
146 c2s: futu_proto::qot_get_company_executive_background::C2s {
147 security: futu_proto::qot_common::Security {
148 market: sec.market as i32,
149 code: sec.code.clone(),
150 },
151 leader_name: Some(leader_name.to_string()),
152 },
153 };
154 let body = req.encode_to_vec();
155 let frame = client
156 .request(
157 futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVE_BACKGROUND,
158 body,
159 )
160 .await?;
161 let resp =
162 futu_proto::qot_get_company_executive_background::Response::decode(frame.body.as_ref())
163 .map_err(|e| anyhow!("decode company_executive_background: {e}"))?;
164 if resp.ret_type != 0 {
165 bail!(
166 "company_executive_background ret_type={} msg={:?}",
167 resp.ret_type,
168 resp.ret_msg
169 );
170 }
171 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
172 let out = CompanyExecutiveBackgroundOut {
173 symbol: symbol.to_string(),
174 leader_name: leader_name.to_string(),
175 brief_background: s2c.brief_background,
176 };
177 Ok(serde_json::to_string_pretty(&out)?)
178}
179
180#[derive(Serialize)]
181struct CompanyOperationalEfficiencyOut {
182 symbol: String,
183 item_list: Vec<OperationalEfficiencyItemOut>,
184 next_key: Option<String>,
185 currency_code: Option<String>,
186}
187
188#[derive(Serialize)]
189struct OperationalEfficiencyItemOut {
190 fiscal_year: Option<i32>,
191 financial_type: Option<i32>,
192 period_text: Option<String>,
193 end_date: Option<i64>,
194 end_date_str: Option<String>,
195 employee_num: Option<i64>,
196 employee_num_yoy: Option<f64>,
197 income_per_capita: Option<f64>,
198 income_per_capita_yoy: Option<f64>,
199 profit_per_capita: Option<f64>,
200 profit_per_capita_yoy: Option<f64>,
201 net_profit_per_capita: Option<f64>,
202 net_profit_per_capita_yoy: Option<f64>,
203}
204
205pub async fn get_company_operational_efficiency(
206 client: &Arc<FutuClient>,
207 symbol: &str,
208 next_key: Option<&str>,
209 num: Option<i32>,
210 currency_code: Option<&str>,
211 financial_type: Option<i32>,
212) -> Result<String> {
213 let sec = parse_symbol(symbol)?;
214 let req = futu_proto::qot_get_company_operational_efficiency::Request {
215 c2s: futu_proto::qot_get_company_operational_efficiency::C2s {
216 security: futu_proto::qot_common::Security {
217 market: sec.market as i32,
218 code: sec.code.clone(),
219 },
220 next_key: next_key.map(str::to_string),
221 num,
222 currency_code: currency_code.map(str::to_string),
223 financial_type,
224 },
225 };
226 let body = req.encode_to_vec();
227 let frame = client
228 .request(
229 futu_core::proto_id::QOT_GET_COMPANY_OPERATIONAL_EFFICIENCY,
230 body,
231 )
232 .await?;
233 let resp =
234 futu_proto::qot_get_company_operational_efficiency::Response::decode(frame.body.as_ref())
235 .map_err(|e| anyhow!("decode company_operational_efficiency: {e}"))?;
236 if resp.ret_type != 0 {
237 bail!(
238 "company_operational_efficiency ret_type={} msg={:?}",
239 resp.ret_type,
240 resp.ret_msg
241 );
242 }
243 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
244 let out = CompanyOperationalEfficiencyOut {
245 symbol: symbol.to_string(),
246 item_list: s2c
247 .item_list
248 .iter()
249 .map(|item| OperationalEfficiencyItemOut {
250 fiscal_year: item.fiscal_year,
251 financial_type: item.financial_type,
252 period_text: item.period_text.clone(),
253 end_date: item.end_date,
254 end_date_str: item.end_date_str.clone(),
255 employee_num: item.employee_num,
256 employee_num_yoy: item.employee_num_yoy,
257 income_per_capita: item.income_per_capita,
258 income_per_capita_yoy: item.income_per_capita_yoy,
259 profit_per_capita: item.profit_per_capita,
260 profit_per_capita_yoy: item.profit_per_capita_yoy,
261 net_profit_per_capita: item.net_profit_per_capita,
262 net_profit_per_capita_yoy: item.net_profit_per_capita_yoy,
263 })
264 .collect(),
265 next_key: s2c.next_key,
266 currency_code: s2c.currency_code,
267 };
268 Ok(serde_json::to_string_pretty(&out)?)
269}
270
271pub async fn get_financials_earnings_price_move(
272 client: &Arc<FutuClient>,
273 symbol: &str,
274 period_count: Option<i32>,
275) -> Result<String> {
276 let sec = parse_symbol(symbol)?;
277 let req = futu_proto::qot_get_financials_earnings_price_move::Request {
278 c2s: futu_proto::qot_get_financials_earnings_price_move::C2s {
279 security: futu_proto::qot_common::Security {
280 market: sec.market as i32,
281 code: sec.code.clone(),
282 },
283 period_count,
284 },
285 };
286 let body = req.encode_to_vec();
287 let frame = client
288 .request(
289 futu_core::proto_id::QOT_GET_FINANCIALS_EARNINGS_PRICE_MOVE,
290 body,
291 )
292 .await?;
293 let resp =
294 futu_proto::qot_get_financials_earnings_price_move::Response::decode(frame.body.as_ref())
295 .map_err(|e| anyhow!("decode financials_earnings_price_move: {e}"))?;
296 if resp.ret_type != 0 {
297 bail!(
298 "financials_earnings_price_move ret_type={} msg={:?}",
299 resp.ret_type,
300 resp.ret_msg
301 );
302 }
303 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
304 Ok(serde_json::to_string_pretty(&serde_json::json!({
305 "symbol": symbol,
306 "detail_list": s2c.detail_list,
307 }))?)
308}
309
310pub async fn get_financials_earnings_price_history(
311 client: &Arc<FutuClient>,
312 symbol: &str,
313) -> Result<String> {
314 let sec = parse_symbol(symbol)?;
315 let req = futu_proto::qot_get_financials_earnings_price_history::Request {
316 c2s: futu_proto::qot_get_financials_earnings_price_history::C2s {
317 security: futu_proto::qot_common::Security {
318 market: sec.market as i32,
319 code: sec.code.clone(),
320 },
321 },
322 };
323 let body = req.encode_to_vec();
324 let frame = client
325 .request(
326 futu_core::proto_id::QOT_GET_FINANCIALS_EARNINGS_PRICE_HISTORY,
327 body,
328 )
329 .await?;
330 let resp = futu_proto::qot_get_financials_earnings_price_history::Response::decode(
331 frame.body.as_ref(),
332 )
333 .map_err(|e| anyhow!("decode financials_earnings_price_history: {e}"))?;
334 if resp.ret_type != 0 {
335 bail!(
336 "financials_earnings_price_history ret_type={} msg={:?}",
337 resp.ret_type,
338 resp.ret_msg
339 );
340 }
341 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
342 Ok(serde_json::to_string_pretty(&serde_json::json!({
343 "symbol": symbol,
344 "detail_list": s2c.detail_list,
345 }))?)
346}
347
348#[derive(Serialize)]
349struct FinancialsStatementsOut {
350 symbol: String,
351 structure_list: Vec<futu_proto::qot_get_financials_statements::FinancialFieldInfo>,
352 report_list: Vec<futu_proto::qot_get_financials_statements::FinancialReport>,
353 next_key: Option<String>,
354}
355
356pub async fn get_financials_statements(
357 client: &Arc<FutuClient>,
358 symbol: &str,
359 statement_type: Option<i32>,
360 financial_type: Option<i32>,
361 currency_code: Option<&str>,
362 next_key: Option<&str>,
363 num: Option<i32>,
364) -> Result<String> {
365 let sec = parse_symbol(symbol)?;
366 let req = futu_proto::qot_get_financials_statements::Request {
367 c2s: futu_proto::qot_get_financials_statements::C2s {
368 security: futu_proto::qot_common::Security {
369 market: sec.market as i32,
370 code: sec.code.clone(),
371 },
372 statement_type,
373 financial_type,
374 currency_code: currency_code.map(str::to_string),
375 next_key: next_key.map(str::to_string),
376 num,
377 },
378 };
379 let body = req.encode_to_vec();
380 let frame = client
381 .request(futu_core::proto_id::QOT_GET_FINANCIALS_STATEMENTS, body)
382 .await?;
383 let resp = futu_proto::qot_get_financials_statements::Response::decode(frame.body.as_ref())
384 .map_err(|e| anyhow!("decode financials_statements: {e}"))?;
385 if resp.ret_type != 0 {
386 bail!(
387 "financials_statements ret_type={} msg={:?}",
388 resp.ret_type,
389 resp.ret_msg
390 );
391 }
392 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
393 let out = FinancialsStatementsOut {
394 symbol: symbol.to_string(),
395 structure_list: s2c.structure_list,
396 report_list: s2c.report_list,
397 next_key: s2c.next_key,
398 };
399 Ok(serde_json::to_string_pretty(&out)?)
400}
401
402#[derive(Serialize)]
403struct FinancialsRevenueBreakdownOut {
404 symbol: String,
405 period: Option<String>,
406 breakdown_list: Vec<futu_proto::qot_get_financials_revenue_breakdown::RevenueBreakdownGroup>,
407 currency_code: Option<String>,
408 screen_date_list: Vec<futu_proto::qot_get_financials_revenue_breakdown::ScreenDate>,
409}
410
411pub async fn get_financials_revenue_breakdown(
412 client: &Arc<FutuClient>,
413 symbol: &str,
414 date: Option<u32>,
415 financial_type: Option<i32>,
416 currency_code: Option<&str>,
417) -> Result<String> {
418 let sec = parse_symbol(symbol)?;
419 let req = futu_proto::qot_get_financials_revenue_breakdown::Request {
420 c2s: futu_proto::qot_get_financials_revenue_breakdown::C2s {
421 security: futu_proto::qot_common::Security {
422 market: sec.market as i32,
423 code: sec.code.clone(),
424 },
425 date,
426 financial_type,
427 currency_code: currency_code.map(str::to_string),
428 },
429 };
430 let body = req.encode_to_vec();
431 let frame = client
432 .request(
433 futu_core::proto_id::QOT_GET_FINANCIALS_REVENUE_BREAKDOWN,
434 body,
435 )
436 .await?;
437 let resp =
438 futu_proto::qot_get_financials_revenue_breakdown::Response::decode(frame.body.as_ref())
439 .map_err(|e| anyhow!("decode financials_revenue_breakdown: {e}"))?;
440 if resp.ret_type != 0 {
441 bail!(
442 "financials_revenue_breakdown ret_type={} msg={:?}",
443 resp.ret_type,
444 resp.ret_msg
445 );
446 }
447 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
448 let out = FinancialsRevenueBreakdownOut {
449 symbol: symbol.to_string(),
450 period: s2c.period,
451 breakdown_list: s2c.breakdown_list,
452 currency_code: s2c.currency_code,
453 screen_date_list: s2c.screen_date_list,
454 };
455 Ok(serde_json::to_string_pretty(&out)?)
456}
457
458#[derive(Serialize)]
459struct ResearchAnalystConsensusOut {
460 symbol: String,
461 highest: Option<f64>,
462 average: Option<f64>,
463 lowest: Option<f64>,
464 rating: Option<i32>,
465 total: Option<i32>,
466 update_time: Option<i64>,
467 update_time_str: Option<String>,
468 buy: Option<f64>,
469 hold: Option<f64>,
470 sell: Option<f64>,
471 strong_buy: Option<f64>,
472 underperform: Option<f64>,
473}
474
475pub async fn get_research_analyst_consensus(
476 client: &Arc<FutuClient>,
477 symbol: &str,
478) -> Result<String> {
479 let sec = parse_symbol(symbol)?;
480 let req = futu_proto::qot_get_research_analyst_consensus::Request {
481 c2s: futu_proto::qot_get_research_analyst_consensus::C2s {
482 security: futu_proto::qot_common::Security {
483 market: sec.market as i32,
484 code: sec.code.clone(),
485 },
486 },
487 };
488 let body = req.encode_to_vec();
489 let frame = client
490 .request(
491 futu_core::proto_id::QOT_GET_RESEARCH_ANALYST_CONSENSUS,
492 body,
493 )
494 .await?;
495 let resp =
496 futu_proto::qot_get_research_analyst_consensus::Response::decode(frame.body.as_ref())
497 .map_err(|e| anyhow!("decode research_analyst_consensus: {e}"))?;
498 if resp.ret_type != 0 {
499 bail!(
500 "research_analyst_consensus ret_type={} msg={:?}",
501 resp.ret_type,
502 resp.ret_msg
503 );
504 }
505 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
506 let out = ResearchAnalystConsensusOut {
507 symbol: symbol.to_string(),
508 highest: s2c.highest,
509 average: s2c.average,
510 lowest: s2c.lowest,
511 rating: s2c.rating,
512 total: s2c.total,
513 update_time: s2c.update_time,
514 update_time_str: s2c.update_time_str,
515 buy: s2c.buy,
516 hold: s2c.hold,
517 sell: s2c.sell,
518 strong_buy: s2c.strong_buy,
519 underperform: s2c.underperform,
520 };
521 Ok(serde_json::to_string_pretty(&out)?)
522}
523
524#[derive(Serialize)]
525struct ResearchRatingSummaryOut {
526 symbol: String,
527 s2c: futu_proto::qot_get_research_rating_summary::S2c,
528}
529
530pub async fn get_research_rating_summary(
531 client: &Arc<FutuClient>,
532 symbol: &str,
533 rating_dimension_type: Option<i32>,
534 uid: Option<&str>,
535 next_key: Option<&str>,
536 num: Option<i32>,
537) -> Result<String> {
538 let sec = parse_symbol(symbol)?;
539 let req = futu_proto::qot_get_research_rating_summary::Request {
540 c2s: futu_proto::qot_get_research_rating_summary::C2s {
541 security: futu_proto::qot_common::Security {
542 market: sec.market as i32,
543 code: sec.code.clone(),
544 },
545 rating_dimension_type,
546 uid: uid.map(str::to_string),
547 next_key: next_key.map(str::to_string),
548 num,
549 },
550 };
551 let body = req.encode_to_vec();
552 let frame = client
553 .request(futu_core::proto_id::QOT_GET_RESEARCH_RATING_SUMMARY, body)
554 .await?;
555 let resp = futu_proto::qot_get_research_rating_summary::Response::decode(frame.body.as_ref())
556 .map_err(|e| anyhow!("decode research_rating_summary: {e}"))?;
557 if resp.ret_type != 0 {
558 bail!(
559 "research_rating_summary ret_type={} msg={:?}",
560 resp.ret_type,
561 resp.ret_msg
562 );
563 }
564 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
565 let out = ResearchRatingSummaryOut {
566 symbol: symbol.to_string(),
567 s2c,
568 };
569 Ok(serde_json::to_string_pretty(&out)?)
570}
571
572#[derive(Serialize)]
573struct ResearchMorningstarReportOut {
574 symbol: String,
575 s2c: futu_proto::qot_get_research_morningstar_report::S2c,
576}
577
578pub async fn get_research_morningstar_report(
579 client: &Arc<FutuClient>,
580 symbol: &str,
581) -> Result<String> {
582 let sec = parse_symbol(symbol)?;
583 let req = futu_proto::qot_get_research_morningstar_report::Request {
584 c2s: futu_proto::qot_get_research_morningstar_report::C2s {
585 security: futu_proto::qot_common::Security {
586 market: sec.market as i32,
587 code: sec.code.clone(),
588 },
589 },
590 };
591 let body = req.encode_to_vec();
592 let frame = client
593 .request(
594 futu_core::proto_id::QOT_GET_RESEARCH_MORNINGSTAR_REPORT,
595 body,
596 )
597 .await?;
598 let resp =
599 futu_proto::qot_get_research_morningstar_report::Response::decode(frame.body.as_ref())
600 .map_err(|e| anyhow!("decode research_morningstar_report: {e}"))?;
601 if resp.ret_type != 0 {
602 bail!(
603 "research_morningstar_report ret_type={} msg={:?}",
604 resp.ret_type,
605 resp.ret_msg
606 );
607 }
608 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
609 let out = ResearchMorningstarReportOut {
610 symbol: symbol.to_string(),
611 s2c,
612 };
613 Ok(serde_json::to_string_pretty(&out)?)
614}
615
616#[derive(Serialize)]
617struct ValuationDetailOut {
618 symbol: String,
619 s2c: futu_proto::qot_get_valuation_detail::S2c,
620}
621
622pub async fn get_valuation_detail(
623 client: &Arc<FutuClient>,
624 symbol: &str,
625 valuation_type: Option<i32>,
626 interval_type: Option<i32>,
627) -> Result<String> {
628 let sec = parse_symbol(symbol)?;
629 let req = futu_proto::qot_get_valuation_detail::Request {
630 c2s: futu_proto::qot_get_valuation_detail::C2s {
631 security: futu_proto::qot_common::Security {
632 market: sec.market as i32,
633 code: sec.code.clone(),
634 },
635 valuation_type,
636 interval_type,
637 },
638 };
639 let body = req.encode_to_vec();
640 let frame = client
641 .request(futu_core::proto_id::QOT_GET_VALUATION_DETAIL, body)
642 .await?;
643 let resp = futu_proto::qot_get_valuation_detail::Response::decode(frame.body.as_ref())
644 .map_err(|e| anyhow!("decode valuation_detail: {e}"))?;
645 if resp.ret_type != 0 {
646 bail!(
647 "valuation_detail ret_type={} msg={:?}",
648 resp.ret_type,
649 resp.ret_msg
650 );
651 }
652 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
653 let out = ValuationDetailOut {
654 symbol: symbol.to_string(),
655 s2c,
656 };
657 Ok(serde_json::to_string_pretty(&out)?)
658}
659
660#[derive(Serialize)]
661struct ValuationPlateStockListOut {
662 symbol: String,
663 s2c: futu_proto::qot_get_valuation_plate_stock_list::S2c,
664}
665
666pub async fn get_valuation_plate_stock_list(
667 client: &Arc<FutuClient>,
668 symbol: &str,
669 valuation_type: Option<i32>,
670 next_key: Option<&str>,
671 num: Option<i32>,
672 sort_type: Option<i32>,
673 sort_id: Option<i32>,
674 filter_security: Option<&str>,
675) -> Result<String> {
676 let sec = parse_symbol(symbol)?;
677 let filter_security = match filter_security {
678 Some(symbol) if !symbol.trim().is_empty() => {
679 let sec = parse_symbol(symbol)?;
680 Some(futu_proto::qot_common::Security {
681 market: sec.market as i32,
682 code: sec.code,
683 })
684 }
685 _ => None,
686 };
687 let req = futu_proto::qot_get_valuation_plate_stock_list::Request {
688 c2s: futu_proto::qot_get_valuation_plate_stock_list::C2s {
689 security: futu_proto::qot_common::Security {
690 market: sec.market as i32,
691 code: sec.code.clone(),
692 },
693 valuation_type,
694 next_key: next_key.map(str::to_string),
695 num,
696 sort_type,
697 sort_id,
698 filter_security,
699 },
700 };
701 let body = req.encode_to_vec();
702 let frame = client
703 .request(
704 futu_core::proto_id::QOT_GET_VALUATION_PLATE_STOCK_LIST,
705 body,
706 )
707 .await?;
708 let resp =
709 futu_proto::qot_get_valuation_plate_stock_list::Response::decode(frame.body.as_ref())
710 .map_err(|e| anyhow!("decode valuation_plate_stock_list: {e}"))?;
711 if resp.ret_type != 0 {
712 bail!(
713 "valuation_plate_stock_list ret_type={} msg={:?}",
714 resp.ret_type,
715 resp.ret_msg
716 );
717 }
718 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
719 let out = ValuationPlateStockListOut {
720 symbol: symbol.to_string(),
721 s2c,
722 };
723 Ok(serde_json::to_string_pretty(&out)?)
724}