1use serde::{Deserialize, Serialize};
7
8pub use crate::diagnostic::DiagnosticDecision as QuoteCapabilityDecision;
9
10pub const SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE: &str = "sys.get_quote_rights_profile";
11pub const SYS_QUERY_GET_QUOTE_CAPABILITY: &str = "sys.get_quote_capability";
12pub const TEST_CMD_GET_QUOTE_RIGHTS_PROFILE: &str = "get_quote_rights_profile";
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct QuoteRightsProfile {
16 pub nick_name: Option<String>,
17 pub user_id: Option<i64>,
18 pub user_attribution: Option<i32>,
19 pub user_attribution_region: Option<String>,
20 pub ret_msg: Option<String>,
21 pub quota: QuoteRightsQuota,
22 pub items: Vec<QuoteRightItem>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct QuoteRightsQuota {
27 pub subscribe_total: Option<i32>,
28 pub history_kl_total: Option<i32>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct QuoteRightItem {
33 pub market: String,
34 pub category: String,
35 pub raw: Option<i32>,
36 pub label: String,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct QuoteCapabilityReport {
41 pub security: QuoteCapabilitySecurity,
42 pub source: QuoteCapabilitySource,
43 pub raw_rights: QuoteCapabilityRawRights,
44 pub orderbook: QuoteCapabilityOrderBook,
45 pub snapshot: QuoteCapabilitySnapshot,
46 pub crypto: QuoteCapabilityCrypto,
47 pub lv2_subs: Vec<QuoteCapabilityLv2Sub>,
48 pub decisions: Vec<QuoteCapabilityDecision>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct QuoteCapabilitySecurity {
53 pub symbol: String,
54 pub market: i32,
55 pub code: String,
56 pub stock_id: u64,
57 pub mkt_id: u32,
58 pub sec_type: i32,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct QuoteCapabilitySource {
63 pub static_cache: bool,
64 pub static_source: String,
65 pub qot_right_freshness: String,
66 pub ret_msg: Option<String>,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct QuoteCapabilityRawRights {
71 pub hk_stock: i32,
72 pub us_stock: i32,
73 pub us_stock_internal: i32,
74 pub us_lv2_arca: bool,
75 pub us_lv2_nyse: bool,
76 pub us_lv2_nasdaq_totalview: bool,
77 pub sg_stock: i32,
78 pub my_stock: i32,
79 pub jp_stock: i32,
80 pub crypto: i32,
81 pub crypto_pt_orderbook: i32,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub struct QuoteCapabilityOrderBook {
86 #[serde(default)]
87 pub max_depth: Option<usize>,
88 #[serde(default)]
89 pub base_max_depth: usize,
90 pub read_uses_requested_count: bool,
91 pub requires_accepted_lv2_push: bool,
92 #[serde(default)]
93 pub depth_policy: String,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(rename_all = "snake_case")]
98pub enum QuoteCapabilityOrderBookDepthPolicy {
99 RequestedCount { base_max_depth: usize },
100 FixedMaxDepth { max_depth: usize },
101 Unavailable { max_depth: usize },
102}
103
104impl QuoteCapabilityOrderBookDepthPolicy {
105 #[must_use]
106 pub fn from_raw(read_uses_requested_count: bool, max_depth: usize) -> Self {
107 if read_uses_requested_count {
108 Self::RequestedCount {
109 base_max_depth: max_depth,
110 }
111 } else if max_depth > 0 {
112 Self::FixedMaxDepth { max_depth }
113 } else {
114 Self::Unavailable { max_depth }
115 }
116 }
117
118 #[must_use]
119 pub fn as_str(self) -> &'static str {
120 match self {
121 Self::RequestedCount { .. } => "requested_count",
122 Self::FixedMaxDepth { .. } => "fixed_max_depth",
123 Self::Unavailable { .. } => "unavailable",
124 }
125 }
126
127 #[must_use]
128 pub fn report_max_depth(self) -> Option<usize> {
129 match self {
130 Self::RequestedCount { .. } => None,
131 Self::FixedMaxDepth { max_depth } | Self::Unavailable { max_depth } => Some(max_depth),
132 }
133 }
134
135 #[must_use]
136 pub fn base_max_depth(self) -> usize {
137 match self {
138 Self::RequestedCount { base_max_depth } => base_max_depth,
139 Self::FixedMaxDepth { max_depth } | Self::Unavailable { max_depth } => max_depth,
140 }
141 }
142
143 #[must_use]
144 pub fn decision_reason(self) -> String {
145 match self {
146 Self::RequestedCount { base_max_depth } => {
147 format!("depth_policy=requested_count base_max_depth={base_max_depth}")
148 }
149 Self::FixedMaxDepth { max_depth } => {
150 format!("depth_policy=fixed_max_depth max_depth={max_depth}")
151 }
152 Self::Unavailable { max_depth } => {
153 format!("depth_policy=unavailable max_depth={max_depth}")
154 }
155 }
156 }
157
158 #[must_use]
159 pub fn is_available(self) -> bool {
160 !matches!(self, Self::Unavailable { .. })
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub struct QuoteCapabilitySnapshot {
166 pub masks_hk_bmp_bid_ask: bool,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub struct QuoteCapabilityCrypto {
171 pub has_pt_orderbook_level1: bool,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct QuoteCapabilityLv2Sub {
176 pub name: String,
177 pub lv2_type: u32,
178 pub level: u32,
179 pub prob2_v2: bool,
180}
181
182#[derive(Debug, Clone, Default, PartialEq, Eq)]
183pub struct QuoteRightsSnapshot {
184 pub nick_name: Option<String>,
185 pub user_id: Option<i64>,
186 pub user_attribution: Option<i32>,
187 pub ret_msg: Option<String>,
188 pub sub_quota: Option<i32>,
189 pub history_kl_quota: Option<i32>,
190 pub hk_qot_right: Option<i32>,
191 pub hk_option_qot_right: Option<i32>,
192 pub hk_future_qot_right: Option<i32>,
193 pub us_qot_right: Option<i32>,
194 pub us_index_qot_right: Option<i32>,
195 pub us_option_qot_right: Option<i32>,
196 pub us_cme_future_qot_right: Option<i32>,
197 pub us_cbot_future_qot_right: Option<i32>,
198 pub us_nymex_future_qot_right: Option<i32>,
199 pub us_comex_future_qot_right: Option<i32>,
200 pub us_cboe_future_qot_right: Option<i32>,
201 pub us_otc_qot_right: Option<i32>,
202 pub sh_qot_right: Option<i32>,
203 pub sz_qot_right: Option<i32>,
204 pub sg_future_qot_right: Option<i32>,
205 pub sg_stock_qot_right: Option<i32>,
206 pub my_stock_qot_right: Option<i32>,
207 pub jp_future_qot_right: Option<i32>,
208 pub jp_stock_qot_right: Option<i32>,
209 pub cc_qot_right: Option<i32>,
210 pub event_contract_qot_right: Option<i32>,
211}
212
213pub fn profile_from_snapshot(s: QuoteRightsSnapshot) -> QuoteRightsProfile {
214 let mut items = Vec::with_capacity(21);
215 push_item(
216 &mut items,
217 "香港市场",
218 "股票",
219 s.hk_qot_right,
220 label_standard,
221 );
222 push_item(
223 &mut items,
224 "香港市场",
225 "期权",
226 s.hk_option_qot_right,
227 label_standard,
228 );
229 push_item(
230 &mut items,
231 "香港市场",
232 "期货",
233 s.hk_future_qot_right,
234 label_standard,
235 );
236 push_item(
237 &mut items,
238 "美国市场",
239 "股票",
240 s.us_qot_right,
241 label_us_stock,
242 );
243 push_item(
244 &mut items,
245 "美国市场",
246 "指数",
247 s.us_index_qot_right,
248 label_standard,
249 );
250 push_item(
251 &mut items,
252 "美国市场",
253 "期权",
254 s.us_option_qot_right,
255 label_standard,
256 );
257 push_item(
258 &mut items,
259 "美国市场",
260 "CME",
261 s.us_cme_future_qot_right,
262 label_standard,
263 );
264 push_item(
265 &mut items,
266 "美国市场",
267 "CBOT",
268 s.us_cbot_future_qot_right,
269 label_standard,
270 );
271 push_item(
272 &mut items,
273 "美国市场",
274 "NYMEX",
275 s.us_nymex_future_qot_right,
276 label_standard,
277 );
278 push_item(
279 &mut items,
280 "美国市场",
281 "COMEX",
282 s.us_comex_future_qot_right,
283 label_standard,
284 );
285 push_item(
286 &mut items,
287 "美国市场",
288 "CBOE",
289 s.us_cboe_future_qot_right,
290 label_standard,
291 );
292 push_item(
293 &mut items,
294 "美国市场",
295 "OTC",
296 s.us_otc_qot_right,
297 label_standard,
298 );
299 push_item(
300 &mut items,
301 "A股市场",
302 "上证",
303 s.sh_qot_right,
304 label_standard,
305 );
306 push_item(
307 &mut items,
308 "A股市场",
309 "深证",
310 s.sz_qot_right,
311 label_standard,
312 );
313 push_item(
314 &mut items,
315 "新加坡市场",
316 "股票",
317 s.sg_stock_qot_right,
318 label_standard,
319 );
320 push_item(
321 &mut items,
322 "新加坡市场",
323 "期货",
324 s.sg_future_qot_right,
325 label_standard,
326 );
327 push_item(
328 &mut items,
329 "马来西亚市场",
330 "股票",
331 s.my_stock_qot_right,
332 label_standard,
333 );
334 push_item(
335 &mut items,
336 "日本市场",
337 "股票",
338 s.jp_stock_qot_right,
339 label_standard,
340 );
341 push_item(
342 &mut items,
343 "日本市场",
344 "期货",
345 s.jp_future_qot_right,
346 label_standard,
347 );
348 push_item(
349 &mut items,
350 "加密货币市场",
351 "加密货币",
352 s.cc_qot_right,
353 label_standard,
354 );
355 push_item(
356 &mut items,
357 "预测市场",
358 "事件合约",
359 s.event_contract_qot_right,
360 label_standard,
361 );
362
363 QuoteRightsProfile {
364 nick_name: s.nick_name,
365 user_id: s.user_id,
366 user_attribution: s.user_attribution,
367 user_attribution_region: user_attribution_region(s.user_attribution)
368 .map(ToString::to_string),
369 ret_msg: s.ret_msg,
370 quota: QuoteRightsQuota {
371 subscribe_total: s.sub_quota,
372 history_kl_total: s.history_kl_quota,
373 },
374 items,
375 }
376}
377
378fn push_item(
379 items: &mut Vec<QuoteRightItem>,
380 market: &str,
381 category: &str,
382 raw: Option<i32>,
383 label_fn: fn(Option<i32>) -> String,
384) {
385 items.push(QuoteRightItem {
386 market: market.to_string(),
387 category: category.to_string(),
388 raw,
389 label: label_fn(raw),
390 });
391}
392
393fn label_standard(raw: Option<i32>) -> String {
394 match raw {
395 None | Some(0) => "未知".to_string(),
396 Some(1) => "BMP".to_string(),
397 Some(2) => "LV1".to_string(),
398 Some(3) => "LV2".to_string(),
399 Some(4) => "SF".to_string(),
400 Some(5) => "无权限".to_string(),
401 Some(6) => "LV3".to_string(),
402 Some(v) => format!("未知({v})"),
403 }
404}
405
406fn label_us_stock(raw: Option<i32>) -> String {
407 match raw {
408 Some(3) => "LV3".to_string(),
409 other => label_standard(other),
410 }
411}
412
413fn user_attribution_region(raw: Option<i32>) -> Option<&'static str> {
414 match raw {
415 Some(1) => Some("CN"),
416 Some(2) => Some("US"),
417 Some(3) => Some("SG"),
418 Some(4) => Some("AU"),
419 Some(5) => Some("JP"),
420 Some(6) => Some("HK"),
421 _ => None,
422 }
423}