1use std::sync::Arc;
15
16use parking_lot::{Mutex, RwLock};
17
18mod capability;
19#[cfg(test)]
20mod capability_tests;
21mod projection;
22mod reconnect;
23mod security_rules;
24
25pub use capability::{
26 QuoteCapability, QuoteCryptoPolicy, QuoteOrderBookPolicy, QuoteSnapshotPolicy,
27 resolve_orderbook_entitlement, resolve_quote_capability,
28};
29use futu_domain_qot_right::{
30 QOT_API_QUOTA_DEFAULT, QotRight6651Change, QotRightBackendCurrent, QotRightBackendProjection,
31 QotRightDirectAuthChange, QotRightPushCurrentFacts, QotRightRefreshCurrentFacts,
32 project_qot_right_6651_change_with_current, project_qot_right_backend_update,
33 project_qot_right_direct_auth_change, qot_right_after_push_changes, qot_right_mark_failed,
34 qot_right_mark_fresh, qot_right_mark_pending, qot_right_on_epoch_advanced,
35};
36pub use futu_domain_qot_right::{
37 QOT_MARKET_CC_SECURITY, QOT_RIGHT_BMP, QOT_RIGHT_LEVEL1, QOT_RIGHT_LEVEL2, QOT_RIGHT_LEVEL3,
38 QOT_RIGHT_NO, QOT_RIGHT_SF, QOT_RIGHT_UNKNOWN, QotRightBackendExtras, QotRightBackendUpdate,
39 QotRightFreshness, QotRightPushedQuoteChangeNotify as PushedQuoteChangeNotify,
40 SECURITY_TYPE_CRYPTO, SECURITY_TYPE_DRVT, SECURITY_TYPE_FUTURE, SECURITY_TYPE_INDEX,
41};
42pub use futu_domain_qot_subscription::{
43 JP_LV2_ORDER_STOCK, JP_LV2_ORDER_STOCK_FULL, LV2_ORDER_US_FUTURE, Lv2OrderGroupPlan,
44 SG_LV2_ORDER_STOCK, SG_LV2_ORDER_STOCK_ODD_LOT, US_LV2_ORDER_ARCA, US_LV2_ORDER_NASDAQ_TV,
45 US_LV2_ORDER_OVERNIGHT,
46};
47pub use projection::{UsFutureDetailAuths, UsIndexFlags, UsLv2Flags, UsOtcAuths};
48use reconnect::{QotRightPublicSnapshot, QotRightReconnectSnapshot};
49pub use reconnect::{
50 QotRightReconnectAccumulator, QotRightReconnectClaim, QotRightReconnectClaimKind,
51 QotRightReconnectHandoff, QotRightReconnectLifecycle, QotRightReconnectRegistration,
52 QotRightReconnectToken,
53};
54pub use security_rules::{
55 basic_qot_uses_us_pre_after_detail, has_lv2_order_groups_for_security, is_crypto_market,
56 lv2_order_groups_for_security, lv2_order_groups_for_security_with_dynamic,
57 lv2_order_uses_jp_main_cache, lv2_order_uses_sg_odd_lot_cache,
58 order_book_max_depth_for_security, order_book_read_uses_requested_count,
59 order_book_requires_accepted_lv2_push, order_book_requires_backend_full_depth,
60 order_book_uses_backend_side_count, snapshot_masks_hk_bmp_bid_ask,
61 us_lv2_order_uses_exchange_cache,
62};
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct QotRightData {
66 pub hk_own_qot_right: i32,
68 pub hk_qot_right: i32,
69 pub us_own_qot_right: i32,
71 pub us_qot_right: i32,
72 pub api_us_qot_right: i32,
74 pub has_us_utp: bool,
76 pub sh_qot_right: i32,
77 pub sz_qot_right: i32,
78 pub hk_option_qot_right: i32,
79 pub hk_future_qot_right: i32,
80 pub has_us_option_qot_right: bool,
81 pub us_option_qot_right: i32,
82 pub us_index_qot_right: i32,
83 pub us_otc_qot_right: i32,
84 pub us_cme_future_qot_right: i32,
85 pub us_cbot_future_qot_right: i32,
86 pub us_nymex_future_qot_right: i32,
87 pub us_comex_future_qot_right: i32,
88 pub us_cboe_future_qot_right: i32,
89 pub sg_future_qot_right: i32,
90 pub jp_future_qot_right: i32,
91 pub sg_stock_qot_right: i32,
92 pub my_stock_qot_right: i32,
93 pub jp_stock_qot_right: i32,
94 pub cc_qot_right: i32,
95 pub cc_pt_orderbook_qot_right: i32,
96 pub event_contract_qot_right: i32,
97 pub event_contract_prohibited_category_ids: Vec<u32>,
98 pub us_lv2_arca_qot_right: bool,
99 pub us_lv2_nyse_qot_right: bool,
100 pub us_lv2_nasdaq_totalview_qot_right: bool,
101 pub sub_quota: i32,
102 pub history_kl_quota: i32,
103 pub hk_option_orderbook_depth: Option<u32>,
105 pub hk_future_orderbook_depth: Option<u32>,
106}
107
108impl Default for QotRightData {
109 fn default() -> Self {
110 const UNKNOWN: i32 = 0;
111 Self {
112 hk_own_qot_right: UNKNOWN,
113 hk_qot_right: UNKNOWN,
114 us_own_qot_right: UNKNOWN,
115 us_qot_right: UNKNOWN,
116 api_us_qot_right: UNKNOWN,
117 has_us_utp: false,
118 sh_qot_right: UNKNOWN,
119 sz_qot_right: UNKNOWN,
120 hk_option_qot_right: UNKNOWN,
121 hk_future_qot_right: UNKNOWN,
122 has_us_option_qot_right: false,
125 us_option_qot_right: UNKNOWN,
126 us_index_qot_right: UNKNOWN,
127 us_otc_qot_right: UNKNOWN,
128 us_cme_future_qot_right: UNKNOWN,
129 us_cbot_future_qot_right: UNKNOWN,
130 us_nymex_future_qot_right: UNKNOWN,
131 us_comex_future_qot_right: UNKNOWN,
132 us_cboe_future_qot_right: UNKNOWN,
133 sg_future_qot_right: UNKNOWN,
134 jp_future_qot_right: UNKNOWN,
135 sg_stock_qot_right: UNKNOWN,
136 my_stock_qot_right: UNKNOWN,
137 jp_stock_qot_right: UNKNOWN,
138 cc_qot_right: UNKNOWN,
139 cc_pt_orderbook_qot_right: UNKNOWN,
140 event_contract_qot_right: UNKNOWN,
141 event_contract_prohibited_category_ids: Vec::new(),
142 us_lv2_arca_qot_right: false,
143 us_lv2_nyse_qot_right: false,
144 us_lv2_nasdaq_totalview_qot_right: false,
145 sub_quota: QOT_API_QUOTA_DEFAULT,
146 history_kl_quota: QOT_API_QUOTA_DEFAULT,
147 hk_option_orderbook_depth: None,
148 hk_future_orderbook_depth: None,
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
154pub struct QotRightRefreshReport {
155 pub changed: bool,
156 pub reconnect_quote_types: Vec<i32>,
157 pub has_set_qot_right_before: bool,
158 pub has_us_utp_changed: bool,
159 pub quota_data_changed: bool,
160 pub quota_projection_available: bool,
161 pub backend_generation: u64,
162 pub route_publication_epoch: u64,
163 pub request_is_highest: bool,
164 pub force_resubscribe: bool,
165 pub resubscribe_scope: futu_domain_qot_right::QotRightResubscribeScope,
166 pub deferred_reconnect_token: Option<QotRightReconnectToken>,
167 pub deferred_reconnect_blocked: bool,
168 pub fresh_at_ms: i64,
169 pub decoded_fields: Vec<&'static str>,
170 pub freshness_after: QotRightFreshness,
171 pub login_epoch: u64,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct QotRightBackendApplyOutcome {
176 pub public_rights_changed: bool,
177 pub reconnect_quote_types: Vec<i32>,
178 pub has_set_qot_right_before: bool,
179 pub has_us_utp_changed: bool,
180 pub quota_data_changed: bool,
181 pub quota_projection_available: bool,
182}
183
184impl QotRightBackendApplyOutcome {
185 #[must_use]
186 pub fn cpp_reconnect_rights_changed(&self) -> bool {
187 !self.reconnect_quote_types.is_empty()
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub struct QotRightFullApplyFacts {
193 pub expected_generation: u64,
194 pub is_highest: bool,
195 pub force_resub: bool,
196 pub pushed_notify_version: Option<u64>,
197}
198
199#[must_use]
200pub fn has_set_qot_right_like_cpp(data: &QotRightData) -> bool {
201 [
204 data.hk_own_qot_right,
205 data.hk_option_qot_right,
206 data.hk_qot_right,
207 data.us_qot_right,
208 data.us_own_qot_right,
209 data.sh_qot_right,
210 data.sz_qot_right,
211 data.hk_future_qot_right,
212 data.us_option_qot_right,
213 data.us_cme_future_qot_right,
214 data.us_cbot_future_qot_right,
215 data.us_nymex_future_qot_right,
216 data.us_comex_future_qot_right,
217 data.us_cboe_future_qot_right,
218 data.us_index_qot_right,
219 data.us_otc_qot_right,
220 data.sg_future_qot_right,
221 data.sg_stock_qot_right,
222 data.jp_future_qot_right,
223 data.my_stock_qot_right,
224 data.jp_stock_qot_right,
225 data.cc_qot_right,
226 data.event_contract_qot_right,
227 ]
228 .into_iter()
229 .any(|right| right != QOT_RIGHT_UNKNOWN)
230}
231
232#[derive(Debug, Clone)]
233pub struct QotRightStateMeta {
234 pub freshness: QotRightFreshness,
235 pub user_id: Option<u64>,
236 pub login_epoch: u64,
237 pub backend_generation: u64,
238 pub last_refresh_at_ms: i64,
239 pub last_pushed_quote_change_notify: Option<PushedQuoteChangeNotify>,
240 pub last_pushed_quote_change_notify_version: u64,
241 pub reconnect_lifecycle: QotRightReconnectLifecycle,
242 pub next_reconnect_token: u64,
243 pub event_contract_category_preload_pending: bool,
247}
248
249impl Default for QotRightStateMeta {
250 fn default() -> Self {
251 Self {
252 freshness: QotRightFreshness::Unknown,
253 user_id: None,
254 login_epoch: 0,
255 backend_generation: 0,
256 last_refresh_at_ms: 0,
257 last_pushed_quote_change_notify: None,
258 last_pushed_quote_change_notify_version: 0,
259 reconnect_lifecycle: QotRightReconnectLifecycle::Idle,
260 next_reconnect_token: 1,
261 event_contract_category_preload_pending: false,
262 }
263 }
264}
265
266fn now_ms() -> i64 {
267 use std::time::{SystemTime, UNIX_EPOCH};
268 SystemTime::now()
269 .duration_since(UNIX_EPOCH)
270 .map(|d| d.as_millis() as i64)
271 .unwrap_or(0)
272}
273
274fn next_reconnect_token(meta: &mut QotRightStateMeta) -> QotRightReconnectToken {
275 let token = QotRightReconnectToken(meta.next_reconnect_token);
276 meta.next_reconnect_token = meta.next_reconnect_token.saturating_add(1);
277 token
278}
279
280#[derive(Clone)]
281pub struct QotRightCache {
282 data: Arc<RwLock<QotRightData>>,
283 meta: Arc<Mutex<QotRightStateMeta>>,
284}
285
286impl Default for QotRightCache {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292impl QotRightCache {
293 pub fn new() -> Self {
294 Self {
295 data: Arc::new(RwLock::new(QotRightData::default())),
296 meta: Arc::new(Mutex::new(QotRightStateMeta::default())),
297 }
298 }
299
300 pub fn get(&self) -> QotRightData {
301 self.data.read().clone()
302 }
303
304 pub fn refresh_current_facts(&self) -> QotRightRefreshCurrentFacts {
305 let d = self.data.read();
306 QotRightRefreshCurrentFacts {
307 hk_qot_right: d.hk_qot_right,
308 hk_future_qot_right: d.hk_future_qot_right,
309 hk_option_qot_right: d.hk_option_qot_right,
310 api_us_qot_right: d.api_us_qot_right,
311 has_us_utp: d.has_us_utp,
312 us_lv2_nasdaq_totalview_qot_right: d.us_lv2_nasdaq_totalview_qot_right,
313 sg_stock_qot_right: d.sg_stock_qot_right,
314 jp_stock_qot_right: d.jp_stock_qot_right,
315 us_cme_future_qot_right: d.us_cme_future_qot_right,
316 us_cbot_future_qot_right: d.us_cbot_future_qot_right,
317 us_nymex_future_qot_right: d.us_nymex_future_qot_right,
318 us_comex_future_qot_right: d.us_comex_future_qot_right,
319 sg_future_qot_right: d.sg_future_qot_right,
320 us_otc_qot_right: d.us_otc_qot_right,
321 us_index_qot_right: d.us_index_qot_right,
322 jp_future_qot_right: d.jp_future_qot_right,
323 sh_qot_right: d.sh_qot_right,
324 sz_qot_right: d.sz_qot_right,
325 }
326 }
327
328 pub fn freshness(&self) -> QotRightFreshness {
329 self.meta.lock().freshness.clone()
330 }
331
332 pub fn is_fresh(&self) -> bool {
333 self.meta.lock().freshness.is_fresh()
334 }
335
336 pub fn meta_snapshot(&self) -> QotRightStateMeta {
337 self.meta.lock().clone()
338 }
339
340 pub fn take_event_contract_category_preload_request(&self) -> bool {
341 let mut meta = self.meta.lock();
342 std::mem::take(&mut meta.event_contract_category_preload_pending)
343 }
344
345 pub fn set_orderbook_depths(&self, hk_option_depth: Option<u32>, hk_future_depth: Option<u32>) {
346 let mut d = self.data.write();
347 if let Some(v) = hk_option_depth {
348 d.hk_option_orderbook_depth = Some(v);
349 }
350 if let Some(v) = hk_future_depth {
351 d.hk_future_orderbook_depth = Some(v);
352 }
353 }
354
355 pub fn mark_stale(&self, reason: &str) {
356 let mut m = self.meta.lock();
357 let old = m.freshness.clone();
358 m.freshness = QotRightFreshness::Stale;
359 tracing::info!(
360 old_freshness = ?old,
361 reason,
362 login_epoch = m.login_epoch,
363 backend_generation = m.backend_generation,
364 "QotRightCache: marked stale"
365 );
366 }
367
368 pub fn advance_login_epoch(&self, new_epoch: u64, user_id: Option<u64>) {
369 let mut m = self.meta.lock();
370 let old_epoch = m.login_epoch;
371 m.login_epoch = new_epoch;
372 m.user_id = user_id;
373 m.freshness = qot_right_on_epoch_advanced(m.freshness.clone());
374 let user_id_fp = user_id.map(futu_core::log_redact::uid_log_fingerprint);
375 tracing::info!(
376 old_epoch,
377 new_epoch,
378 user_id_fp = ?user_id_fp,
379 "QotRightCache: login epoch advanced"
380 );
381 }
382
383 pub fn advance_backend_generation(&self, new_generation: u64) {
384 let mut m = self.meta.lock();
385 let old_gen = m.backend_generation;
386 m.backend_generation = new_generation;
387 m.freshness = qot_right_on_epoch_advanced(m.freshness.clone());
388 tracing::info!(
389 old_generation = old_gen,
390 new_generation,
391 "QotRightCache: backend generation advanced"
392 );
393 }
394
395 pub fn mark_pending(&self) {
396 self.meta.lock().freshness = qot_right_mark_pending();
397 }
398
399 pub fn mark_pending_if_generation(&self, expected_generation: u64) -> bool {
400 let mut meta = self.meta.lock();
401 if meta.backend_generation != expected_generation {
402 return false;
403 }
404 meta.freshness = qot_right_mark_pending();
405 true
406 }
407
408 pub fn mark_fresh(&self) {
409 let mut m = self.meta.lock();
410 m.freshness = qot_right_mark_fresh();
411 m.last_refresh_at_ms = now_ms();
412 }
413
414 pub fn mark_failed(&self, error: impl Into<String>) {
415 self.meta.lock().freshness = qot_right_mark_failed(error, now_ms());
416 }
417
418 pub fn mark_failed_if_generation(
419 &self,
420 expected_generation: u64,
421 error: impl Into<String>,
422 ) -> bool {
423 let mut meta = self.meta.lock();
424 if meta.backend_generation != expected_generation {
425 return false;
426 }
427 meta.freshness = qot_right_mark_failed(error, now_ms());
428 true
429 }
430
431 pub fn last_refresh_at_ms(&self) -> i64 {
432 self.meta.lock().last_refresh_at_ms
433 }
434
435 pub fn set_pushed_quote_change_notify(&self, notify: PushedQuoteChangeNotify) {
436 let mut meta = self.meta.lock();
437 meta.last_pushed_quote_change_notify_version = meta
438 .last_pushed_quote_change_notify_version
439 .saturating_add(1);
440 meta.last_pushed_quote_change_notify = Some(notify);
441 }
442
443 pub fn pushed_quote_change_notify(&self) -> Option<PushedQuoteChangeNotify> {
444 self.meta.lock().last_pushed_quote_change_notify.clone()
445 }
446
447 pub fn pushed_quote_change_notify_snapshot(&self) -> (u64, Option<PushedQuoteChangeNotify>) {
448 let meta = self.meta.lock();
449 (
450 meta.last_pushed_quote_change_notify_version,
451 meta.last_pushed_quote_change_notify.clone(),
452 )
453 }
454
455 pub fn apply_6651_changes(&self, items: &[(i32, i32, i32)]) -> Vec<i32> {
456 let mut d = self.data.write();
457 let mut changed_types = Vec::new();
458
459 for &(quote_type, before, after) in items {
460 let current = QotRightPushCurrentFacts {
461 api_us_qot_right: d.api_us_qot_right,
462 us_lv2_arca: d.us_lv2_arca_qot_right,
463 us_lv2_nyse: d.us_lv2_nyse_qot_right,
464 us_lv2_nasdaq_totalview: d.us_lv2_nasdaq_totalview_qot_right,
465 };
466 if let Some(projection) = project_qot_right_6651_change_with_current(
467 QotRight6651Change {
468 quote_type,
469 before,
470 after,
471 },
472 current,
473 ) {
474 apply_backend_projection(&mut d, projection.patch);
475 changed_types.push(projection.quote_type);
476 }
477 }
478 if !changed_types.is_empty() {
479 drop(d);
480 let mut m = self.meta.lock();
481 m.freshness = qot_right_after_push_changes(m.freshness.clone(), true);
482 if m.freshness.is_fresh() {
483 m.last_refresh_at_ms = now_ms();
484 }
485 }
486 changed_types
487 }
488
489 pub fn apply_direct_auth_changes(&self, items: &[(i32, u32)]) -> Vec<i32> {
490 let mut d = self.data.write();
491 let mut changed_types = Vec::new();
492
493 for &(quote_type, after) in items {
494 if let Some(projection) =
495 project_qot_right_direct_auth_change(QotRightDirectAuthChange { quote_type, after })
496 {
497 apply_backend_projection(&mut d, projection.patch);
498 changed_types.push(projection.quote_type);
499 }
500 }
501 if !changed_types.is_empty() {
502 drop(d);
503 let mut m = self.meta.lock();
504 m.freshness = qot_right_after_push_changes(m.freshness.clone(), true);
505 if m.freshness.is_fresh() {
506 m.last_refresh_at_ms = now_ms();
507 }
508 }
509 changed_types
510 }
511
512 pub fn update_from_backend(
521 &self,
522 update: QotRightBackendUpdate,
523 ) -> QotRightBackendApplyOutcome {
524 let mut data = self.data.write();
525 let mut meta = self.meta.lock();
526 let expected_generation = meta.backend_generation;
527 Self::apply_full_backend_update_locked(
528 &mut data,
529 &mut meta,
530 QotRightFullApplyFacts {
531 expected_generation,
532 is_highest: false,
533 force_resub: false,
534 pushed_notify_version: None,
535 },
536 update,
537 )
538 }
539
540 pub fn update_from_backend_if_generation(
541 &self,
542 facts: QotRightFullApplyFacts,
543 update: QotRightBackendUpdate,
544 ) -> Option<QotRightBackendApplyOutcome> {
545 let mut d = self.data.write();
546 let mut m = self.meta.lock();
547 if m.backend_generation != facts.expected_generation {
548 return None;
549 }
550 Some(Self::apply_full_backend_update_locked(
551 &mut d, &mut m, facts, update,
552 ))
553 }
554
555 fn apply_full_backend_update_locked(
556 d: &mut QotRightData,
557 m: &mut QotRightStateMeta,
558 facts: QotRightFullApplyFacts,
559 update: QotRightBackendUpdate,
560 ) -> QotRightBackendApplyOutcome {
561 let has_set_qot_right_before = has_set_qot_right_like_cpp(d);
562 let public_before = QotRightPublicSnapshot::from(&*d);
563 let reconnect_before = QotRightReconnectSnapshot::from(&*d);
564 let has_us_utp_before = d.has_us_utp;
565 let quota_before = (d.sub_quota, d.history_kl_quota);
566 let projection = project_qot_right_backend_update(
567 QotRightBackendCurrent {
568 us_qot_right: d.us_qot_right,
569 },
570 update,
571 );
572 let event_contract_became_level1 = projection.event_contract_qot_right
573 == Some(QOT_RIGHT_LEVEL1)
574 && d.event_contract_qot_right != QOT_RIGHT_LEVEL1;
575 apply_backend_projection(d, projection);
576 let quota_data_changed = quota_before != (d.sub_quota, d.history_kl_quota);
577 let public_rights_changed = public_before != QotRightPublicSnapshot::from(&*d);
578 let reconnect_quote_types = reconnect_before.changed_quote_types(d);
579 let has_us_utp_changed = has_us_utp_before != d.has_us_utp;
580
581 m.freshness = qot_right_mark_fresh();
583 m.last_refresh_at_ms = now_ms();
584 m.event_contract_category_preload_pending |= event_contract_became_level1;
585
586 if facts.is_highest
587 && (!reconnect_quote_types.is_empty() || facts.force_resub)
588 && facts.pushed_notify_version == Some(m.last_pushed_quote_change_notify_version)
589 {
590 m.last_pushed_quote_change_notify = None;
591 }
592
593 QotRightBackendApplyOutcome {
594 public_rights_changed,
595 reconnect_quote_types,
596 has_set_qot_right_before,
597 has_us_utp_changed,
598 quota_data_changed,
599 quota_projection_available: true,
603 }
604 }
605
606 pub fn update_quotas_from_backend_if_generation(
607 &self,
608 expected_generation: u64,
609 update: QotRightBackendUpdate,
610 ) -> Option<QotRightBackendApplyOutcome> {
611 let projection =
612 project_qot_right_backend_update(QotRightBackendCurrent::default(), update);
613 let mut d = self.data.write();
614 let m = self.meta.lock();
615 if m.backend_generation != expected_generation {
616 return None;
617 }
618 let quota_before = (d.sub_quota, d.history_kl_quota);
619 if let Some(value) = projection.sub_quota {
620 d.sub_quota = value;
621 }
622 if let Some(value) = projection.history_kl_quota {
623 d.history_kl_quota = value;
624 }
625 Some(QotRightBackendApplyOutcome {
626 public_rights_changed: false,
627 reconnect_quote_types: Vec::new(),
628 has_set_qot_right_before: false,
629 has_us_utp_changed: false,
630 quota_data_changed: quota_before != (d.sub_quota, d.history_kl_quota),
631 quota_projection_available: true,
632 })
633 }
634}
635
636fn apply_backend_projection(data: &mut QotRightData, projection: QotRightBackendProjection) {
637 if let Some(v) = projection.hk_own_qot_right {
638 data.hk_own_qot_right = v;
639 }
640 if let Some(v) = projection.hk_qot_right {
641 data.hk_qot_right = v;
642 }
643 if let Some(v) = projection.us_own_qot_right {
644 data.us_own_qot_right = v;
645 }
646 if let Some(v) = projection.us_qot_right {
647 data.us_qot_right = v;
648 }
649 if let Some(v) = projection.api_us_qot_right {
650 data.api_us_qot_right = v;
651 }
652 if let Some(v) = projection.has_us_utp {
653 data.has_us_utp = v;
654 }
655 if let Some(v) = projection.sh_qot_right {
656 data.sh_qot_right = v;
657 }
658 if let Some(v) = projection.sz_qot_right {
659 data.sz_qot_right = v;
660 }
661 if let Some(v) = projection.hk_option_qot_right {
662 data.hk_option_qot_right = v;
663 }
664 if let Some(v) = projection.hk_future_qot_right {
665 data.hk_future_qot_right = v;
666 }
667 if let Some(v) = projection.hk_option_orderbook_depth {
668 data.hk_option_orderbook_depth = Some(v);
669 }
670 if let Some(v) = projection.hk_future_orderbook_depth {
671 data.hk_future_orderbook_depth = Some(v);
672 }
673 if let Some(v) = projection.has_us_option_qot_right {
674 data.has_us_option_qot_right = v;
675 }
676 if let Some(v) = projection.us_option_qot_right {
677 data.us_option_qot_right = v;
678 }
679 if let Some(v) = projection.us_index_qot_right {
680 data.us_index_qot_right = v;
681 }
682 if let Some(v) = projection.us_otc_qot_right {
683 data.us_otc_qot_right = v;
684 }
685 if let Some(v) = projection.us_cme_future_qot_right {
686 data.us_cme_future_qot_right = v;
687 }
688 if let Some(v) = projection.us_cbot_future_qot_right {
689 data.us_cbot_future_qot_right = v;
690 }
691 if let Some(v) = projection.us_nymex_future_qot_right {
692 data.us_nymex_future_qot_right = v;
693 }
694 if let Some(v) = projection.us_comex_future_qot_right {
695 data.us_comex_future_qot_right = v;
696 }
697 if let Some(v) = projection.us_cboe_future_qot_right {
698 data.us_cboe_future_qot_right = v;
699 }
700 if let Some(v) = projection.sg_future_qot_right {
701 data.sg_future_qot_right = v;
702 }
703 if let Some(v) = projection.jp_future_qot_right {
704 data.jp_future_qot_right = v;
705 }
706 if let Some(v) = projection.sg_stock_qot_right {
707 data.sg_stock_qot_right = v;
708 }
709 if let Some(v) = projection.my_stock_qot_right {
710 data.my_stock_qot_right = v;
711 }
712 if let Some(v) = projection.jp_stock_qot_right {
713 data.jp_stock_qot_right = v;
714 }
715 if let Some(v) = projection.cc_qot_right {
716 data.cc_qot_right = v;
717 }
718 if let Some(v) = projection.cc_pt_orderbook_qot_right {
719 data.cc_pt_orderbook_qot_right = v;
720 }
721 if let Some(v) = projection.event_contract_qot_right {
722 data.event_contract_qot_right = v;
723 }
724 if let Some(ids) = projection.event_contract_prohibited_category_ids {
725 data.event_contract_prohibited_category_ids = ids;
726 }
727 if let Some(v) = projection.us_lv2_arca_qot_right {
728 data.us_lv2_arca_qot_right = v;
729 }
730 if let Some(v) = projection.us_lv2_nyse_qot_right {
731 data.us_lv2_nyse_qot_right = v;
732 }
733 if let Some(v) = projection.us_lv2_nasdaq_totalview_qot_right {
734 data.us_lv2_nasdaq_totalview_qot_right = v;
735 }
736 if let Some(v) = projection.sub_quota {
737 data.sub_quota = v;
738 }
739 if let Some(v) = projection.history_kl_quota {
740 data.history_kl_quota = v;
741 }
742}
743
744#[cfg(test)]
745mod tests;