Skip to main content

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}