futu_backend/stock_list/market.rs
1/// 行情市场类型 (C++ NN_QuoteMktType + MarketID 分组)
2///
3/// 作 CMD 6822/6823 TCP header reserved[0] 时必须先经
4/// `quote_mkt_to_nn_mkt_type` 转成 C++ `NN_QuoteMktType`。
5///
6/// 其中 `DigitalCcy` 虽然是 v1.4.49 扩展进来的 market-id 分组,但线上
7/// C++ 10.5.6508 已有 `NN_QuoteMktType_CRYPTO = 17`,不能再当成
8/// "只用于过滤、不发 backend" 的本地分组。
9#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum QuoteMktType {
13 Unknown = 0,
14 HK = 1,
15 US = 2,
16 SH = 3,
17 SZ = 4,
18 HKFuture = 5,
19 HKFuture2 = 6,
20 USOption = 7,
21 USFuture = 8,
22 HKOption = 9,
23 SHKC = 10,
24 Forex = 11,
25 SGFuture = 12,
26 JPFuture = 13,
27 // v1.4.49 新增:MarketID 范围分组
28 Bond = 14, // 130-159 债券
29 GlobalIndex = 15, // 260-359 全球指数
30 SGSecurity = 16, // 180-184 新加坡股票
31 StockConnect = 17, // 50-51 港股通/A股通
32 DigitalCcy = 18, // 360-459 数字货币
33 TreasuryYield = 19, // 460-559 国债收益率
34 // v1.4.50 新增:仅用于 MarketID 范围过滤,不作 TCP header
35 Fund = 20, // 560-569 基金市场 (C++ NN_QuoteMktID_FUND_*)
36 // v1.4.111: C++ 10.6 GetGlobalState exposes JP/MY stock market state.
37 JPSecurity = 21, // 830-849 日本东证市场
38 MYSecurity = 22, // 1350-1399 马来西亚股票
39}
40
41/// v1.4.68 修正(external reviewer v1.4.57 #5 P2 修):CMD 6822/6823 reserved[0] 实际接受
42/// `NN_QuoteMktType` 值(C++ enum),**不是 `QotCommon.QotMarket` 值**。
43///
44/// C++ 实锤对照 `NNBiz_Qot_EventNotice.cpp::SubEventNotice(enMktType)` L178-188:
45/// CMD 6822 订阅请求 reserved[0] = NN_QuoteMktType 枚举值:
46/// - NN_QuoteMktType_HK = 1
47/// - NN_QuoteMktType_US = 2
48/// - NN_QuoteMktType_SH = 3
49/// - NN_QuoteMktType_SZ = 4
50/// - NN_QuoteMktType_FUT_HK = 5 ← 港期老
51/// - NN_QuoteMktType_FUT_HK_NEW = 6 ← 港期新(主要)
52/// - NN_QuoteMktType_US_OPTIONS = 7
53/// - NN_QuoteMktType_US_FUT = 8 ← 美期
54/// - NN_QuoteMktType_HK_OPTIONS = 9
55/// - NN_QuoteMktType_SH_KCB = 10
56/// - NN_QuoteMktType_Forex = 11
57/// - NN_QuoteMktType_SG_FUTURE = 13 ← 新加坡期货(跳 12)
58/// - NN_QuoteMktType_SG_SECURITY = 15 ← 新加坡股票
59/// - NN_QuoteMktType_JP_FUTURE = 16 ← 日本期货
60/// - NN_QuoteMktType_CRYPTO = 17 ← 加密货币
61/// - NN_QuoteMktType_JP_SECURITY = 25 ← 日本东证市场
62/// - NN_QuoteMktType_MY_SECURITY = 27 ← 马来西亚股票
63///
64/// v1.4.47 P1.2 曾改用 QotMarket 值(HK=1, US=11, ...),以为"QuoteMktType
65/// 不等于 QotMarket 就不对"—— **反了方向**。CMD 6822 backend 接受的是
66/// NN_QuoteMktType。HK/US/SH/SZ 的 QotMarket 值(1/11/21/22)对 backend 来说
67/// 部分 work 是因为:1 ↔ NN_QuoteMktType_HK=1 巧合对齐;其他(US=11 / SH=21
68/// / SZ=22)不在 NN_QuoteMktType 枚举范围内 backend 可能 fallback 全市场
69/// 返回。但 HKFuture / USFuture / SGFuture / JPFuture 都返 0(因为 v1.4.47
70/// 把它们改 reserved[0]=0 导致 backend 不订阅 HK 期货 → external reviewer 报告 #5)。
71///
72/// 本次修法:**按 C++ SubEventNotice 实装**。期货按 FUT_HK_NEW=6 / US_FUT=8
73/// / SG_FUTURE=13 / JP_FUTURE=16 订阅 → backend CMD 6823 响应能返 HK 期货
74/// market_id(5-6 / 110-119)→ pick_market_state 能拿到 HK future state。
75pub(super) fn quote_mkt_to_nn_mkt_type(m: QuoteMktType) -> u8 {
76 match m {
77 QuoteMktType::HK => 1, // NN_QuoteMktType_HK
78 QuoteMktType::US => 2, // NN_QuoteMktType_US
79 QuoteMktType::SH => 3, // NN_QuoteMktType_SH
80 QuoteMktType::SZ => 4, // NN_QuoteMktType_SZ
81 QuoteMktType::HKFuture => 5, // NN_QuoteMktType_FUT_HK(老港期)
82 QuoteMktType::HKFuture2 => 6, // NN_QuoteMktType_FUT_HK_NEW(主要港期)
83 QuoteMktType::USOption => 7, // NN_QuoteMktType_US_OPTIONS
84 QuoteMktType::USFuture => 8, // NN_QuoteMktType_US_FUT
85 QuoteMktType::HKOption => 9, // NN_QuoteMktType_HK_OPTIONS
86 QuoteMktType::SHKC => 10, // NN_QuoteMktType_SH_KCB
87 QuoteMktType::Forex => 11, // NN_QuoteMktType_Forex
88 QuoteMktType::SGFuture => 13, // NN_QuoteMktType_SG_FUTURE(C++ 跳 12)
89 QuoteMktType::SGSecurity => 15, // NN_QuoteMktType_SG_SECURITY
90 QuoteMktType::JPFuture => 16, // NN_QuoteMktType_JP_FUTURE(C++ 跳 14-15)
91 QuoteMktType::DigitalCcy => 17, // C++ NN_QuoteMktType_CRYPTO
92 QuoteMktType::JPSecurity => 25, // NN_QuoteMktType_JP_SECURITY
93 QuoteMktType::MYSecurity => 27, // NN_QuoteMktType_MY_SECURITY
94 // v1.4.49 新增 variant 多数不在 NN_QuoteMktType,仅用于 pick_market_state
95 // 按 MarketID 过滤。返 0 → backend 忽略(CMD 6822 订阅 reserved[0]=0 可能
96 // 表示"通用",不影响其他订阅的市场)。
97 QuoteMktType::Bond
98 | QuoteMktType::GlobalIndex
99 | QuoteMktType::StockConnect
100 | QuoteMktType::TreasuryYield
101 | QuoteMktType::Fund
102 | QuoteMktType::Unknown => 0,
103 }
104}
105
106/// 构造行情命令的 header reserved 字段。
107///
108/// v1.4.47 P1.2 修:`reserved[0]` 用 Futu QotMarket 值(不是内部 enum 值)。
109pub fn make_quote_reserved(mkt: QuoteMktType, ex_type: u8) -> [u8; 10] {
110 let mut reserved = [0u8; 10];
111 reserved[0] = quote_mkt_to_nn_mkt_type(mkt);
112 reserved[1] = ex_type;
113 reserved
114}
115
116/// 参考 C++ `market_tradingDay.proto::MarketID` enum.
117pub fn market_id_matches(mkt: QuoteMktType, id: u32) -> bool {
118 match mkt {
119 // v1.4.50: HK 加 1000-1049(HSI Index 扩展,C++ 映射归 HK 市场)
120 QuoteMktType::HK => (1..=4).contains(&id) || (1000..=1049).contains(&id),
121 // C++ `NN_QuoteMktType_From_NN_QuoteMktID` keeps old/new HK futures
122 // distinct: 5 -> FUT_HK, 6 and 110..=119 -> FUT_HK_NEW.
123 QuoteMktType::HKFuture => id == 5,
124 QuoteMktType::HKFuture2 => id == 6 || (110..=119).contains(&id),
125 // v1.4.50: HKOption 加 570-579(HK Index Option 扩展)
126 QuoteMktType::HKOption => (7..=8).contains(&id) || (570..=579).contains(&id),
127 // v1.4.50: US 加 1200-1249(US New VIX,C++ 映射归 US 市场)
128 QuoteMktType::US => (10..=29).contains(&id) || (1200..=1249).contains(&id),
129 QuoteMktType::SH | QuoteMktType::SZ | QuoteMktType::SHKC => (30..=40).contains(&id),
130 QuoteMktType::USOption => (41..=45).contains(&id),
131 QuoteMktType::USFuture => (60..=109).contains(&id),
132 QuoteMktType::SGFuture => (160..=179).contains(&id),
133 QuoteMktType::JPFuture => (185..=194).contains(&id),
134 QuoteMktType::Forex => (120..=123).contains(&id),
135 // v1.4.49 新增 6 个 variant(对齐 C++ market_tradingDay.proto MarketID)
136 QuoteMktType::StockConnect => (50..=51).contains(&id),
137 QuoteMktType::Bond => (130..=159).contains(&id),
138 QuoteMktType::SGSecurity => (180..=184).contains(&id),
139 QuoteMktType::GlobalIndex => (260..=359).contains(&id),
140 QuoteMktType::DigitalCcy => (360..=459).contains(&id),
141 QuoteMktType::TreasuryYield => (460..=559).contains(&id),
142 // v1.4.50 新增
143 QuoteMktType::Fund => (560..=569).contains(&id),
144 QuoteMktType::JPSecurity => (830..=849).contains(&id),
145 QuoteMktType::MYSecurity => (1350..=1399).contains(&id),
146 QuoteMktType::Unknown => false,
147 }
148}
149
150/// v1.4.73 B1 D5 升级:按"描述市场实际情况"优先级对 `QotMarketState` 打分。
151pub(super) fn market_state_priority(status: u32) -> u8 {
152 match status {
153 0 => 0,
154 // 可交易 / 开盘状态
155 3 | 5 | 13 | 15 | 23 => 1,
156 // 不可交易 / 等待 / 休市 / 精确 pre/after-market 状态
157 1 | 2 | 4 | 6 | 8..=11 | 14 | 16..=19 | 12 => 2,
158 // 未知新增 variant → 保守 fallback
159 _ => 1,
160 }
161}
162
163/// FTAPI QotMarket → 后端 QuoteMktType
164pub fn qot_market_to_backend(qot_market: i32) -> Option<QuoteMktType> {
165 match qot_market {
166 1 => Some(QuoteMktType::HK),
167 2 => Some(QuoteMktType::HKFuture2),
168 11 => Some(QuoteMktType::US),
169 21 => Some(QuoteMktType::SH),
170 22 => Some(QuoteMktType::SZ),
171 31 => Some(QuoteMktType::SGSecurity),
172 41 => Some(QuoteMktType::JPSecurity),
173 61 => Some(QuoteMktType::MYSecurity),
174 91 => Some(QuoteMktType::DigitalCcy),
175 _ => None,
176 }
177}
178
179/// v1.4.47 P1.2 修(external reviewer 验收报告 §3 P2 / §14.3 根因):
180/// FTAPI **TrdMarket** → 后端 `QuoteMktType`。
181pub fn trd_market_to_backend(trd_market: i32) -> Option<QuoteMktType> {
182 match trd_market {
183 1 | 4 => Some(QuoteMktType::HK), // HK / HKCC → HK_Security
184 2 => Some(QuoteMktType::US), // US stock
185 3 => Some(QuoteMktType::SH), // CN A 股(默认 SH;SZ 由 code 前缀区分,
186 // 但 market-state 查询粒度到不了 symbol 层,先用 SH)
187 5 => Some(QuoteMktType::HKFuture), // Futures
188 6 | 124 => Some(QuoteMktType::SGSecurity),
189 7 => Some(QuoteMktType::DigitalCcy),
190 15 | 126 => Some(QuoteMktType::JPSecurity),
191 111 | 125 => Some(QuoteMktType::MYSecurity),
192 // AU=8 / CA=112: C++ NN_QuoteMktType 当前没有 AU/CA 股票枚举,保持 None。
193 _ => None,
194 }
195}