1use parking_lot::{Mutex, RwLock};
15
16use crate::static_data::CachedSecurityInfo;
17
18const QOT_RIGHT_UNKNOWN: i32 = 0;
19const QOT_RIGHT_BMP: i32 = 1;
20const QOT_RIGHT_LEVEL1: i32 = 2;
21const QOT_RIGHT_LEVEL2: i32 = 3;
22pub const QOT_RIGHT_SF: i32 = 4;
23const QOT_RIGHT_NO: i32 = 5;
24const SECURITY_TYPE_INDEX: i32 = 6;
25const SECURITY_TYPE_DRVT: i32 = 8;
26const SECURITY_TYPE_FUTURE: i32 = 10;
27const SECURITY_TYPE_CRYPTO: i32 = 12;
28const QOT_MARKET_CC_SECURITY: i32 = 91;
29pub const US_LV2_ORDER_ARCA: u32 = 1;
30pub const US_LV2_ORDER_NASDAQ_TV: u32 = 4;
31pub const US_LV2_ORDER_OVERNIGHT: u32 = 128;
32pub const LV2_ORDER_US_FUTURE: u32 = 1;
33
34fn hk_clt_to_api(clt: u32) -> i32 {
35 match clt {
36 1 => 3,
37 2 => 1,
38 3 => 2,
39 4 => 4,
40 _ => 0,
41 }
42}
43
44fn us_clt_to_api(clt: u32) -> i32 {
45 match clt {
46 1 => 2,
47 2 => 5,
48 _ => 5,
49 }
50}
51
52fn cn_clt_to_api(clt: u32) -> i32 {
53 match clt {
54 1 => 2,
55 2 => 3,
56 3 => 5,
57 _ => 5,
58 }
59}
60
61fn other_clt_to_api(clt: u32) -> i32 {
62 match clt {
63 1 => 3,
64 2 => 2,
65 _ => 5,
66 }
67}
68
69fn us_future_clt_to_api(clt: u32) -> i32 {
70 match clt {
71 0 => 5,
72 1 => 3,
73 2 => 1,
74 3 => 3,
75 4 => 2,
76 _ => 5,
77 }
78}
79
80fn us_option_clt_to_api(clt: u32) -> i32 {
81 match clt {
82 0 => 1,
83 1 => 2,
84 _ => 1,
85 }
86}
87
88fn us_otc_comm_auth_to_api(deal_data_auth: Option<u32>, order_book_auth: Option<u32>) -> i32 {
89 const COMM_AUTH_RT: u32 = 2;
93 const QOT_RIGHT_LEVEL1: i32 = 2;
94 const QOT_RIGHT_NO: i32 = 5;
95 let deal = deal_data_auth.unwrap_or(COMM_AUTH_RT);
96 let order_book = order_book_auth.unwrap_or(COMM_AUTH_RT);
97 if deal == COMM_AUTH_RT && order_book == COMM_AUTH_RT {
98 QOT_RIGHT_LEVEL1
99 } else {
100 QOT_RIGHT_NO
101 }
102}
103
104fn crypto_auth_to_api(auth: u32) -> i32 {
105 if auth == 1 {
106 QOT_RIGHT_LEVEL1
107 } else {
108 QOT_RIGHT_NO
109 }
110}
111
112fn us_index_flags_to_api(
113 dow_jones: Option<u32>,
114 nasdaq: Option<u32>,
115 standard_poor: Option<u32>,
116) -> Option<i32> {
117 const QOT_RIGHT_LEVEL1: i32 = 2;
121 const QOT_RIGHT_NO: i32 = 5;
122 if dow_jones.is_none() && nasdaq.is_none() && standard_poor.is_none() {
123 return None;
124 }
125 let has_any = dow_jones == Some(1) || nasdaq == Some(1) || standard_poor == Some(1);
126 Some(if has_any {
127 QOT_RIGHT_LEVEL1
128 } else {
129 QOT_RIGHT_NO
130 })
131}
132
133fn has_any_us_lv2_flag(flags: UsLv2Flags) -> bool {
134 let (arca, nyse, nasdaq_tv, edg, bzx) = flags;
135 arca == Some(1) || nyse == Some(1) || nasdaq_tv == Some(1) || edg == Some(1) || bzx == Some(1)
136}
137
138pub type UsLv2Flags = (
139 Option<u32>,
140 Option<u32>,
141 Option<u32>,
142 Option<u32>,
143 Option<u32>,
144);
145pub type UsIndexFlags = (Option<u32>, Option<u32>, Option<u32>);
146pub type UsOtcAuths = (Option<u32>, Option<u32>);
147pub type UsFutureDetailAuths = (Option<u32>, Option<u32>, Option<u32>, Option<u32>);
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub struct Lv2OrderSubDescriptor {
151 pub lv2_type: u32,
152 pub level: u32,
153 pub prob2_v2: bool,
154}
155
156#[derive(Debug, Clone, Copy, Default)]
157pub struct QotRightBackendExtras {
158 pub us_lv2_flags: Option<UsLv2Flags>,
159 pub us_index_flags: Option<UsIndexFlags>,
160 pub us_otc_auths: Option<UsOtcAuths>,
161}
162
163impl QotRightBackendExtras {
164 pub const fn none() -> Self {
165 Self {
166 us_lv2_flags: None,
167 us_index_flags: None,
168 us_otc_auths: None,
169 }
170 }
171}
172
173#[derive(Debug, Clone, Copy, Default)]
174pub struct QotRightBackendUpdate {
175 pub hk_got: Option<u32>,
176 pub us_got: Option<u32>,
177 pub cn_got: Option<u32>,
178 pub sh_auth: Option<u32>,
179 pub sz_auth: Option<u32>,
180 pub hk_option: Option<u32>,
181 pub hk_future: Option<u32>,
182 pub us_option: Option<u32>,
183 pub us_future_cme_cboe: Option<u32>,
184 pub us_future_detail: Option<UsFutureDetailAuths>,
185 pub sg_future: Option<u32>,
186 pub jp_future: Option<u32>,
187 pub digital_currency_auth: Option<u32>,
188 pub digital_pt_orderbook_auth: Option<u32>,
189 pub sub_limit: Option<u32>,
190 pub kl_limit: Option<u32>,
191 pub extras: QotRightBackendExtras,
192}
193
194#[derive(Debug, Clone)]
195pub struct QotRightData {
196 pub hk_qot_right: i32,
197 pub us_qot_right: i32,
198 pub api_us_qot_right: i32,
200 pub sh_qot_right: i32,
201 pub sz_qot_right: i32,
202 pub hk_option_qot_right: i32,
203 pub hk_future_qot_right: i32,
204 pub has_us_option_qot_right: bool,
205 pub us_option_qot_right: i32,
206 pub us_index_qot_right: i32,
207 pub us_otc_qot_right: i32,
208 pub us_cme_future_qot_right: i32,
209 pub us_cbot_future_qot_right: i32,
210 pub us_nymex_future_qot_right: i32,
211 pub us_comex_future_qot_right: i32,
212 pub us_cboe_future_qot_right: i32,
213 pub sg_future_qot_right: i32,
214 pub jp_future_qot_right: i32,
215 pub cc_qot_right: i32,
216 pub cc_pt_orderbook_qot_right: i32,
217 pub us_lv2_arca_qot_right: bool,
218 pub us_lv2_nyse_qot_right: bool,
219 pub us_lv2_nasdaq_totalview_qot_right: bool,
220 pub sub_quota: i32,
221 pub history_kl_quota: i32,
222 pub hk_option_orderbook_depth: Option<u32>,
224 pub hk_future_orderbook_depth: Option<u32>,
225}
226
227impl Default for QotRightData {
228 fn default() -> Self {
229 const UNKNOWN: i32 = 0;
230 Self {
231 hk_qot_right: UNKNOWN,
232 us_qot_right: UNKNOWN,
233 api_us_qot_right: UNKNOWN,
234 sh_qot_right: UNKNOWN,
235 sz_qot_right: UNKNOWN,
236 hk_option_qot_right: UNKNOWN,
237 hk_future_qot_right: UNKNOWN,
238 has_us_option_qot_right: true,
239 us_option_qot_right: UNKNOWN,
240 us_index_qot_right: UNKNOWN,
241 us_otc_qot_right: UNKNOWN,
242 us_cme_future_qot_right: UNKNOWN,
243 us_cbot_future_qot_right: UNKNOWN,
244 us_nymex_future_qot_right: UNKNOWN,
245 us_comex_future_qot_right: UNKNOWN,
246 us_cboe_future_qot_right: UNKNOWN,
247 sg_future_qot_right: UNKNOWN,
248 jp_future_qot_right: UNKNOWN,
249 cc_qot_right: UNKNOWN,
250 cc_pt_orderbook_qot_right: UNKNOWN,
251 us_lv2_arca_qot_right: false,
252 us_lv2_nyse_qot_right: false,
253 us_lv2_nasdaq_totalview_qot_right: false,
254 sub_quota: 4000,
255 history_kl_quota: 100,
256 hk_option_orderbook_depth: None,
257 hk_future_orderbook_depth: None,
258 }
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
265pub enum QotRightFreshness {
266 Unknown,
267 Pending,
268 Fresh,
269 Stale,
270 Failed { error: String, since_ms: i64 },
271}
272
273impl QotRightFreshness {
274 pub fn is_fresh(&self) -> bool {
275 matches!(self, QotRightFreshness::Fresh)
276 }
277 pub fn is_unconfirmed(&self) -> bool {
278 !self.is_fresh()
279 }
280}
281
282#[derive(Debug, Clone, Default)]
283pub struct PushedQuoteChangeNotify {
284 pub change_items: Vec<(i32, i32, i32)>,
285}
286
287#[derive(Debug, Clone)]
288pub struct QotRightRefreshReport {
289 pub changed: bool,
290 pub fresh_at_ms: i64,
291 pub decoded_fields: Vec<&'static str>,
292 pub freshness_after: QotRightFreshness,
293 pub login_epoch: u64,
294}
295
296#[derive(Debug, Clone)]
297pub struct QotRightStateMeta {
298 pub freshness: QotRightFreshness,
299 pub user_id: Option<u64>,
300 pub login_epoch: u64,
301 pub backend_generation: u64,
302 pub last_refresh_at_ms: i64,
303 pub last_pushed_quote_change_notify: Option<PushedQuoteChangeNotify>,
304}
305
306impl Default for QotRightStateMeta {
307 fn default() -> Self {
308 Self {
309 freshness: QotRightFreshness::Unknown,
310 user_id: None,
311 login_epoch: 0,
312 backend_generation: 0,
313 last_refresh_at_ms: 0,
314 last_pushed_quote_change_notify: None,
315 }
316 }
317}
318
319fn now_ms() -> i64 {
320 use std::time::{SystemTime, UNIX_EPOCH};
321 SystemTime::now()
322 .duration_since(UNIX_EPOCH)
323 .map(|d| d.as_millis() as i64)
324 .unwrap_or(0)
325}
326
327fn is_hk_future_market(info: &CachedSecurityInfo) -> bool {
328 info.sec_type == SECURITY_TYPE_FUTURE && matches!(info.mkt_id, 5 | 6 | 110..=119 | 1400..=1499)
329}
330
331fn is_us_future_market(info: &CachedSecurityInfo) -> bool {
332 info.sec_type == SECURITY_TYPE_FUTURE && (60..=109).contains(&info.mkt_id)
333}
334
335fn is_sg_future_market(info: &CachedSecurityInfo) -> bool {
336 info.sec_type == SECURITY_TYPE_FUTURE && (160..=179).contains(&info.mkt_id)
337}
338
339fn is_jp_future_market(info: &CachedSecurityInfo) -> bool {
340 info.sec_type == SECURITY_TYPE_FUTURE && (185..=194).contains(&info.mkt_id)
341}
342
343fn is_hk_option_market(info: &CachedSecurityInfo) -> bool {
344 info.sec_type == SECURITY_TYPE_DRVT && matches!(info.mkt_id, 7 | 8 | 570..=579)
345}
346
347fn is_us_option_market(info: &CachedSecurityInfo) -> bool {
348 info.sec_type == SECURITY_TYPE_DRVT && (41..=49).contains(&info.mkt_id)
349}
350
351fn is_hk_security_market(info: &CachedSecurityInfo) -> bool {
352 matches!(info.mkt_id, 1..=4 | 1000..=1049)
353 || (info.market == 1
354 && !is_hk_future_market(info)
355 && !is_hk_option_market(info)
356 && info.sec_type != SECURITY_TYPE_FUTURE
357 && info.sec_type != SECURITY_TYPE_DRVT)
358}
359
360pub fn snapshot_masks_hk_bmp_bid_ask(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
365 let is_hk = matches!(info.market, 1 | 2);
366 if !is_hk {
367 return false;
368 }
369
370 if is_hk_option_market(info) {
371 return qr.hk_option_qot_right == QOT_RIGHT_BMP;
372 }
373 if is_hk_future_market(info) {
374 return qr.hk_future_qot_right == QOT_RIGHT_BMP;
375 }
376 is_hk_security_market(info) && qr.hk_qot_right == QOT_RIGHT_BMP
377}
378
379fn is_us_security_market(info: &CachedSecurityInfo) -> bool {
380 matches!(info.mkt_id, 10..=29 | 1200..=1249)
381 || (info.market == 11
382 && !is_us_future_market(info)
383 && !is_us_option_market(info)
384 && info.sec_type != SECURITY_TYPE_FUTURE
385 && info.sec_type != SECURITY_TYPE_DRVT)
386}
387
388pub fn is_crypto_market(info: &CachedSecurityInfo) -> bool {
394 info.sec_type == SECURITY_TYPE_CRYPTO
395 || info.market == QOT_MARKET_CC_SECURITY
396 || (360..=459).contains(&info.mkt_id)
397}
398
399pub fn basic_qot_uses_us_pre_after_detail(info: &CachedSecurityInfo) -> bool {
407 matches!(info.mkt_id, 10..=29 | 41..=45 | 60..=109 | 1200..=1249) || info.market == 11
408}
409
410fn is_sh_market(info: &CachedSecurityInfo) -> bool {
411 matches!(info.mkt_id, 30 | 32 | 33 | 34 | 36..=40) || info.market == 21
412}
413
414fn is_sz_market(info: &CachedSecurityInfo) -> bool {
415 matches!(info.mkt_id, 31 | 35) || info.market == 22
416}
417
418fn us_future_right_for_market(qr: &QotRightData, mkt_id: u32) -> i32 {
419 match mkt_id {
420 60..=69 => qr.us_nymex_future_qot_right,
421 70..=79 => qr.us_comex_future_qot_right,
422 80..=89 => qr.us_cbot_future_qot_right,
423 90..=99 => qr.us_cme_future_qot_right,
424 100..=109 => qr.us_cboe_future_qot_right,
425 _ => QOT_RIGHT_UNKNOWN,
426 }
427}
428
429pub fn merged_lv2_order_subs_for_security(
430 info: &CachedSecurityInfo,
431 qr: &QotRightData,
432 _extended_time: bool,
433) -> Vec<Lv2OrderSubDescriptor> {
434 if is_us_future_market(info) {
435 if us_future_right_for_market(qr, info.mkt_id) == QOT_RIGHT_LEVEL2 {
436 return vec![Lv2OrderSubDescriptor {
437 lv2_type: LV2_ORDER_US_FUTURE,
438 level: 60,
439 prob2_v2: true,
440 }];
441 }
442 return vec![];
443 }
444
445 if !is_us_security_market(info) || info.sec_type == SECURITY_TYPE_INDEX {
446 return vec![];
447 }
448 if !matches!(qr.us_qot_right, QOT_RIGHT_LEVEL1 | QOT_RIGHT_LEVEL2) {
449 return vec![];
450 }
451
452 let mut subs = Vec::new();
453 if qr.us_lv2_arca_qot_right || qr.us_lv2_nyse_qot_right || qr.us_lv2_nasdaq_totalview_qot_right
457 {
458 subs.push(Lv2OrderSubDescriptor {
459 lv2_type: US_LV2_ORDER_OVERNIGHT,
460 level: 60,
461 prob2_v2: true,
462 });
463 }
464 if qr.us_lv2_nasdaq_totalview_qot_right {
465 subs.push(Lv2OrderSubDescriptor {
466 lv2_type: US_LV2_ORDER_NASDAQ_TV,
467 level: 60,
468 prob2_v2: false,
469 });
470 }
471 if qr.us_lv2_arca_qot_right {
472 subs.push(Lv2OrderSubDescriptor {
473 lv2_type: US_LV2_ORDER_ARCA,
474 level: 60,
475 prob2_v2: false,
476 });
477 }
478 subs
479}
480
481pub fn has_merged_lv2_order_subs_for_security(
482 info: &CachedSecurityInfo,
483 qr: &QotRightData,
484) -> bool {
485 !merged_lv2_order_subs_for_security(info, qr, true).is_empty()
486}
487
488pub fn order_book_max_depth_for_security(info: &CachedSecurityInfo, qr: &QotRightData) -> usize {
502 if is_crypto_market(info) {
503 return if qr.cc_qot_right == QOT_RIGHT_LEVEL1 {
504 40
505 } else {
506 0
507 };
508 }
509
510 if is_us_option_market(info) {
511 return if qr.us_option_qot_right == QOT_RIGHT_BMP {
512 0
513 } else {
514 1
515 };
516 }
517
518 if is_us_future_market(info) {
519 return match us_future_right_for_market(qr, info.mkt_id) {
520 QOT_RIGHT_LEVEL1 => 1,
521 QOT_RIGHT_LEVEL2 => 40,
522 _ => 0,
523 };
524 }
525
526 if is_hk_option_market(info) {
527 return if qr.hk_option_qot_right == QOT_RIGHT_BMP {
528 0
529 } else {
530 qr.hk_option_orderbook_depth.unwrap_or(0) as usize
531 };
532 }
533
534 if is_hk_future_market(info) {
535 return if qr.hk_future_qot_right == QOT_RIGHT_BMP {
536 0
537 } else {
538 qr.hk_future_orderbook_depth.unwrap_or(0) as usize
539 };
540 }
541
542 if is_hk_security_market(info) {
543 return match qr.hk_qot_right {
544 QOT_RIGHT_BMP | QOT_RIGHT_NO => 0,
545 QOT_RIGHT_LEVEL1 => 1,
546 QOT_RIGHT_LEVEL2 | QOT_RIGHT_SF => 10,
547 _ => 10,
548 };
549 }
550
551 if is_us_security_market(info) {
552 return if info.sec_type == SECURITY_TYPE_INDEX || qr.us_qot_right == QOT_RIGHT_BMP {
553 0
554 } else {
555 1
556 };
557 }
558
559 if is_sh_market(info) {
560 return if qr.sh_qot_right == QOT_RIGHT_BMP {
561 0
562 } else {
563 10
564 };
565 }
566
567 if is_sz_market(info) {
568 return if qr.sz_qot_right == QOT_RIGHT_BMP {
569 0
570 } else {
571 10
572 };
573 }
574
575 if is_sg_future_market(info) {
576 return match qr.sg_future_qot_right {
577 QOT_RIGHT_LEVEL1 => 1,
578 QOT_RIGHT_LEVEL2 => 40,
579 _ => 0,
580 };
581 }
582
583 if is_jp_future_market(info) {
584 return match qr.jp_future_qot_right {
585 QOT_RIGHT_LEVEL1 => 1,
586 QOT_RIGHT_LEVEL2 => 40,
587 _ => 0,
588 };
589 }
590
591 10
592}
593
594pub fn order_book_requires_backend_full_depth(
600 info: &CachedSecurityInfo,
601 qr: &QotRightData,
602) -> bool {
603 qr.hk_qot_right == QOT_RIGHT_SF && is_hk_security_market(info)
604}
605
606pub fn order_book_uses_backend_side_count(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
607 order_book_requires_backend_full_depth(info, qr)
608}
609
610pub fn order_book_read_uses_requested_count(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
617 order_book_uses_backend_side_count(info, qr)
618 || (qr.us_lv2_nasdaq_totalview_qot_right && is_us_security_market(info))
619}
620
621pub fn order_book_requires_accepted_lv2_push(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
629 (qr.us_lv2_nasdaq_totalview_qot_right && is_us_security_market(info)) || is_crypto_market(info)
630}
631
632pub struct QotRightCache {
633 data: RwLock<QotRightData>,
634 meta: Mutex<QotRightStateMeta>,
635}
636
637impl Default for QotRightCache {
638 fn default() -> Self {
639 Self::new()
640 }
641}
642
643impl QotRightCache {
644 pub fn new() -> Self {
645 Self {
646 data: RwLock::new(QotRightData::default()),
647 meta: Mutex::new(QotRightStateMeta::default()),
648 }
649 }
650
651 pub fn get(&self) -> QotRightData {
652 self.data.read().clone()
653 }
654
655 pub fn freshness(&self) -> QotRightFreshness {
656 self.meta.lock().freshness.clone()
657 }
658
659 pub fn is_fresh(&self) -> bool {
660 self.meta.lock().freshness.is_fresh()
661 }
662
663 pub fn meta_snapshot(&self) -> QotRightStateMeta {
664 self.meta.lock().clone()
665 }
666
667 pub fn set_orderbook_depths(&self, hk_option_depth: Option<u32>, hk_future_depth: Option<u32>) {
668 let mut d = self.data.write();
669 if let Some(v) = hk_option_depth {
670 d.hk_option_orderbook_depth = Some(v);
671 }
672 if let Some(v) = hk_future_depth {
673 d.hk_future_orderbook_depth = Some(v);
674 }
675 }
676
677 pub fn mark_stale(&self, reason: &str) {
678 let mut m = self.meta.lock();
679 let old = m.freshness.clone();
680 m.freshness = QotRightFreshness::Stale;
681 tracing::info!(
682 old_freshness = ?old,
683 reason,
684 login_epoch = m.login_epoch,
685 backend_generation = m.backend_generation,
686 "QotRightCache: marked stale"
687 );
688 }
689
690 pub fn advance_login_epoch(&self, new_epoch: u64, user_id: Option<u64>) {
691 let mut m = self.meta.lock();
692 let old_epoch = m.login_epoch;
693 m.login_epoch = new_epoch;
694 m.user_id = user_id;
695 if matches!(m.freshness, QotRightFreshness::Fresh) {
696 m.freshness = QotRightFreshness::Stale;
697 }
698 tracing::info!(
699 old_epoch,
700 new_epoch,
701 user_id = ?user_id,
702 "QotRightCache: login epoch advanced"
703 );
704 }
705
706 pub fn advance_backend_generation(&self, new_generation: u64) {
707 let mut m = self.meta.lock();
708 let old_gen = m.backend_generation;
709 m.backend_generation = new_generation;
710 if matches!(m.freshness, QotRightFreshness::Fresh) {
711 m.freshness = QotRightFreshness::Stale;
712 }
713 tracing::info!(
714 old_generation = old_gen,
715 new_generation,
716 "QotRightCache: backend generation advanced"
717 );
718 }
719
720 pub fn mark_pending(&self) {
721 self.meta.lock().freshness = QotRightFreshness::Pending;
722 }
723
724 pub fn mark_fresh(&self) {
725 let mut m = self.meta.lock();
726 m.freshness = QotRightFreshness::Fresh;
727 m.last_refresh_at_ms = now_ms();
728 }
729
730 pub fn mark_failed(&self, error: impl Into<String>) {
731 self.meta.lock().freshness = QotRightFreshness::Failed {
732 error: error.into(),
733 since_ms: now_ms(),
734 };
735 }
736
737 pub fn last_refresh_at_ms(&self) -> i64 {
738 self.meta.lock().last_refresh_at_ms
739 }
740
741 pub fn set_pushed_quote_change_notify(&self, notify: PushedQuoteChangeNotify) {
742 self.meta.lock().last_pushed_quote_change_notify = Some(notify);
743 }
744
745 pub fn pushed_quote_change_notify(&self) -> Option<PushedQuoteChangeNotify> {
746 self.meta.lock().last_pushed_quote_change_notify.clone()
747 }
748
749 pub fn apply_6651_changes(&self, items: &[(i32, i32, i32)]) -> Vec<i32> {
750 let mut d = self.data.write();
751 let mut changed_types = Vec::new();
752
753 for &(quote_type, before, after) in items {
754 if before == 0 || before == after {
757 continue;
758 }
759 let after_u = after as u32;
760 if apply_qot_right_after_change(&mut d, quote_type, after_u) {
761 changed_types.push(quote_type);
762 }
763 }
764 if !changed_types.is_empty() {
765 drop(d);
766 let mut m = self.meta.lock();
767 m.freshness = QotRightFreshness::Fresh;
768 m.last_refresh_at_ms = now_ms();
769 }
770 changed_types
771 }
772
773 pub fn apply_direct_auth_changes(&self, items: &[(i32, u32)]) -> Vec<i32> {
774 let mut d = self.data.write();
775 let mut changed_types = Vec::new();
776
777 for &(quote_type, after) in items {
778 if apply_qot_right_after_change(&mut d, quote_type, after) {
779 changed_types.push(quote_type);
780 }
781 }
782 if !changed_types.is_empty() {
783 drop(d);
784 let mut m = self.meta.lock();
785 m.freshness = QotRightFreshness::Fresh;
786 m.last_refresh_at_ms = now_ms();
787 }
788 changed_types
789 }
790
791 pub fn update_from_backend(&self, update: QotRightBackendUpdate) {
800 let QotRightBackendUpdate {
801 hk_got,
802 us_got,
803 cn_got,
804 sh_auth,
805 sz_auth,
806 hk_option,
807 hk_future,
808 us_option,
809 us_future_cme_cboe,
810 us_future_detail,
811 sg_future,
812 jp_future,
813 digital_currency_auth,
814 digital_pt_orderbook_auth,
815 sub_limit,
816 kl_limit,
817 extras,
818 } = update;
819 let mut d = self.data.write();
820
821 if let Some(v) = hk_got {
822 d.hk_qot_right = hk_clt_to_api(v);
823 }
824 if let Some(v) = us_got {
825 let right = us_clt_to_api(v);
826 d.us_qot_right = right;
827 }
828 if let Some(flags) = extras.us_lv2_flags {
829 let (arca, nyse, nasdaq_tv, _edg, _bzx) = flags;
830 d.us_lv2_arca_qot_right = arca == Some(1);
831 d.us_lv2_nyse_qot_right = nyse == Some(1);
832 d.us_lv2_nasdaq_totalview_qot_right = nasdaq_tv == Some(1);
833 if has_any_us_lv2_flag(flags) && matches!(d.us_qot_right, 2 | 3) {
834 d.us_qot_right = 3;
838 }
839 }
840 if let Some((dow_jones, nasdaq, standard_poor)) = extras.us_index_flags
841 && let Some(api_right) = us_index_flags_to_api(dow_jones, nasdaq, standard_poor)
842 {
843 d.us_index_qot_right = api_right;
844 }
845 if let Some((deal_data_auth, order_book_auth)) = extras.us_otc_auths {
846 d.us_otc_qot_right = us_otc_comm_auth_to_api(deal_data_auth, order_book_auth);
847 }
848 if let Some(v) = sh_auth {
849 d.sh_qot_right = cn_clt_to_api(v);
850 } else if let Some(v) = cn_got {
851 d.sh_qot_right = cn_clt_to_api(v);
852 }
853 if let Some(v) = sz_auth {
854 d.sz_qot_right = cn_clt_to_api(v);
855 } else if let Some(v) = cn_got {
856 d.sz_qot_right = cn_clt_to_api(v);
857 }
858 if let Some(v) = hk_option {
859 d.hk_option_qot_right = hk_clt_to_api(v);
860 }
861 if let Some(v) = hk_future {
862 d.hk_future_qot_right = hk_clt_to_api(v);
863 }
864 if let Some(v) = us_option {
865 d.us_option_qot_right = us_option_clt_to_api(v);
866 d.has_us_option_qot_right = d.us_option_qot_right != 1;
867 }
868 if let Some(v) = us_future_cme_cboe {
869 d.us_cme_future_qot_right = us_future_clt_to_api(v);
870 d.us_cboe_future_qot_right = 5;
875 }
876
877 if let Some((cme, cbot, nymex, comex)) = us_future_detail {
880 if let Some(cme) = cme {
881 d.us_cme_future_qot_right = us_future_clt_to_api(cme);
882 }
883 if let Some(cbot) = cbot {
884 d.us_cbot_future_qot_right = us_future_clt_to_api(cbot);
885 }
886 if let Some(nymex) = nymex {
887 d.us_nymex_future_qot_right = us_future_clt_to_api(nymex);
888 }
889 if let Some(comex) = comex {
890 d.us_comex_future_qot_right = us_future_clt_to_api(comex);
891 }
892 d.us_cboe_future_qot_right = 5;
894 }
895
896 if let Some(v) = sg_future {
897 d.sg_future_qot_right = other_clt_to_api(v);
898 }
899 if let Some(v) = jp_future {
900 d.jp_future_qot_right = other_clt_to_api(v);
901 }
902 if let Some(v) = digital_currency_auth {
903 d.cc_qot_right = crypto_auth_to_api(v);
904 }
905 if let Some(v) = digital_pt_orderbook_auth {
906 d.cc_pt_orderbook_qot_right = crypto_auth_to_api(v);
907 }
908
909 if let Some(v) = sub_limit
910 && v > 0
911 {
912 d.sub_quota = v as i32;
913 }
914 if let Some(v) = kl_limit
915 && v > 0
916 {
917 d.history_kl_quota = v as i32;
918 }
919
920 drop(d);
922 let mut m = self.meta.lock();
923 m.freshness = QotRightFreshness::Fresh;
924 m.last_refresh_at_ms = now_ms();
925 }
926}
927
928fn apply_qot_right_after_change(data: &mut QotRightData, quote_type: i32, after: u32) -> bool {
929 match quote_type {
930 1 => data.hk_qot_right = hk_clt_to_api(after),
931 3 | 17 => {
932 let right = us_clt_to_api(after);
933 data.us_qot_right = right;
934 data.api_us_qot_right = right;
935 }
936 4 => data.hk_future_qot_right = hk_clt_to_api(after),
937 5 => data.hk_option_qot_right = hk_clt_to_api(after),
938 36 => data.sg_future_qot_right = other_clt_to_api(after),
939 39 => data.jp_future_qot_right = other_clt_to_api(after),
940 30 => data.us_cme_future_qot_right = us_future_clt_to_api(after),
941 31 => data.us_cbot_future_qot_right = us_future_clt_to_api(after),
942 32 => data.us_nymex_future_qot_right = us_future_clt_to_api(after),
943 33 => data.us_comex_future_qot_right = us_future_clt_to_api(after),
944 37 => data.us_otc_qot_right = if after == 2 { 2 } else { 5 },
945 38 => data.us_index_qot_right = us_clt_to_api(after),
946 40 => data.sh_qot_right = cn_clt_to_api(after),
947 41 => data.sz_qot_right = cn_clt_to_api(after),
948 _ => return false,
949 }
950 true
951}
952
953#[cfg(test)]
954mod tests;