futu_core/
trade_security.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct TrdSecMarketInput<'a> {
10 pub ftapi_sec_market: i32,
11 pub trd_market: i32,
12 pub code: &'a str,
13}
14
15#[must_use]
19pub fn strip_market_prefix(code: &str) -> String {
20 const MARKET_PREFIXES: &[&str] = &[
23 "HK.", "US.", "SH.", "SZ.", "SG.", "JP.", "AU.", "CA.", "MY.", "BJ.", "CN.", "CRYPTO.",
24 "CC.",
25 ];
26 for prefix in MARKET_PREFIXES {
27 if let Some(stripped) = code.strip_prefix(prefix) {
28 return stripped.to_string();
29 }
30 }
31 code.to_string()
32}
33
34#[must_use]
39pub fn is_option_code(code: &str) -> bool {
40 let code = strip_market_prefix(code);
41 let code = code.as_str();
42
43 if code.len() < 10 {
44 return false;
45 }
46 let has_cp_late = code
47 .char_indices()
48 .any(|(i, c)| i >= 5 && (c == 'C' || c == 'P'));
49 let has_digit = code.chars().any(|c| c.is_ascii_digit());
50 let has_alpha = code.chars().any(|c| c.is_ascii_alphabetic());
51 has_cp_late && has_digit && has_alpha
52}
53
54#[must_use]
56pub fn is_hk_index_option_code(code: &str) -> bool {
57 let code = strip_market_prefix(code);
58 let code = code.as_str();
59 let prefix: String = code
60 .chars()
61 .take_while(|c| c.is_ascii_alphabetic())
62 .collect();
63 matches!(
64 prefix.to_uppercase().as_str(),
65 "HSI" | "HHI" | "HTI" | "MHI" | "MCH"
66 )
67}
68
69#[must_use]
74pub fn is_futures_code(code: &str) -> bool {
75 let code = strip_market_prefix(code);
76 let code = code.as_str();
77
78 if let Some(stem) = code
79 .strip_suffix("main")
80 .or_else(|| code.strip_suffix(".main"))
81 {
82 let stem = stem.trim_end_matches('.');
83 if (1..=4).contains(&stem.len()) && stem.chars().all(|c| c.is_ascii_alphabetic()) {
84 return true;
85 }
86 }
87
88 if (5..=8).contains(&code.len()) {
89 let len = code.len();
90 let bytes = code.as_bytes();
91 if !bytes[len - 4..].iter().all(|b| b.is_ascii_digit()) {
92 return false;
93 }
94 let ticker = &bytes[..len - 4];
95 return (1..=4).contains(&ticker.len())
96 && ticker.iter().all(|b| b.is_ascii_alphanumeric())
97 && ticker.iter().any(|b| b.is_ascii_alphabetic());
98 }
99
100 false
101}
102
103#[must_use]
105pub fn extract_futures_ticker_prefix_like_cpp(code: &str) -> String {
106 let bare = strip_market_prefix(code);
107 let bare = bare.strip_suffix("main").unwrap_or(&bare);
108 let chars: Vec<char> = bare.chars().collect();
109 let len = chars.len();
110 if len >= 4 && chars[len - 4..].iter().all(|c| c.is_ascii_digit()) {
111 return chars[..len - 4].iter().collect::<String>().to_uppercase();
112 }
113 bare.to_uppercase()
114}
115
116#[must_use]
118pub fn sec_market_from_code_prefix_like_cpp(code: &str) -> Option<i32> {
119 crate::market::entry_by_code_prefix(code).map(|entry| entry.sec_market)
120}
121
122#[must_use]
128pub fn futures_ticker_to_sec_market_like_cpp(code: &str) -> Option<i32> {
129 if !is_futures_code(code) {
130 return None;
131 }
132 let ticker = extract_futures_ticker_prefix_like_cpp(code);
133 if matches!(
134 ticker.as_str(),
135 "NQ" | "MNQ"
136 | "ES"
137 | "MES"
138 | "RTY"
139 | "M2K"
140 | "NKD"
141 | "YM"
142 | "MYM"
143 | "6E"
144 | "6J"
145 | "6B"
146 | "6A"
147 | "6C"
148 | "6S"
149 | "6M"
150 | "6N"
151 | "GE"
152 | "SR3"
153 | "BTC"
154 | "MBT"
155 | "ETH"
156 | "MET"
157 | "CL"
158 | "MCL"
159 | "NG"
160 | "MNG"
161 | "HO"
162 | "RB"
163 | "BZ"
164 | "WBS"
165 | "GC"
166 | "MGC"
167 | "SI"
168 | "SIL"
169 | "HG"
170 | "MHG"
171 | "PL"
172 | "PA"
173 | "ZS"
174 | "ZC"
175 | "ZW"
176 | "ZO"
177 | "ZR"
178 | "ZT"
179 | "ZF"
180 | "ZN"
181 | "ZB"
182 | "UB"
183 | "TN"
184 | "VX"
185 | "VXM"
186 ) {
187 return Some(2);
188 }
189 if matches!(
190 ticker.as_str(),
191 "HSI" | "HHI" | "HTI" | "MHI" | "MCH" | "VHSI" | "CSI300" | "CSI500" | "CSI800"
192 ) {
193 return Some(1);
194 }
195 None
196}
197
198#[must_use]
204pub fn derive_trd_sec_market_like_cpp(input: TrdSecMarketInput<'_>) -> i32 {
205 if let Some(from_prefix) = sec_market_from_code_prefix_like_cpp(input.code) {
206 return from_prefix;
207 }
208
209 if let Some(from_ticker) = futures_ticker_to_sec_market_like_cpp(input.code) {
210 return from_ticker;
211 }
212
213 if input.ftapi_sec_market != 0 {
214 return input.ftapi_sec_market;
215 }
216
217 match input.trd_market {
218 1 | 4 | 10 | 113 => 1,
222 2 | 11 | 123 => 2,
223 3 => {
224 let bare = input
225 .code
226 .trim_start_matches("SH.")
227 .trim_start_matches("SZ.")
228 .trim_start_matches("CN.");
229 match bare.chars().next() {
230 Some('6') | Some('9') => 31,
231 Some('0') | Some('2') | Some('3') => 32,
232 _ => 31,
233 }
234 }
235 6 | 12 | 124 => 41,
236 7 => 101,
240 8 => 61,
241 15 | 13 | 126 => 51,
242 17 => 111,
245 111 | 125 => 71,
246 112 => 81,
247 _ => 0,
248 }
249}