Skip to main content

futu_cache/
qot_market.rs

1//! v1.4.106 codex 1148 F2 (P2): 公共 `Qot_Common.QotMarket` → symbol suffix
2//! 转换。
3//!
4//! 此前 `crates/futu-gateway-qot/src/handlers/qot/option.rs::qot_market_to_symbol_suffix`
5//! 与 `crates/futu-gateway-core/src/bridge/mkt_id_refresh.rs` 各有一份 inline 表,
6//! 后者跑漂移 (`51 → HK_Future`、`62 → MY`、`72 → CA`、漏 `81 → FX`)。
7//! 收敛为单一 source of truth,对齐 proto 枚举:
8//!
9//! 来源: `proto/Qot_Common.proto:8-21`
10//!   ```text
11//!   QotMarket_Unknown        = 0;
12//!   QotMarket_HK_Security    = 1;   // 香港市场
13//!   QotMarket_HK_Future      = 2;   // 港期货 (已废弃, 使用 HK_Security)
14//!   QotMarket_US_Security    = 11;  // 美国市场
15//!   QotMarket_CNSH_Security  = 21;  // 沪股市场
16//!   QotMarket_CNSZ_Security  = 22;  // 深股市场
17//!   QotMarket_SG_Security    = 31;  // 新加坡市场
18//!   QotMarket_JP_Security    = 41;  // 日本市场
19//!   QotMarket_AU_Security    = 51;  // 澳大利亚市场
20//!   QotMarket_MY_Security    = 61;  // 马来西亚市场
21//!   QotMarket_CA_Security    = 71;  // 加拿大市场
22//!   QotMarket_FX_Security    = 81;  // 外汇市场
23//!   QotMarket_CC_Security    = 91;  // 加密货币市场
24//!   ```
25//!
26//! 后端 CMD 20106 `SecuritiesReq.symbols` 用 `"CODE.SUFFIX"` 格式 (e.g.
27//! `"AAPL.US"` / `"00700.HK"`)。本 helper 把 FTAPI QotMarket 值转为对应的
28//! 后端 symbol 后缀。
29//!
30//! 注意: 后端真正的"symbol_suffix"是从 `quote_config_svr.proto:82` 下发的
31//! 配置而不是硬编码 (C++ 不在源码内 hardcode 这个表)。daemon 直连 backend
32//! 时不查 quote_config,因此这里维护一份硬编码版作 cache-first 路由。下次
33//! backend 增加新市场时此处需同步更新。
34
35/// FTAPI `Qot_Common.QotMarket` (proto enum 值) → backend symbol suffix
36/// (`"CODE.{SUFFIX}"` 用于 CMD 20106 `SecuritiesReq.symbols`).
37///
38/// 返 `None` 表示 daemon 当前不支持的 market — caller 应**显式** skip /
39/// reject 而非 fallthrough。
40///
41/// **支持的市场**:
42/// - `1` (HK_Security) → `"HK"`
43/// - `2` (HK_Future, 已废弃) → `"HK"` (alias HK_Security, 与 option.rs
44///   既有行为一致, 因为 backend 把 HK_Future symbol 当 HK 处理)
45/// - `11` (US_Security) → `"US"`
46/// - `21` (CNSH_Security) → `"SH"`
47/// - `22` (CNSZ_Security) → `"SZ"`
48/// - `31` (SG_Security) → `"SG"`
49/// - `41` (JP_Security) → `"JP"`
50/// - `51` (AU_Security) → `"AU"`
51/// - `61` (MY_Security) → `"MY"`
52/// - `71` (CA_Security) → `"CA"`
53/// - `81` (FX_Security) → `"FX"`
54/// - `91` (CC_Security) → `"CC"`
55///
56/// **不支持的市场返 None**: 0 (Unknown) / 任何其他值。
57#[must_use]
58pub fn qot_market_to_symbol_suffix(market: i32) -> Option<&'static str> {
59    match market {
60        1 | 2 => Some("HK"), // HK_Security / HK_Future (deprecated alias)
61        11 => Some("US"),    // US_Security
62        21 => Some("SH"),    // CNSH_Security
63        22 => Some("SZ"),    // CNSZ_Security
64        31 => Some("SG"),    // SG_Security
65        41 => Some("JP"),    // JP_Security
66        51 => Some("AU"),    // AU_Security
67        61 => Some("MY"),    // MY_Security
68        71 => Some("CA"),    // CA_Security
69        81 => Some("FX"),    // FX_Security
70        91 => Some("CC"),    // CC_Security
71        _ => None,           // Unknown / 未支持
72    }
73}
74
75/// Build backend CMD 20106 `SecuritiesReq.symbols` entry from FTAPI market+code.
76///
77/// Ref: moomoo
78/// `Src/FTQuant/Src/FTQuantServer/APIProto_StockInfoReq.cpp:487-550`
79/// `MakeReqSymbol`.
80///
81/// Most securities are `"CODE.SUFFIX"`, but plate/list codes are special:
82/// `LIST*` is converted to `BK*`, and existing `BK*` codes are sent without
83/// suffix. That mirrors C++ instead of treating every user code as a plain
84/// exchange symbol.
85#[must_use]
86pub fn make_cmd20106_symbol(market: i32, code: &str) -> Option<String> {
87    let code = code.trim();
88    if code.is_empty() {
89        return None;
90    }
91    if let Some(rest) = code.strip_prefix("LIST") {
92        return Some(format!("BK{rest}"));
93    }
94    if code.starts_with("BK") {
95        return Some(code.to_string());
96    }
97    qot_market_to_symbol_suffix(market).map(|suffix| format!("{code}.{suffix}"))
98}
99
100/// Strip the synthetic suffix added by [`make_cmd20106_symbol`] when matching
101/// CMD 20106 response symbols back to cache keys.
102#[must_use]
103pub fn cmd20106_symbol_code(symbol: &str) -> &str {
104    symbol.split_once('.').map_or(symbol, |(code, _)| code)
105}
106
107#[cfg(test)]
108mod tests;