1pub fn strip_market_prefix(code: &str) -> String {
11 const MARKET_PREFIXES: &[&str] = &[
12 "HK.", "US.", "SH.", "SZ.", "SG.", "JP.", "AU.", "CA.", "MY.", "BJ.", "CN.",
13 ];
14 for prefix in MARKET_PREFIXES {
15 if let Some(stripped) = code.strip_prefix(prefix) {
16 return stripped.to_string();
17 }
18 }
19 code.to_string()
20}
21
22pub fn derive_sec_market(ftapi_sec_market: i32, trd_market: i32, code: &str) -> i32 {
28 if let Some(from_prefix) = sec_market_from_code_prefix(code) {
29 return from_prefix;
30 }
31
32 if let Some(from_ticker) = futures_ticker_to_sec_market(code) {
33 return from_ticker;
34 }
35
36 if ftapi_sec_market != 0 {
37 return ftapi_sec_market;
38 }
39
40 match trd_market {
41 1 | 4 | 113 => 1,
42 2 | 11 | 123 => 2,
43 3 => {
44 let bare = code
45 .trim_start_matches("SH.")
46 .trim_start_matches("SZ.")
47 .trim_start_matches("CN.");
48 match bare.chars().next() {
49 Some('6') | Some('9') => 31,
50 Some('0') | Some('2') | Some('3') => 32,
51 _ => 31,
52 }
53 }
54 6 | 12 | 124 => 41,
55 8 => 61,
56 15 | 13 | 126 => 51,
57 111 | 125 => 71,
58 112 => 81,
59 _ => 0,
60 }
61}
62
63pub fn sec_market_from_code_prefix(code: &str) -> Option<i32> {
65 futu_core::market::entry_by_code_prefix(code).map(|e| e.sec_market)
66}
67
68#[must_use]
74pub fn trd_market_label(i: i32) -> Option<&'static str> {
75 match i {
76 1 => Some("HK"),
77 2 => Some("US"),
78 3 => Some("CN"),
79 4 => Some("HKCC"),
80 5 => Some("FUTURES"),
81 6 => Some("SG"),
82 7 => Some("CRYPTO"),
83 8 => Some("AU"),
84 10 => Some("FUTURES_SIMULATE_HK"),
85 11 => Some("FUTURES_SIMULATE_US"),
86 12 => Some("FUTURES_SIMULATE_SG"),
87 13 => Some("FUTURES_SIMULATE_JP"),
88 15 => Some("JP"),
89 111 => Some("MY"),
90 112 => Some("CA"),
91 113 => Some("HKFUND"),
92 123 => Some("USFUND"),
93 124 => Some("SGFUND"),
94 125 => Some("MYFUND"),
95 126 => Some("JPFUND"),
96 _ => None,
97 }
98}
99
100#[must_use]
109pub fn view_only_fund_market_label(trd_market: i32) -> Option<&'static str> {
110 use crate::currency::{legacy_backend_fund_market_id, trd_market_id};
111
112 match trd_market {
113 legacy_backend_fund_market_id::HK_FUND => Some("HKFund(raw)"),
115 legacy_backend_fund_market_id::US_FUND_OLD => Some("USFund(raw,old)"),
116 legacy_backend_fund_market_id::US_FUND => Some("USFund(raw)"),
117 legacy_backend_fund_market_id::SG_FUND => Some("SGFund(raw)"),
118 trd_market_id::HK_FUND => Some("HKFund"),
120 trd_market_id::US_FUND => Some("USFund"),
121 trd_market_id::SG_FUND => Some("SGFund"),
122 trd_market_id::MY_FUND => Some("MYFund"),
123 trd_market_id::JP_FUND => Some("JPFund"),
124 _ => None,
125 }
126}
127
128pub fn is_futures_code(code: &str) -> bool {
133 let code = strip_market_prefix(code);
134 let code = code.as_str();
135
136 if let Some(stem) = code
137 .strip_suffix("main")
138 .or_else(|| code.strip_suffix(".main"))
139 {
140 let stem = stem.trim_end_matches('.');
141 if (1..=4).contains(&stem.len()) && stem.chars().all(|c| c.is_ascii_alphabetic()) {
142 return true;
143 }
144 }
145
146 if (5..=8).contains(&code.len()) {
147 let len = code.len();
148 let bytes = code.as_bytes();
149 if !bytes[len - 4..].iter().all(|b| b.is_ascii_digit()) {
150 return false;
151 }
152 let ticker = &bytes[..len - 4];
153 return (1..=4).contains(&ticker.len())
154 && ticker.iter().all(|b| b.is_ascii_alphanumeric())
155 && ticker.iter().any(|b| b.is_ascii_alphabetic());
156 }
157
158 false
159}
160
161pub fn futures_ticker_to_sec_market(code: &str) -> Option<i32> {
163 if !is_futures_code(code) {
164 return None;
165 }
166 let ticker = extract_futures_ticker_prefix(code);
167 if matches!(
168 ticker.as_str(),
169 "NQ" | "MNQ"
170 | "ES"
171 | "MES"
172 | "RTY"
173 | "M2K"
174 | "NKD"
175 | "YM"
176 | "MYM"
177 | "6E"
178 | "6J"
179 | "6B"
180 | "6A"
181 | "6C"
182 | "6S"
183 | "6M"
184 | "6N"
185 | "GE"
186 | "SR3"
187 | "BTC"
188 | "MBT"
189 | "ETH"
190 | "MET"
191 | "CL"
192 | "MCL"
193 | "NG"
194 | "MNG"
195 | "HO"
196 | "RB"
197 | "BZ"
198 | "WBS"
199 | "GC"
200 | "MGC"
201 | "SI"
202 | "SIL"
203 | "HG"
204 | "MHG"
205 | "PL"
206 | "PA"
207 | "ZS"
208 | "ZC"
209 | "ZW"
210 | "ZO"
211 | "ZR"
212 | "ZT"
213 | "ZF"
214 | "ZN"
215 | "ZB"
216 | "UB"
217 | "TN"
218 | "VX"
219 | "VXM"
220 ) {
221 return Some(2);
222 }
223 if matches!(
224 ticker.as_str(),
225 "HSI" | "HHI" | "HTI" | "MHI" | "MCH" | "VHSI" | "CSI300" | "CSI500" | "CSI800"
226 ) {
227 return Some(1);
228 }
229 None
230}
231
232pub fn extract_futures_ticker_prefix(code: &str) -> String {
234 let bare = strip_market_prefix(code);
235 let bare = bare.strip_suffix("main").unwrap_or(&bare);
236 let chars: Vec<char> = bare.chars().collect();
237 let len = chars.len();
238 if len >= 4 && chars[len - 4..].iter().all(|c| c.is_ascii_digit()) {
239 return chars[..len - 4].iter().collect::<String>().to_uppercase();
240 }
241 bare.to_uppercase()
242}
243
244#[cfg(test)]
245mod tests;