Skip to main content

futu_core/qot_right_gate/
subscribe.rs

1use crate::diagnostic_text::jp_stock_quote_permission_hint;
2
3use super::common::{
4    qot_permission_action, qot_right_denies_realtime, qot_right_is_none, us_future_right_for_mkt,
5};
6use super::constants::{QOT_RIGHT_LEVEL1, QOT_RIGHT_NO, QOT_RIGHT_UNKNOWN};
7use super::model::{QotRightSnapshot, QotSubRightSecurity, SecurityRightClass};
8
9const QOT_MARKET_EVENT_CONTRACT: i32 = 101;
10
11pub fn qot_sub_right_reject_reason(
12    market: i32,
13    code: &str,
14    security: SecurityRightClass,
15    sub_types: &[i32],
16    rights: &QotRightSnapshot,
17) -> Option<String> {
18    // Public market 101 is the fixed FTAPI EventContract ABI value. Do not
19    // infer this market from static metadata: C++ selects the EventContract
20    // subscription instance from the public market before checking its
21    // dedicated right.
22    // Ref: APIServer_Inner_API.cpp:635-638,1288-1295;
23    // APIServer_Qot_Sub.cpp:151-159.
24    if market == QOT_MARKET_EVENT_CONTRACT && rights.event_contract_qot_right == QOT_RIGHT_NO {
25        return Some(format!(
26            "Subscribe: EventContract 行情权限不足,不能操作 {code};\
27             请检查 EventContract quote permissions。"
28        ));
29    }
30
31    let wants_orderbook = sub_types.contains(&2);
32    let wants_ticker = sub_types.contains(&4);
33    let wants_broker = sub_types.contains(&14);
34    let is_option = security.is_option();
35    let is_hk_future = security.is_hk_future();
36    let is_future = security.is_future(market);
37    let is_index = security.is_index();
38    let is_crypto = security.is_crypto(market);
39    let is_sg_stock = security.is_sg_security_market(market);
40    let is_my_stock = security.is_my_security_market(market);
41    let is_jp_stock = security.is_jp_security_market(market);
42    let is_other = !is_option && !is_future && !is_index;
43
44    match market {
45        _ if is_crypto && qot_right_denies_realtime(rights.cc_qot_right) => Some(format!(
46            "Subscribe: crypto 行情权限不足,不能订阅 {code};\
47                 请检查 Crypto MarketCrypto quote permissions;{}。",
48            qot_permission_action(Some("Crypto"), code)
49        )),
50        21 if qot_right_denies_realtime(rights.sh_qot_right) => Some(format!(
51            "Subscribe: 沪股行情权限不足,不能订阅 SH market;{}。",
52            qot_permission_action(Some("SH"), code)
53        )),
54        22 if qot_right_denies_realtime(rights.sz_qot_right) => Some(format!(
55            "Subscribe: 深股行情权限不足,不能订阅 SZ market;{}。",
56            qot_permission_action(Some("SZ"), code)
57        )),
58        31 if security.is_sg_future() && qot_right_denies_realtime(rights.sg_future_qot_right) => {
59            Some(format!(
60                "Subscribe: SG future 行情权限不足,不能订阅 SG future;{}。",
61                qot_permission_action(Some("SG"), code)
62            ))
63        }
64        31 if is_sg_stock && qot_right_is_none(rights.sg_stock_qot_right) => Some(format!(
65            "Subscribe: SG stock 行情权限不足,不能订阅 {code};{}。",
66            qot_permission_action(Some("SG"), code)
67        )),
68        61 if is_my_stock && qot_right_is_none(rights.my_stock_qot_right) => Some(format!(
69            "Subscribe: MY stock 行情权限不足,不能订阅 {code};{}。",
70            qot_permission_action(Some("MY"), code)
71        )),
72        41 if security.is_jp_future() && qot_right_denies_realtime(rights.jp_future_qot_right) => {
73            Some(format!(
74                "Subscribe: JP future 行情权限不足,不能订阅 JP future;{}。",
75                qot_permission_action(Some("JP"), code)
76            ))
77        }
78        41 if is_jp_stock && qot_right_is_none(rights.jp_stock_qot_right) => Some(format!(
79            "Subscribe: JP stock / 日股行情权限不足,不能订阅 {code};{}",
80            jp_stock_quote_permission_hint().text_with_code()
81        )),
82        1 if is_other || is_index => {
83            if qot_right_denies_realtime(rights.hk_qot_right) {
84                Some(format!(
85                    "Subscribe: HK 行情权限不足,不能订阅 {code};{}。",
86                    qot_permission_action(Some("HK"), code)
87                ))
88            } else if wants_broker && rights.hk_qot_right == QOT_RIGHT_LEVEL1 {
89                Some(format!(
90                    "Subscribe: HK Level1 权限不支持 broker queue 订阅 ({code})。"
91                ))
92            } else {
93                None
94            }
95        }
96        1 if is_option => {
97            if qot_right_denies_realtime(rights.hk_option_qot_right) {
98                Some(format!(
99                    "Subscribe: HK option 行情权限不足,不能订阅 {code}。"
100                ))
101            } else if wants_orderbook && rights.hk_option_orderbook_depth == Some(0) {
102                Some(format!(
103                    "Subscribe: HK option order book depth 为 0,不能订阅摆盘 ({code})。"
104                ))
105            } else if wants_ticker && rights.hk_option_qot_right == QOT_RIGHT_LEVEL1 {
106                Some(format!(
107                    "Subscribe: HK option Level1 权限不支持 ticker 订阅 ({code});Ticker 需要 LV2。"
108                ))
109            } else {
110                None
111            }
112        }
113        1 if is_hk_future => {
114            if qot_right_denies_realtime(rights.hk_future_qot_right) {
115                Some(format!(
116                    "Subscribe: HK future 行情权限不足,不能订阅 {code}。"
117                ))
118            } else if wants_orderbook && rights.hk_future_orderbook_depth == Some(0) {
119                Some(format!(
120                    "Subscribe: HK future order book depth 为 0,不能订阅摆盘 ({code})。"
121                ))
122            } else if wants_ticker && rights.hk_future_qot_right == QOT_RIGHT_LEVEL1 {
123                Some(format!(
124                    "Subscribe: HK future Level1 权限不支持 ticker 订阅 ({code});Ticker 需要 LV2。"
125                ))
126            } else {
127                None
128            }
129        }
130        11 if is_option && qot_right_denies_realtime(rights.us_option_qot_right) => Some(format!(
131            "Subscribe: US option 行情权限不足,不能订阅 {code};{}。",
132            qot_permission_action(Some("US"), code)
133        )),
134        11 if is_future => {
135            let right = us_future_right_for_mkt(rights, security.mkt_id);
136            if right == QOT_RIGHT_UNKNOWN || qot_right_denies_realtime(right) {
137                Some(format!(
138                    "Subscribe: US future 行情权限不足或未知,不能订阅 {code};{}。",
139                    qot_permission_action(Some("US"), code)
140                ))
141            } else {
142                None
143            }
144        }
145        11 if is_index && qot_right_denies_realtime(rights.us_index_qot_right) => Some(format!(
146            "Subscribe: US index 行情权限不足,不能订阅 {code};{}。",
147            qot_permission_action(Some("US"), code)
148        )),
149        11 if security.mkt_id == 13 && qot_right_denies_realtime(rights.us_otc_qot_right) => {
150            Some(format!(
151                "Subscribe: US OTC 行情权限不足,不能订阅 {code};{}。",
152                qot_permission_action(Some("US"), code)
153            ))
154        }
155        11 if is_other && qot_right_denies_realtime(rights.us_qot_right) => Some(format!(
156            "Subscribe: US 行情权限不足,不能订阅 {code};{}。",
157            qot_permission_action(Some("US"), code)
158        )),
159        _ => None,
160    }
161}
162
163pub fn first_qot_sub_right_reject_reason(
164    securities: &[QotSubRightSecurity<'_>],
165    sub_types: &[i32],
166    rights: &QotRightSnapshot,
167) -> Option<String> {
168    securities.iter().find_map(|security| {
169        qot_sub_right_reject_reason(
170            security.market,
171            security.code,
172            security.security,
173            sub_types,
174            rights,
175        )
176    })
177}