futu_core/market.rs
1//! 市场 enum 与时区 dispatch(v1.4.69 跨 crate 共享)
2//!
3//! v1.4.68 以前 `bridge/utils.rs` 和 `qot/util.rs` 各维护一份 `qot_market_to_tz`
4//! 实装(L40 一行 match)。注释约定 "修改需同步 sync"。v1.4.69 合并到
5//! `futu-core::market`,两个调用方都 `use futu_core::market::qot_market_to_tz`
6//! 避免重复维护。
7//!
8//! 对齐 C++ `APIServer_Inner_API.cpp::GetTimeZoneByAPIQotMkt` (L3684) — 按
9//! FTAPI `QotMarket` enum 值分 IANA 时区。
10//!
11//! **映射表**:
12//!
13//! | FTAPI QotMarket | 值 | IANA Tz | C++ E_StandardTime |
14//! |---|---|---|---|
15//! | HK_Security | 1 | Asia/Hong_Kong | China |
16//! | HK_Future (deprecated) | 2 | Asia/Hong_Kong | China |
17//! | US_Security | 11 | America/New_York (DST) | USEastern |
18//! | CNSH_Security | 21 | Asia/Hong_Kong | China |
19//! | CNSZ_Security | 22 | Asia/Hong_Kong | China |
20//! | SG_Security | 31 | Asia/Singapore | SG |
21//! | JP_Security | 41 | Asia/Tokyo | JP |
22//! | AU_Security | 51 | Australia/Sydney (DST) | AU |
23//! | MY_Security | 61 | Asia/Kuala_Lumpur | MY |
24//! | CA_Security | 71 | America/Toronto (DST) | CA |
25//! | FX_Security | 81 | Asia/Hong_Kong (fallback) | — |
26//! | 其他/Unknown | — | Asia/Hong_Kong (fallback 零回归) | China default |
27
28/// **Stable API** (since v1.4.69) — 市场 dispatch 表 entry(跨 crate 共享)。
29///
30/// 合并 3 个 fn 的重复 match case:
31/// - `sec_market_from_code_prefix`(prefix → sec_market)
32/// - `sec_market_to_qot_market`(sec_market → qot_market)
33/// - `sec_market_to_exchange_str`(sec_market → exchange_str)
34///
35/// 每 entry 覆盖一个 market family(9 个标准市场)。新加市场只需加表一行,
36/// 3 个 helper 自动支持(vs 之前 3 处独立 match 容易漏 case)。
37///
38/// **CN 特例**:CN prefix 没有标 sec_market(由 code 首数字判 SH(31) vs
39/// SZ(32)),因此 `prefix == "CN."` 的 entry 不放表里,`sec_market_from_code_prefix`
40/// 对 `CN.` 单独处理。
41pub struct MarketDispatchEntry {
42 pub prefix: &'static str,
43 pub sec_market: i32,
44 pub qot_market: u32,
45 pub exchange_str: &'static str,
46}
47
48/// **Stable API** (since v1.4.69) — 9 个标准市场 dispatch 表常量。
49///
50/// 对齐 C++ `_NNProto_Trd_Comm.cpp::GetStockExchangeByMktID` + FTAPI
51/// QotMarket/TrdSecMarket proto 枚举。
52pub const MARKET_DISPATCH: &[MarketDispatchEntry] = &[
53 MarketDispatchEntry {
54 prefix: "HK.",
55 sec_market: 1,
56 qot_market: 1,
57 exchange_str: "SEHK",
58 },
59 MarketDispatchEntry {
60 prefix: "US.",
61 sec_market: 2,
62 qot_market: 11,
63 exchange_str: "US",
64 },
65 MarketDispatchEntry {
66 prefix: "SH.",
67 sec_market: 31,
68 qot_market: 21,
69 exchange_str: "SSE",
70 },
71 MarketDispatchEntry {
72 prefix: "SZ.",
73 sec_market: 32,
74 qot_market: 22,
75 exchange_str: "SZSE",
76 },
77 MarketDispatchEntry {
78 prefix: "SG.",
79 sec_market: 41,
80 qot_market: 31,
81 exchange_str: "SGX",
82 },
83 MarketDispatchEntry {
84 prefix: "JP.",
85 sec_market: 51,
86 qot_market: 41,
87 exchange_str: "TSE",
88 },
89 MarketDispatchEntry {
90 prefix: "AU.",
91 sec_market: 61,
92 qot_market: 51,
93 exchange_str: "ASX",
94 },
95 MarketDispatchEntry {
96 prefix: "MY.",
97 sec_market: 71,
98 qot_market: 61,
99 exchange_str: "BURSA",
100 },
101 MarketDispatchEntry {
102 prefix: "CA.",
103 sec_market: 81,
104 qot_market: 71,
105 exchange_str: "TSX",
106 },
107];
108
109/// **Stable API** (since v1.4.69) — 查 sec_market 对应的 entry(O(n),n=9 常数时间)。
110pub fn entry_by_sec_market(sec_market: i32) -> Option<&'static MarketDispatchEntry> {
111 MARKET_DISPATCH.iter().find(|e| e.sec_market == sec_market)
112}
113
114/// **Stable API** (since v1.4.69) — 查 code prefix 对应的 entry(含 `CN.` 特殊分派)。
115///
116/// `CN.` 按 code 首数字判 SH(31) vs SZ(32):
117/// - `6` / `9` → SH entry (sec_market=31)
118/// - `0` / `2` / `3` → SZ entry (sec_market=32)
119/// - 其他 → SH entry(default)
120pub fn entry_by_code_prefix(code: &str) -> Option<&'static MarketDispatchEntry> {
121 // CN. 特殊分派(SH/SZ by first digit)
122 if let Some(bare) = code.strip_prefix("CN.") {
123 let sec_market = match bare.chars().next() {
124 Some('6') | Some('9') => 31,
125 Some('0') | Some('2') | Some('3') => 32,
126 _ => 31, // default SH
127 };
128 return entry_by_sec_market(sec_market);
129 }
130 MARKET_DISPATCH.iter().find(|e| code.starts_with(e.prefix))
131}
132
133/// **Stable API** (since v1.4.69) — FTAPI QotMarket 值 → IANA 时区 dispatch。
134///
135/// 已知未覆盖:FX_Security / 未知 market 值 → HKT fallback 保证零回归。
136#[must_use]
137pub fn qot_market_to_tz(market: i32) -> chrono_tz::Tz {
138 match market {
139 1 | 2 | 21 | 22 => chrono_tz::Asia::Hong_Kong, // HK / HK_Future / CNSH / CNSZ
140 11 => chrono_tz::America::New_York, // US (DST-aware)
141 31 => chrono_tz::Asia::Singapore, // SG
142 41 => chrono_tz::Asia::Tokyo, // JP
143 51 => chrono_tz::Australia::Sydney, // AU (DST-aware)
144 61 => chrono_tz::Asia::Kuala_Lumpur, // MY
145 71 => chrono_tz::America::Toronto, // CA (DST-aware)
146 _ => chrono_tz::Asia::Hong_Kong, // 未知 fallback HKT
147 }
148}
149
150/// **Stable API** (since v1.4.71) — FTAPI `TrdMarket` 值 → IANA 时区 dispatch。
151///
152/// **对齐 C++ `GetTimeZoneByTrdMkt`**(`APIServer_Inner_API.cpp:4022`),用于:
153/// 1. 用户传的 `begin_time` / `end_time` 字符串解析(C++ `APITimeStrToTimeStamp_Trd`)
154/// 2. backend 返回的时间戳 → market local 时间字符串(C++ `TimeStampToAPITimeStr_Trd`)
155/// 3. 历史查询 default time range fallback(查 US 账户 "最近 90 天" 应按 US tz 算)
156///
157/// | FTAPI TrdMarket | 值 | 时区 | C++ `E_StandardTime` |
158/// |---|---|---|---|
159/// | HK / CN / HKCC | 1/3/4 | Asia/Hong_Kong (UTC+8) | China |
160/// | US | 2 | America/New_York (DST-aware EDT/EST) | USEastern |
161/// | SG | 6 | Asia/Singapore (UTC+8) | SG |
162/// | AU | 8 | Australia/Sydney (DST-aware AEDT/AEST) | AU |
163/// | JP | 15 | Asia/Tokyo (UTC+9) | JP |
164/// | MY | 111 | Asia/Kuala_Lumpur (UTC+8) | MY |
165/// | CA | 112 | America/Toronto (DST-aware EDT/EST) | CA |
166/// | Futures (5) / Unknown (0) | 5/0/其他 | Asia/Hong_Kong fallback | China |
167///
168/// **Futures 细分限制**:C++ Futures 按 `mkt_id` (CME→US/NYMEX→US/SGX→SG/HKFE→HK)
169/// 细分,Rust 当前 order/orderfill struct **没有** `mkt_id` 上下文,只能 fallback
170/// HKT。未来从 quote cache pass `mkt_id` 时再扩(`CachedSecurityInfo.mkt_id` 已有
171/// v1.4.59 加的字段,待 Phase D 全量 wire)。
172#[must_use]
173pub fn trd_market_to_tz(trd_market: i32) -> chrono_tz::Tz {
174 match trd_market {
175 1 | 3 | 4 => chrono_tz::Asia::Hong_Kong, // HK / CN / HKCC
176 2 => chrono_tz::America::New_York, // US (DST-aware)
177 6 => chrono_tz::Asia::Singapore, // SG
178 8 => chrono_tz::Australia::Sydney, // AU (DST-aware)
179 15 => chrono_tz::Asia::Tokyo, // JP
180 111 => chrono_tz::Asia::Kuala_Lumpur, // MY
181 112 => chrono_tz::America::Toronto, // CA (DST-aware)
182 // Futures (5) / Unknown (0) / 未映射:fallback HKT
183 // C++ Futures 按 mkt_id 细分,Rust struct 无此上下文 → 保守 HKT
184 _ => chrono_tz::Asia::Hong_Kong,
185 }
186}
187
188#[cfg(test)]
189mod tests;