futu_core/
qot_symbol_list.rs1use crate::qot_symbol::validate_symbol_code_len;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct QotSymbolListSecurity {
14 pub market: i32,
15 pub code: String,
16}
17
18impl QotSymbolListSecurity {
19 #[must_use]
20 pub fn new(market: i32, code: impl Into<String>) -> Self {
21 Self {
22 market,
23 code: code.into(),
24 }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ParsedQotSymbolList {
31 pub securities: Vec<QotSymbolListSecurity>,
32}
33
34impl ParsedQotSymbolList {
35 #[must_use]
36 pub fn len(&self) -> usize {
37 self.securities.len()
38 }
39
40 #[must_use]
41 pub fn is_empty(&self) -> bool {
42 self.securities.is_empty()
43 }
44
45 #[must_use]
46 pub fn as_slice(&self) -> &[QotSymbolListSecurity] {
47 &self.securities
48 }
49}
50
51pub fn parse_required_symbol_list(
56 securities: &[QotSymbolListSecurity],
57) -> Result<ParsedQotSymbolList, String> {
58 if securities.is_empty() {
59 return Err(
60 "security_list empty: 必须至少传入 1 个 (market, code) 才能查询列表型行情".to_string(),
61 );
62 }
63
64 for (i, sec) in securities.iter().enumerate() {
65 if sec.market == 0 {
66 return Err(format!(
67 "security_list[{i}] market=0 (QotMarket_Unknown): 必须传入有效 market enum (HK=1 / HK_Future=2 / US=11 / SH=21 / SZ=22 / SG=31 / JP=41 / AU=42 / SG_Future=43 / ...)"
68 ));
69 }
70 if sec.code.is_empty() {
71 return Err(format!(
72 "security_list[{i}] code=\"\": 必须非空 (market={})",
73 sec.market
74 ));
75 }
76 validate_symbol_code_len(&sec.code).map_err(|err| {
77 format!(
78 "security_list[{i}] code length invalid (market={}): {err}",
79 sec.market
80 )
81 })?;
82 }
83
84 Ok(ParsedQotSymbolList {
85 securities: securities.to_vec(),
86 })
87}
88
89pub fn resolve_required_stock_ids<F>(
93 parsed: &ParsedQotSymbolList,
94 mut resolver: F,
95) -> Result<Vec<(QotSymbolListSecurity, u64)>, String>
96where
97 F: FnMut(&QotSymbolListSecurity) -> Option<u64>,
98{
99 let mut resolved: Vec<(QotSymbolListSecurity, u64)> =
100 Vec::with_capacity(parsed.securities.len());
101 let mut missing: Vec<String> = Vec::new();
102
103 for sec in &parsed.securities {
104 match resolver(sec) {
105 Some(stock_id) if stock_id > 0 => resolved.push((sec.clone(), stock_id)),
106 _ => missing.push(format!("(market={}, code={:?})", sec.market, sec.code)),
107 }
108 }
109
110 if !missing.is_empty() {
111 return Err(format!(
112 "无法解析以下 {} 个 symbol 到 stock_id (cache miss / 未知 symbol): [{}] — 请确认 stock_list cache 已刷新或 symbol 拼写正确",
113 missing.len(),
114 missing.join(", ")
115 ));
116 }
117
118 Ok(resolved)
119}