1pub const SECURITY_TYPE_DRVT: i32 = 8;
8pub const SECURITY_TYPE_INDEX: i32 = 6;
9pub const SECURITY_TYPE_FUTURE: i32 = 10;
10pub const SECURITY_TYPE_CRYPTO: i32 = 12;
11pub const QOT_MARKET_CC_SECURITY: i32 = 91;
12pub const QOT_RIGHT_UNKNOWN: i32 = 0;
13pub const QOT_RIGHT_BMP: i32 = 1;
14pub const QOT_RIGHT_LEVEL1: i32 = 2;
15pub const QOT_RIGHT_NO: i32 = 5;
16
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
18pub struct SecurityRightClass {
19 pub sec_type: i32,
20 pub mkt_id: u32,
21 pub option_code_like: bool,
22}
23
24impl SecurityRightClass {
25 fn is_option(self) -> bool {
26 self.sec_type == SECURITY_TYPE_DRVT || self.option_code_like
27 }
28
29 fn is_future(self, public_market: i32) -> bool {
30 self.sec_type == SECURITY_TYPE_FUTURE
31 || (public_market == 11 && (60..=109).contains(&self.mkt_id))
32 }
33
34 fn is_hk_future(self) -> bool {
35 self.sec_type == SECURITY_TYPE_FUTURE && matches!(self.mkt_id, 5 | 6 | 110..=119)
39 }
40
41 fn is_us_security(self, public_market: i32) -> bool {
42 matches!(self.mkt_id, 10..=29 | 1200..=1249)
43 || (public_market == 11 && !self.is_future(public_market) && !self.is_option())
44 }
45
46 fn is_us_otc(self) -> bool {
47 self.mkt_id == 13
50 }
51
52 fn is_index(self) -> bool {
53 self.sec_type == SECURITY_TYPE_INDEX
54 }
55
56 fn is_crypto(self, public_market: i32) -> bool {
57 self.sec_type == SECURITY_TYPE_CRYPTO
58 || public_market == QOT_MARKET_CC_SECURITY
59 || (360..=459).contains(&self.mkt_id)
60 }
61
62 fn is_sh_market(self, public_market: i32) -> bool {
63 matches!(self.mkt_id, 30 | 32 | 33 | 34 | 36..=40) || public_market == 21
64 }
65
66 fn is_sz_market(self, public_market: i32) -> bool {
67 matches!(self.mkt_id, 31 | 35) || public_market == 22
68 }
69
70 fn is_sg_future(self) -> bool {
71 self.sec_type == SECURITY_TYPE_FUTURE && (160..=179).contains(&self.mkt_id)
72 }
73
74 fn is_jp_future(self) -> bool {
75 self.sec_type == SECURITY_TYPE_FUTURE && (185..=194).contains(&self.mkt_id)
76 }
77
78 fn is_sg_security_market(self, public_market: i32) -> bool {
79 (180..=184).contains(&self.mkt_id)
82 || (public_market == 31
83 && self.sec_type != SECURITY_TYPE_FUTURE
84 && self.sec_type != SECURITY_TYPE_DRVT)
85 }
86
87 fn is_my_security_market(self, public_market: i32) -> bool {
88 (1350..=1399).contains(&self.mkt_id)
91 || (public_market == 61
92 && self.sec_type != SECURITY_TYPE_FUTURE
93 && self.sec_type != SECURITY_TYPE_DRVT)
94 }
95
96 fn is_jp_security_market(self, public_market: i32) -> bool {
97 (830..=849).contains(&self.mkt_id)
101 || (public_market == 41
102 && self.sec_type != SECURITY_TYPE_FUTURE
103 && self.sec_type != SECURITY_TYPE_DRVT
104 && !self.is_jp_future()
105 && !(800..=829).contains(&self.mkt_id))
106 }
107}
108
109#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
110pub struct QotRightSnapshot {
111 pub hk_qot_right: i32,
112 pub us_qot_right: i32,
113 pub sh_qot_right: i32,
114 pub sz_qot_right: i32,
115 pub hk_option_qot_right: i32,
116 pub hk_future_qot_right: i32,
117 pub hk_option_orderbook_depth: Option<u32>,
118 pub hk_future_orderbook_depth: Option<u32>,
119 pub us_option_qot_right: i32,
120 pub us_index_qot_right: i32,
121 pub us_otc_qot_right: i32,
122 pub us_cme_future_qot_right: i32,
123 pub us_cbot_future_qot_right: i32,
124 pub us_nymex_future_qot_right: i32,
125 pub us_comex_future_qot_right: i32,
126 pub us_cboe_future_qot_right: i32,
127 pub sg_future_qot_right: i32,
128 pub jp_future_qot_right: i32,
129 pub sg_stock_qot_right: i32,
130 pub my_stock_qot_right: i32,
131 pub jp_stock_qot_right: i32,
132 pub cc_qot_right: i32,
133}
134
135fn qot_right_denies_realtime(right: i32) -> bool {
136 matches!(right, QOT_RIGHT_BMP | QOT_RIGHT_NO)
137}
138
139fn qot_right_is_none(right: i32) -> bool {
140 right == QOT_RIGHT_NO
141}
142
143fn us_future_right_for_mkt(rights: &QotRightSnapshot, mkt_id: u32) -> i32 {
144 match mkt_id {
145 60..=69 => rights.us_nymex_future_qot_right,
146 70..=79 => rights.us_comex_future_qot_right,
147 80..=89 => rights.us_cbot_future_qot_right,
148 90..=99 => rights.us_cme_future_qot_right,
149 100..=109 => rights.us_cboe_future_qot_right,
150 _ => QOT_RIGHT_UNKNOWN,
151 }
152}
153
154pub fn qot_read_right_reject_reason(
155 market: i32,
156 code: &str,
157 security: SecurityRightClass,
158 rights: &QotRightSnapshot,
159) -> Option<String> {
160 if security.is_us_security(market) {
165 if security.is_index() && rights.us_index_qot_right == QOT_RIGHT_BMP {
166 return Some(format!("US index 行情权限不足,不能获取 {code}。"));
167 }
168 if security.is_us_otc() && rights.us_otc_qot_right == QOT_RIGHT_BMP {
169 return Some(format!("US OTC 行情权限不足,不能获取 {code}。"));
170 }
171 if rights.us_qot_right == QOT_RIGHT_BMP {
172 return Some(format!("US 行情权限不足,不能获取 {code}。"));
173 }
174 } else if market == 11 && security.is_option() {
175 if rights.us_option_qot_right == QOT_RIGHT_BMP {
176 return Some(format!("US option 行情权限不足,不能获取 {code}。"));
177 }
178 } else if security.is_future(market) && (60..=109).contains(&security.mkt_id) {
179 if us_future_right_for_mkt(rights, security.mkt_id) == QOT_RIGHT_BMP {
180 return Some(format!("US future 行情权限不足,不能获取 {code}。"));
181 }
182 } else if security.is_sh_market(market) {
183 if rights.sh_qot_right == QOT_RIGHT_BMP {
184 return Some(format!("SH 行情权限不足,不能获取 {code}。"));
185 }
186 } else if security.is_sz_market(market) {
187 if rights.sz_qot_right == QOT_RIGHT_BMP {
188 return Some(format!("SZ 行情权限不足,不能获取 {code}。"));
189 }
190 } else if security.is_sg_future() {
191 if rights.sg_future_qot_right == QOT_RIGHT_BMP {
192 return Some(format!("SG future 行情权限不足,不能获取 {code}。"));
193 }
194 } else if security.is_jp_future() {
195 if rights.jp_future_qot_right == QOT_RIGHT_BMP {
196 return Some(format!("JP future 行情权限不足,不能获取 {code}。"));
197 }
198 } else if security.is_sg_security_market(market) {
199 if qot_right_is_none(rights.sg_stock_qot_right) {
200 return Some(format!("SG stock 行情权限不足,不能获取 {code}。"));
201 }
202 } else if security.is_my_security_market(market) {
203 if qot_right_is_none(rights.my_stock_qot_right) {
204 return Some(format!("MY stock 行情权限不足,不能获取 {code}。"));
205 }
206 } else if security.is_jp_security_market(market) {
207 if qot_right_is_none(rights.jp_stock_qot_right) {
208 return Some(format!("JP stock 行情权限不足,不能获取 {code}。"));
209 }
210 } else if security.is_crypto(market) && rights.cc_qot_right != QOT_RIGHT_LEVEL1 {
211 return Some(format!(
212 "Crypto 行情权限不足,不能获取 {code};\
213 请检查 Crypto MarketCrypto quote permissions。"
214 ));
215 }
216
217 None
218}
219
220pub fn qot_sub_right_reject_reason(
221 market: i32,
222 code: &str,
223 security: SecurityRightClass,
224 sub_types: &[i32],
225 rights: &QotRightSnapshot,
226) -> Option<String> {
227 let wants_orderbook = sub_types.contains(&2);
228 let wants_ticker = sub_types.contains(&4);
229 let wants_broker = sub_types.contains(&14);
230 let is_option = security.is_option();
231 let is_hk_future = security.is_hk_future();
232 let is_future = security.is_future(market);
233 let is_index = security.is_index();
234 let is_crypto = security.is_crypto(market);
235 let is_sg_stock = security.is_sg_security_market(market);
236 let is_my_stock = security.is_my_security_market(market);
237 let is_jp_stock = security.is_jp_security_market(market);
238 let is_other = !is_option && !is_future && !is_index;
239
240 match market {
241 _ if is_crypto && qot_right_denies_realtime(rights.cc_qot_right) => Some(format!(
242 "Subscribe: crypto 行情权限不足,不能订阅 {code};\
243 请检查 Crypto MarketCrypto quote permissions。"
244 )),
245 21 if qot_right_denies_realtime(rights.sh_qot_right) => {
246 Some("Subscribe: 沪股行情权限不足,不能订阅 SH market。".to_string())
247 }
248 22 if qot_right_denies_realtime(rights.sz_qot_right) => {
249 Some("Subscribe: 深股行情权限不足,不能订阅 SZ market。".to_string())
250 }
251 31 if security.is_sg_future() && qot_right_denies_realtime(rights.sg_future_qot_right) => {
252 Some("Subscribe: SG future 行情权限不足,不能订阅 SG future。".to_string())
253 }
254 31 if is_sg_stock && qot_right_is_none(rights.sg_stock_qot_right) => Some(format!(
255 "Subscribe: SG stock 行情权限不足,不能订阅 {code}。"
256 )),
257 61 if is_my_stock && qot_right_is_none(rights.my_stock_qot_right) => Some(format!(
258 "Subscribe: MY stock 行情权限不足,不能订阅 {code}。"
259 )),
260 41 if security.is_jp_future() && qot_right_denies_realtime(rights.jp_future_qot_right) => {
261 Some("Subscribe: JP future 行情权限不足,不能订阅 JP future。".to_string())
262 }
263 41 if is_jp_stock && qot_right_is_none(rights.jp_stock_qot_right) => Some(format!(
264 "Subscribe: JP stock 行情权限不足,不能订阅 {code}。"
265 )),
266 1 if is_other || is_index => {
267 if qot_right_denies_realtime(rights.hk_qot_right) {
268 Some(format!("Subscribe: HK 行情权限不足,不能订阅 {code}。"))
269 } else if wants_broker && rights.hk_qot_right == QOT_RIGHT_LEVEL1 {
270 Some(format!(
271 "Subscribe: HK Level1 权限不支持 broker queue 订阅 ({code})。"
272 ))
273 } else {
274 None
275 }
276 }
277 1 if is_option => {
278 if qot_right_denies_realtime(rights.hk_option_qot_right) {
279 Some(format!(
280 "Subscribe: HK option 行情权限不足,不能订阅 {code}。"
281 ))
282 } else if wants_orderbook && rights.hk_option_orderbook_depth == Some(0) {
283 Some(format!(
284 "Subscribe: HK option order book depth 为 0,不能订阅摆盘 ({code})。"
285 ))
286 } else if wants_ticker && rights.hk_option_qot_right == QOT_RIGHT_LEVEL1 {
287 Some(format!(
288 "Subscribe: HK option Level1 权限不支持 ticker 订阅 ({code});Ticker 需要 LV2。"
289 ))
290 } else {
291 None
292 }
293 }
294 1 if is_hk_future => {
295 if qot_right_denies_realtime(rights.hk_future_qot_right) {
296 Some(format!(
297 "Subscribe: HK future 行情权限不足,不能订阅 {code}。"
298 ))
299 } else if wants_orderbook && rights.hk_future_orderbook_depth == Some(0) {
300 Some(format!(
301 "Subscribe: HK future order book depth 为 0,不能订阅摆盘 ({code})。"
302 ))
303 } else if wants_ticker && rights.hk_future_qot_right == QOT_RIGHT_LEVEL1 {
304 Some(format!(
305 "Subscribe: HK future Level1 权限不支持 ticker 订阅 ({code});Ticker 需要 LV2。"
306 ))
307 } else {
308 None
309 }
310 }
311 11 if is_option && qot_right_denies_realtime(rights.us_option_qot_right) => Some(format!(
312 "Subscribe: US option 行情权限不足,不能订阅 {code}。"
313 )),
314 11 if is_future => {
315 let right = us_future_right_for_mkt(rights, security.mkt_id);
316 if right == QOT_RIGHT_UNKNOWN || qot_right_denies_realtime(right) {
317 Some(format!(
318 "Subscribe: US future 行情权限不足或未知,不能订阅 {code}。"
319 ))
320 } else {
321 None
322 }
323 }
324 11 if is_index && qot_right_denies_realtime(rights.us_index_qot_right) => Some(format!(
325 "Subscribe: US index 行情权限不足,不能订阅 {code}。"
326 )),
327 11 if security.mkt_id == 13 && qot_right_denies_realtime(rights.us_otc_qot_right) => {
328 Some(format!("Subscribe: US OTC 行情权限不足,不能订阅 {code}。"))
329 }
330 11 if is_other && qot_right_denies_realtime(rights.us_qot_right) => {
331 Some(format!("Subscribe: US 行情权限不足,不能订阅 {code}。"))
332 }
333 _ => None,
334 }
335}
336
337#[cfg(test)]
338mod tests;