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_index(self) -> bool {
35 self.sec_type == SECURITY_TYPE_INDEX
36 }
37
38 fn is_crypto(self, public_market: i32) -> bool {
39 self.sec_type == SECURITY_TYPE_CRYPTO
40 || public_market == QOT_MARKET_CC_SECURITY
41 || (360..=459).contains(&self.mkt_id)
42 }
43}
44
45#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
46pub struct QotRightSnapshot {
47 pub hk_qot_right: i32,
48 pub us_qot_right: i32,
49 pub sh_qot_right: i32,
50 pub sz_qot_right: i32,
51 pub hk_option_qot_right: i32,
52 pub hk_future_qot_right: i32,
53 pub hk_option_orderbook_depth: Option<u32>,
54 pub hk_future_orderbook_depth: Option<u32>,
55 pub us_option_qot_right: i32,
56 pub us_index_qot_right: i32,
57 pub us_otc_qot_right: i32,
58 pub us_cme_future_qot_right: i32,
59 pub us_cbot_future_qot_right: i32,
60 pub us_nymex_future_qot_right: i32,
61 pub us_comex_future_qot_right: i32,
62 pub us_cboe_future_qot_right: i32,
63 pub sg_future_qot_right: i32,
64 pub jp_future_qot_right: i32,
65 pub cc_qot_right: i32,
66}
67
68fn qot_right_denies_realtime(right: i32) -> bool {
69 matches!(right, QOT_RIGHT_BMP | QOT_RIGHT_NO)
70}
71
72fn us_future_right_for_mkt(rights: &QotRightSnapshot, mkt_id: u32) -> i32 {
73 match mkt_id {
74 60..=69 => rights.us_nymex_future_qot_right,
75 70..=79 => rights.us_comex_future_qot_right,
76 80..=89 => rights.us_cbot_future_qot_right,
77 90..=99 => rights.us_cme_future_qot_right,
78 100..=109 => rights.us_cboe_future_qot_right,
79 _ => QOT_RIGHT_UNKNOWN,
80 }
81}
82
83pub fn qot_sub_right_reject_reason(
84 market: i32,
85 code: &str,
86 security: SecurityRightClass,
87 sub_types: &[i32],
88 rights: &QotRightSnapshot,
89) -> Option<String> {
90 let wants_orderbook = sub_types.contains(&2);
91 let wants_ticker = sub_types.contains(&4);
92 let wants_broker = sub_types.contains(&14);
93 let is_option = security.is_option();
94 let is_future = security.is_future(market);
95 let is_index = security.is_index();
96 let is_crypto = security.is_crypto(market);
97 let is_other = !is_option && !is_future && !is_index;
98
99 match market {
100 _ if is_crypto && qot_right_denies_realtime(rights.cc_qot_right) => {
101 Some(format!("Subscribe: crypto 行情权限不足,不能订阅 {code}。"))
102 }
103 21 if qot_right_denies_realtime(rights.sh_qot_right) => {
104 Some("Subscribe: 沪股行情权限不足,不能订阅 SH market。".to_string())
105 }
106 22 if qot_right_denies_realtime(rights.sz_qot_right) => {
107 Some("Subscribe: 深股行情权限不足,不能订阅 SZ market。".to_string())
108 }
109 31 if qot_right_denies_realtime(rights.sg_future_qot_right) => {
110 Some("Subscribe: 新加坡行情权限不足,不能订阅 SG market。".to_string())
111 }
112 41 if qot_right_denies_realtime(rights.jp_future_qot_right) => {
113 Some("Subscribe: 日本行情权限不足,不能订阅 JP market。".to_string())
114 }
115 1 if is_other || is_index => {
116 if qot_right_denies_realtime(rights.hk_qot_right) {
117 Some(format!("Subscribe: HK 行情权限不足,不能订阅 {code}。"))
118 } else if wants_broker && rights.hk_qot_right == QOT_RIGHT_LEVEL1 {
119 Some(format!(
120 "Subscribe: HK Level1 权限不支持 broker queue 订阅 ({code})。"
121 ))
122 } else {
123 None
124 }
125 }
126 1 if is_option => {
127 if qot_right_denies_realtime(rights.hk_option_qot_right) {
128 Some(format!(
129 "Subscribe: HK option 行情权限不足,不能订阅 {code}。"
130 ))
131 } else if wants_orderbook && rights.hk_option_orderbook_depth == Some(0) {
132 Some(format!(
133 "Subscribe: HK option order book depth 为 0,不能订阅摆盘 ({code})。"
134 ))
135 } else if wants_ticker && rights.hk_option_qot_right == QOT_RIGHT_LEVEL1 {
136 Some(format!(
137 "Subscribe: HK option Level1 权限不支持 ticker 订阅 ({code});Ticker 需要 LV2。"
138 ))
139 } else {
140 None
141 }
142 }
143 1 if is_future => {
144 if qot_right_denies_realtime(rights.hk_future_qot_right) {
145 Some(format!(
146 "Subscribe: HK future 行情权限不足,不能订阅 {code}。"
147 ))
148 } else if wants_orderbook && rights.hk_future_orderbook_depth == Some(0) {
149 Some(format!(
150 "Subscribe: HK future order book depth 为 0,不能订阅摆盘 ({code})。"
151 ))
152 } else if wants_ticker && rights.hk_future_qot_right == QOT_RIGHT_LEVEL1 {
153 Some(format!(
154 "Subscribe: HK future Level1 权限不支持 ticker 订阅 ({code});Ticker 需要 LV2。"
155 ))
156 } else {
157 None
158 }
159 }
160 11 if is_option && qot_right_denies_realtime(rights.us_option_qot_right) => Some(format!(
161 "Subscribe: US option 行情权限不足,不能订阅 {code}。"
162 )),
163 11 if is_future => {
164 let right = us_future_right_for_mkt(rights, security.mkt_id);
165 if right == QOT_RIGHT_UNKNOWN || qot_right_denies_realtime(right) {
166 Some(format!(
167 "Subscribe: US future 行情权限不足或未知,不能订阅 {code}。"
168 ))
169 } else {
170 None
171 }
172 }
173 11 if is_index && qot_right_denies_realtime(rights.us_index_qot_right) => Some(format!(
174 "Subscribe: US index 行情权限不足,不能订阅 {code}。"
175 )),
176 11 if security.mkt_id == 13 && qot_right_denies_realtime(rights.us_otc_qot_right) => {
177 Some(format!("Subscribe: US OTC 行情权限不足,不能订阅 {code}。"))
178 }
179 11 if is_other && qot_right_denies_realtime(rights.us_qot_right) => {
180 Some(format!("Subscribe: US 行情权限不足,不能订阅 {code}。"))
181 }
182 _ => None,
183 }
184}
185
186#[cfg(test)]
187mod tests;