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 8 => Some("AU"),
83 15 => Some("JP"),
84 111 => Some("MY"),
85 112 => Some("CA"),
86 113 => Some("HKFUND"),
87 123 => Some("USFUND"),
88 _ => None,
89 }
90}
91
92#[must_use]
101pub fn view_only_fund_market_label(trd_market: i32) -> Option<&'static str> {
102 use crate::currency::{legacy_backend_fund_market_id, trd_market_id};
103
104 match trd_market {
105 legacy_backend_fund_market_id::HK_FUND => Some("HKFund(raw)"),
107 legacy_backend_fund_market_id::US_FUND_OLD => Some("USFund(raw,old)"),
108 legacy_backend_fund_market_id::US_FUND => Some("USFund(raw)"),
109 legacy_backend_fund_market_id::SG_FUND => Some("SGFund(raw)"),
110 trd_market_id::HK_FUND => Some("HKFund"),
112 trd_market_id::US_FUND => Some("USFund"),
113 trd_market_id::SG_FUND => Some("SGFund"),
114 trd_market_id::MY_FUND => Some("MYFund"),
115 trd_market_id::JP_FUND => Some("JPFund"),
116 _ => None,
117 }
118}
119
120pub fn is_futures_code(code: &str) -> bool {
125 let code = strip_market_prefix(code);
126 let code = code.as_str();
127
128 if let Some(stem) = code
129 .strip_suffix("main")
130 .or_else(|| code.strip_suffix(".main"))
131 {
132 let stem = stem.trim_end_matches('.');
133 if (1..=4).contains(&stem.len()) && stem.chars().all(|c| c.is_ascii_alphabetic()) {
134 return true;
135 }
136 }
137
138 if (5..=8).contains(&code.len()) {
139 let len = code.len();
140 let bytes = code.as_bytes();
141 if !bytes[len - 4..].iter().all(|b| b.is_ascii_digit()) {
142 return false;
143 }
144 let prefix = &code[..len - 4];
145 return !prefix.is_empty() && prefix.chars().any(|c| c.is_ascii_alphabetic());
146 }
147
148 false
149}
150
151pub fn futures_ticker_to_sec_market(code: &str) -> Option<i32> {
153 if !is_futures_code(code) {
154 return None;
155 }
156 let ticker = extract_futures_ticker_prefix(code);
157 if matches!(
158 ticker.as_str(),
159 "NQ" | "MNQ"
160 | "ES"
161 | "MES"
162 | "RTY"
163 | "M2K"
164 | "NKD"
165 | "YM"
166 | "MYM"
167 | "6E"
168 | "6J"
169 | "6B"
170 | "6A"
171 | "6C"
172 | "6S"
173 | "6M"
174 | "6N"
175 | "GE"
176 | "SR3"
177 | "BTC"
178 | "MBT"
179 | "ETH"
180 | "MET"
181 | "CL"
182 | "MCL"
183 | "NG"
184 | "MNG"
185 | "HO"
186 | "RB"
187 | "BZ"
188 | "WBS"
189 | "GC"
190 | "MGC"
191 | "SI"
192 | "SIL"
193 | "HG"
194 | "MHG"
195 | "PL"
196 | "PA"
197 | "ZS"
198 | "ZC"
199 | "ZW"
200 | "ZO"
201 | "ZR"
202 | "ZT"
203 | "ZF"
204 | "ZN"
205 | "ZB"
206 | "UB"
207 | "TN"
208 | "VX"
209 | "VXM"
210 ) {
211 return Some(2);
212 }
213 if matches!(
214 ticker.as_str(),
215 "HSI" | "HHI" | "HTI" | "MHI" | "MCH" | "VHSI" | "CSI300" | "CSI500" | "CSI800"
216 ) {
217 return Some(1);
218 }
219 None
220}
221
222pub fn extract_futures_ticker_prefix(code: &str) -> String {
224 let bare = strip_market_prefix(code);
225 let bare = bare.strip_suffix("main").unwrap_or(&bare);
226 let chars: Vec<char> = bare.chars().collect();
227 let len = chars.len();
228 if len >= 4 && chars[len - 4..].iter().all(|c| c.is_ascii_digit()) {
229 return chars[..len - 4].iter().collect::<String>().to_uppercase();
230 }
231 bare.to_uppercase()
232}
233
234#[cfg(test)]
235mod tests;