Skip to main content

futu_core/qot_right_gate/
read.rs

1use crate::diagnostic_text::jp_stock_quote_permission_hint;
2
3use super::common::{qot_permission_action, qot_right_is_none, us_future_right_for_mkt};
4use super::constants::{QOT_RIGHT_BMP, QOT_RIGHT_LEVEL1, QOT_RIGHT_NO};
5use super::model::{QotRightSnapshot, SecurityRightClass};
6
7const QOT_MARKET_EVENT_CONTRACT: i32 = 101;
8const QOT_MARKET_JP_SECURITY: i32 = 41;
9const SECURITY_TYPE_EQTY: i32 = 3;
10const REPRESENTATIVE_JP_CASH_TSE_MKT_ID: u32 = 830;
11
12pub fn qot_read_right_reject_reason(
13    market: i32,
14    code: &str,
15    security: SecurityRightClass,
16    rights: &QotRightSnapshot,
17) -> Option<String> {
18    // C++ CheckHasQotRight gates read-style QOT/F10 endpoints and is deliberately
19    // narrower than IsHasRightToSub. Notably, ordinary HK securities are not
20    // rejected here merely because HK real-time rights are BMP.
21    // Ref: FutuOpenD/Src/APIServer/APIServer_Inner_API.cpp:1037-1143.
22    // Public market 101 is the fixed FTAPI EventContract ABI value. C++
23    // identifies EventContract from the public market and rejects the
24    // explicit None right before serving quote data.
25    // Ref: APIServer_Inner_API.cpp:635-638,1288-1295.
26    if market == QOT_MARKET_EVENT_CONTRACT && rights.event_contract_qot_right == QOT_RIGHT_NO {
27        return Some(format!(
28            "EventContract 行情权限不足,不能获取 {code};\
29             请检查 EventContract quote permissions。"
30        ));
31    }
32
33    if security.is_us_security(market) {
34        if security.is_index() && rights.us_index_qot_right == QOT_RIGHT_BMP {
35            return Some(format!(
36                "US index 行情权限不足,不能获取 {code};{}",
37                qot_permission_action(Some("US"), code)
38            ));
39        }
40        if security.is_us_otc() && rights.us_otc_qot_right == QOT_RIGHT_BMP {
41            return Some(format!(
42                "US OTC 行情权限不足,不能获取 {code};{}",
43                qot_permission_action(Some("US"), code)
44            ));
45        }
46        if rights.us_qot_right == QOT_RIGHT_BMP {
47            return Some(format!(
48                "US 行情权限不足,不能获取 {code};{}",
49                qot_permission_action(Some("US"), code)
50            ));
51        }
52    } else if market == 11 && security.is_option() {
53        if rights.us_option_qot_right == QOT_RIGHT_BMP {
54            return Some(format!(
55                "US option 行情权限不足,不能获取 {code};{}",
56                qot_permission_action(Some("US"), code)
57            ));
58        }
59    } else if security.is_future(market) && (60..=109).contains(&security.mkt_id) {
60        if us_future_right_for_mkt(rights, security.mkt_id) == QOT_RIGHT_BMP {
61            return Some(format!(
62                "US future 行情权限不足,不能获取 {code};{}",
63                qot_permission_action(Some("US"), code)
64            ));
65        }
66    } else if security.is_sh_market(market) {
67        if rights.sh_qot_right == QOT_RIGHT_BMP {
68            return Some(format!(
69                "SH 行情权限不足,不能获取 {code};{}",
70                qot_permission_action(Some("SH"), code)
71            ));
72        }
73    } else if security.is_sz_market(market) {
74        if rights.sz_qot_right == QOT_RIGHT_BMP {
75            return Some(format!(
76                "SZ 行情权限不足,不能获取 {code};{}",
77                qot_permission_action(Some("SZ"), code)
78            ));
79        }
80    } else if security.is_sg_future() {
81        if rights.sg_future_qot_right == QOT_RIGHT_BMP {
82            return Some(format!(
83                "SG future 行情权限不足,不能获取 {code};{}",
84                qot_permission_action(Some("SG"), code)
85            ));
86        }
87    } else if security.is_jp_future() {
88        if rights.jp_future_qot_right == QOT_RIGHT_BMP {
89            return Some(format!(
90                "JP future 行情权限不足,不能获取 {code};{}",
91                qot_permission_action(Some("JP"), code)
92            ));
93        }
94    } else if security.is_sg_security_market(market) {
95        if qot_right_is_none(rights.sg_stock_qot_right) {
96            return Some(format!(
97                "SG stock 行情权限不足,不能获取 {code};{}",
98                qot_permission_action(Some("SG"), code)
99            ));
100        }
101    } else if security.is_my_security_market(market) {
102        if qot_right_is_none(rights.my_stock_qot_right) {
103            return Some(format!(
104                "MY stock 行情权限不足,不能获取 {code};{}",
105                qot_permission_action(Some("MY"), code)
106            ));
107        }
108    } else if security.is_jp_security_market(market) {
109        if qot_right_is_none(rights.jp_stock_qot_right) {
110            return Some(format!(
111                "JP stock / 日股行情权限不足,不能获取 {code};{}",
112                jp_stock_quote_permission_hint().text_with_code()
113            ));
114        }
115    } else if security.is_crypto(market) && rights.cc_qot_right != QOT_RIGHT_LEVEL1 {
116        return Some(format!(
117            "Crypto 行情权限不足,不能获取 {code};\
118             请检查 Crypto MarketCrypto quote permissions;{}。",
119            qot_permission_action(Some("Crypto"), code)
120        ));
121    }
122
123    None
124}
125
126pub fn qot_read_right_reject_reason_for_jp_stock_without_static(
127    market: i32,
128    code: &str,
129    rights: &QotRightSnapshot,
130) -> Option<String> {
131    if market != QOT_MARKET_JP_SECURITY {
132        return None;
133    }
134
135    // Static cache miss means we do not yet know the exact JP sub-market, but
136    // public QotMarket=41 + an explicit "no JP stock right" is enough to give
137    // the actionable C++-aligned right-gate hint instead of masking it as
138    // "unknown security". Use a representative JP cash mkt_id in the TSE
139    // range; C++ APIServer_Inner_API.cpp:1245-1252 / 4505-4512 gates JP cash
140    // securities on JP stock quote right.
141    qot_read_right_reject_reason(
142        market,
143        code,
144        SecurityRightClass {
145            sec_type: SECURITY_TYPE_EQTY,
146            mkt_id: REPRESENTATIVE_JP_CASH_TSE_MKT_ID,
147            option_code_like: false,
148        },
149        rights,
150    )
151}