Skip to main content

futu_cache/qot_right/
security_rules.rs

1use crate::static_data::CachedSecurityInfo;
2
3use futu_domain_qot_orderbook::entitlement::OrderBookRightFacts;
4use futu_domain_qot_orderbook::{OrderBookEntitlementFacts, OrderBookSecurityFacts};
5use futu_domain_qot_subscription::{
6    SubscriptionQotRightFacts, SubscriptionSecurityFacts, basic_qot_uses_us_pre_after_detail_facts,
7    lv2_order_groups_for_security_facts, lv2_order_groups_for_security_facts_with_dynamic,
8};
9
10use super::{
11    JP_LV2_ORDER_STOCK, JP_LV2_ORDER_STOCK_FULL, Lv2OrderGroupPlan, QOT_MARKET_CC_SECURITY,
12    QOT_RIGHT_BMP, QotRightData, SECURITY_TYPE_CRYPTO, SECURITY_TYPE_DRVT, SECURITY_TYPE_FUTURE,
13    SECURITY_TYPE_INDEX, SG_LV2_ORDER_STOCK_ODD_LOT,
14};
15
16fn is_hk_future_market(info: &CachedSecurityInfo) -> bool {
17    info.sec_type == SECURITY_TYPE_FUTURE && matches!(info.mkt_id, 5 | 6 | 110..=119)
18}
19
20fn is_us_future_market(info: &CachedSecurityInfo) -> bool {
21    info.sec_type == SECURITY_TYPE_FUTURE && (60..=109).contains(&info.mkt_id)
22}
23
24fn is_sg_security_market(info: &CachedSecurityInfo) -> bool {
25    (180..=184).contains(&info.mkt_id)
26        || (info.market == 31
27            && info.sec_type != SECURITY_TYPE_FUTURE
28            && info.sec_type != SECURITY_TYPE_DRVT)
29}
30
31fn is_jp_future_market(info: &CachedSecurityInfo) -> bool {
32    info.sec_type == SECURITY_TYPE_FUTURE && (185..=194).contains(&info.mkt_id)
33}
34
35fn is_jp_security_market(info: &CachedSecurityInfo) -> bool {
36    (830..=849).contains(&info.mkt_id)
37        || (info.market == 41
38            && info.sec_type != SECURITY_TYPE_FUTURE
39            && info.sec_type != SECURITY_TYPE_DRVT
40            && !is_jp_future_market(info)
41            && !(800..=829).contains(&info.mkt_id))
42}
43
44fn is_hk_option_market(info: &CachedSecurityInfo) -> bool {
45    info.sec_type == SECURITY_TYPE_DRVT && matches!(info.mkt_id, 7 | 8 | 570..=579)
46}
47
48fn is_us_option_market(info: &CachedSecurityInfo) -> bool {
49    info.sec_type == SECURITY_TYPE_DRVT && (41..=49).contains(&info.mkt_id)
50}
51
52fn is_hk_security_market(info: &CachedSecurityInfo) -> bool {
53    matches!(info.mkt_id, 1..=4 | 1000..=1049)
54        || (info.market == 1
55            && !is_hk_future_market(info)
56            && !is_hk_option_market(info)
57            && info.sec_type != SECURITY_TYPE_FUTURE
58            && info.sec_type != SECURITY_TYPE_DRVT)
59}
60
61/// C++ `APIServer_Qot_StockSnapshot.cpp:18-43` (`IsHKBMP_OneStock`).
62///
63/// When this returns true, GetSecuritySnapshot must omit bid/ask price and
64/// volume fields instead of returning delayed BMP values.
65pub(super) fn snapshot_masks_hk_bmp_bid_ask_impl(
66    info: &CachedSecurityInfo,
67    qr: &QotRightData,
68) -> bool {
69    let is_hk = matches!(info.market, 1 | 2);
70    if !is_hk {
71        return false;
72    }
73
74    if is_hk_option_market(info) {
75        return qr.hk_option_qot_right == QOT_RIGHT_BMP;
76    }
77    if is_hk_future_market(info) {
78        return qr.hk_future_qot_right == QOT_RIGHT_BMP;
79    }
80    is_hk_security_market(info) && qr.hk_qot_right == QOT_RIGHT_BMP
81}
82
83pub fn snapshot_masks_hk_bmp_bid_ask(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
84    snapshot_masks_hk_bmp_bid_ask_impl(info, qr)
85}
86
87fn is_us_security_market(info: &CachedSecurityInfo) -> bool {
88    matches!(info.mkt_id, 10..=29 | 1200..=1249)
89        || (info.market == 11
90            && !is_us_future_market(info)
91            && !is_us_option_market(info)
92            && info.sec_type != SECURITY_TYPE_FUTURE
93            && info.sec_type != SECURITY_TYPE_DRVT)
94}
95
96/// C++ `QotRealTimeData::ParseUSLv2ToMainCache` treats normal US securities
97/// (for example AAPL whose backend `instrument_type` is 3) as exchange-level
98/// LV2 sources: NASDAQ TotalView / ARCA first write `(stock_id, lv2_type)`,
99/// then all sources are merged into the public orderbook cache. Indexes do
100/// not use this stock LV2 merge path.
101pub fn us_lv2_order_uses_exchange_cache(info: &CachedSecurityInfo) -> bool {
102    is_us_security_market(info) && info.sec_type != SECURITY_TYPE_INDEX
103}
104
105/// v1.4.110 codex Phase 4 Slice 7: 把 crypto detection helper 暴露给 push_parser
106/// / handler (crypto LV2 sub-system 入口).
107///
108/// 对齐 C++ `IsCrypto(stockID)` (NNBiz_Qot_SecList.cpp). 三个条件任一: sec_type==Crypto
109/// / FTAPI QotMarket=91 (CC_Security) / mkt_id ∈ [360,459] (DigitalCcy range).
110pub fn is_crypto_market(info: &CachedSecurityInfo) -> bool {
111    info.sec_type == SECURITY_TYPE_CRYPTO
112        || info.market == QOT_MARKET_CC_SECURITY
113        || (360..=459).contains(&info.mkt_id)
114}
115
116pub(super) fn orderbook_security_facts_impl(info: &CachedSecurityInfo) -> OrderBookSecurityFacts {
117    OrderBookSecurityFacts::from_static_fields(
118        info.stock_id,
119        info.market,
120        info.mkt_id,
121        info.sec_type,
122    )
123}
124
125pub(super) fn orderbook_right_facts_impl(qr: &QotRightData) -> OrderBookRightFacts {
126    OrderBookRightFacts {
127        hk_qot_right: qr.hk_qot_right,
128        us_qot_right: qr.us_qot_right,
129        sh_qot_right: qr.sh_qot_right,
130        sz_qot_right: qr.sz_qot_right,
131        hk_option_qot_right: qr.hk_option_qot_right,
132        hk_future_qot_right: qr.hk_future_qot_right,
133        hk_option_orderbook_depth: qr.hk_option_orderbook_depth,
134        hk_future_orderbook_depth: qr.hk_future_orderbook_depth,
135        us_option_qot_right: qr.us_option_qot_right,
136        us_cme_future_qot_right: qr.us_cme_future_qot_right,
137        us_cbot_future_qot_right: qr.us_cbot_future_qot_right,
138        us_nymex_future_qot_right: qr.us_nymex_future_qot_right,
139        us_comex_future_qot_right: qr.us_comex_future_qot_right,
140        us_cboe_future_qot_right: qr.us_cboe_future_qot_right,
141        sg_future_qot_right: qr.sg_future_qot_right,
142        jp_future_qot_right: qr.jp_future_qot_right,
143        sg_stock_qot_right: qr.sg_stock_qot_right,
144        jp_stock_qot_right: qr.jp_stock_qot_right,
145        my_stock_qot_right: qr.my_stock_qot_right,
146        cc_qot_right: qr.cc_qot_right,
147        cc_pt_orderbook_qot_right: qr.cc_pt_orderbook_qot_right,
148        us_lv2_arca_qot_right: qr.us_lv2_arca_qot_right,
149        us_lv2_nyse_qot_right: qr.us_lv2_nyse_qot_right,
150        us_lv2_nasdaq_totalview_qot_right: qr.us_lv2_nasdaq_totalview_qot_right,
151    }
152}
153
154fn orderbook_entitlement_facts_impl(
155    info: &CachedSecurityInfo,
156    qr: &QotRightData,
157) -> OrderBookEntitlementFacts {
158    futu_domain_qot_orderbook::resolve_orderbook_entitlement_facts(
159        &orderbook_security_facts_impl(info),
160        &orderbook_right_facts_impl(qr),
161    )
162}
163
164/// C++ `SubBitUtil::GetPushTypeSvrSubBit(NN_PushQot_Type_Most)` 对 US /
165/// US options / US futures 保留 `SBIT_US_PREMARKET_AFTERHOURS_DETAIL`, 让
166/// BasicQot 可以随订阅收到 preMarket / afterMarket / overnight。
167///
168/// Ref:
169/// - `FutuOpenD/Src/NNProtoCenter/Quote/SubBitUtil.cpp:7-17`
170/// - `FutuOpenD/Src/NNProtoCenter/Quote/SubBitUtil.cpp:91-95`
171pub fn basic_qot_uses_us_pre_after_detail(info: &CachedSecurityInfo) -> bool {
172    basic_qot_uses_us_pre_after_detail_facts(&subscription_security_facts_impl(info))
173}
174
175fn subscription_security_facts_impl(info: &CachedSecurityInfo) -> SubscriptionSecurityFacts {
176    SubscriptionSecurityFacts {
177        stock_id: info.stock_id,
178        sec_type: info.sec_type,
179        mkt_id: info.mkt_id,
180        market: info.market,
181    }
182}
183
184fn subscription_qot_right_facts_impl(qr: &QotRightData) -> SubscriptionQotRightFacts {
185    SubscriptionQotRightFacts {
186        us_qot_right: qr.us_qot_right,
187        us_cme_future_qot_right: qr.us_cme_future_qot_right,
188        us_cbot_future_qot_right: qr.us_cbot_future_qot_right,
189        us_nymex_future_qot_right: qr.us_nymex_future_qot_right,
190        us_comex_future_qot_right: qr.us_comex_future_qot_right,
191        us_cboe_future_qot_right: qr.us_cboe_future_qot_right,
192        sg_stock_qot_right: qr.sg_stock_qot_right,
193        jp_stock_qot_right: qr.jp_stock_qot_right,
194        us_lv2_arca_qot_right: qr.us_lv2_arca_qot_right,
195        us_lv2_nyse_qot_right: qr.us_lv2_nyse_qot_right,
196        us_lv2_nasdaq_totalview_qot_right: qr.us_lv2_nasdaq_totalview_qot_right,
197        event_contract_qot_right: qr.event_contract_qot_right,
198    }
199}
200
201pub(super) fn lv2_order_groups_for_security_impl(
202    info: &CachedSecurityInfo,
203    qr: &QotRightData,
204    extended_time: bool,
205) -> Vec<Lv2OrderGroupPlan> {
206    lv2_order_groups_for_security_facts(
207        &subscription_security_facts_impl(info),
208        &subscription_qot_right_facts_impl(qr),
209        extended_time,
210    )
211}
212
213pub fn lv2_order_groups_for_security(
214    info: &CachedSecurityInfo,
215    qr: &QotRightData,
216    extended_time: bool,
217) -> Vec<Lv2OrderGroupPlan> {
218    lv2_order_groups_for_security_impl(info, qr, extended_time)
219}
220
221pub fn lv2_order_groups_for_security_with_dynamic(
222    info: &CachedSecurityInfo,
223    qr: &QotRightData,
224    extended_time: bool,
225    dynamic_lv2_prob_sum: Option<i32>,
226) -> Vec<Lv2OrderGroupPlan> {
227    lv2_order_groups_for_security_facts_with_dynamic(
228        &subscription_security_facts_impl(info),
229        &subscription_qot_right_facts_impl(qr),
230        extended_time,
231        dynamic_lv2_prob_sum,
232    )
233}
234
235pub fn has_lv2_order_groups_for_security(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
236    !lv2_order_groups_for_security_impl(info, qr, true).is_empty()
237}
238
239pub fn order_book_max_depth_for_security(info: &CachedSecurityInfo, qr: &QotRightData) -> usize {
240    orderbook_entitlement_facts_impl(info, qr).max_depth
241}
242
243/// C++ `IsHKSF`: HK 股票 + HK SF 权限要求 backend 全档摆盘。
244///
245/// Ref:
246/// - `FutuOpenD/Src/APIServer/APIServer_Inner_API.cpp:900-913`
247/// - `FutuOpenD/Src/APIServer/Business/Quote/QotSubscribe.cpp:1282-1286`
248pub fn order_book_requires_backend_full_depth(
249    info: &CachedSecurityInfo,
250    qr: &QotRightData,
251) -> bool {
252    orderbook_entitlement_facts_impl(info, qr).requires_backend_full_depth
253}
254
255pub fn order_book_uses_backend_side_count(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
256    orderbook_entitlement_facts_impl(info, qr).requires_backend_full_depth
257}
258
259/// C++ `GetOrderBook` read path skips `GetOrderBookMaxNum` clipping for HK SF
260/// and US TotalView Lv2.
261///
262/// Ref:
263/// - `FutuOpenD/Src/APIServer/Business/Quote/APIServer_Qot_OrderBook.cpp:86-89`
264/// - `FutuOpenD/Src/APIServer/APIServer_Inner_API.cpp:900-929`
265pub fn order_book_read_uses_requested_count(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
266    orderbook_entitlement_facts_impl(info, qr).read_uses_requested_count
267}
268
269/// C++ `GetOrderBook` 对 US TotalView Lv2 / Crypto orderbook 还有一层
270/// `IsHadAcceptUSLv2TotalViewData` gate: 普通 orderbook cache 存在不等于
271/// LV2 真盘口已经到达。
272///
273/// Ref:
274/// - `FutuOpenD/Src/APIServer/Business/Quote/APIServer_Qot_OrderBook.cpp:93-99`
275/// - `FutuOpenD/Src/APIServer/Business/Quote/QotRealTimeData.cpp:576-579`
276pub fn order_book_requires_accepted_lv2_push(info: &CachedSecurityInfo, qr: &QotRightData) -> bool {
277    orderbook_entitlement_facts_impl(info, qr).requires_accepted_lv2_push
278}
279
280pub fn lv2_order_uses_sg_odd_lot_cache(info: &CachedSecurityInfo, lv2_type: i32) -> bool {
281    is_sg_security_market(info) && lv2_type == SG_LV2_ORDER_STOCK_ODD_LOT as i32
282}
283
284pub fn lv2_order_uses_jp_main_cache(info: &CachedSecurityInfo, lv2_type: i32) -> bool {
285    is_jp_security_market(info)
286        && matches!(
287            lv2_type,
288            value if value == JP_LV2_ORDER_STOCK as i32
289                || value == JP_LV2_ORDER_STOCK_FULL as i32
290        )
291}