1use prost::Message;
2
3use crate::proto_internal::ft_cmd_stock_quote_sub_data;
4
5use super::{sbit, sub_type};
6
7pub fn sub_type_to_bits(sub_type: i32) -> Vec<(u32, i64)> {
10 match sub_type {
11 sub_type::BASIC => vec![
12 (sbit::PRICE, 0),
13 (sbit::STOCK_STATE, 0),
14 (sbit::STOCK_TYPE_SPECIFIC, 0),
15 (sbit::DEAL_STATISTICS, 0),
16 ],
17 sub_type::ORDER_BOOK => vec![(sbit::ORDER_BOOK, 0)],
18 sub_type::TICKER => vec![(sbit::TICK, 1)], sub_type::RT => vec![(sbit::STOCK_STATE, 0), (sbit::TIME_SHARING, 0)],
20 sub_type::BROKER => vec![(sbit::HK_BROKER_QUEUE, 0)],
21 sub_type::KL_1MIN => vec![(sbit::KLINE_1MIN, 1)], sub_type::KL_3MIN => vec![(sbit::KLINE_3MIN, 1)],
23 sub_type::KL_5MIN => vec![(sbit::KLINE_5MIN, 1)],
24 sub_type::KL_15MIN => vec![(sbit::KLINE_15MIN, 1)],
25 sub_type::KL_30MIN => vec![(sbit::KLINE_30MIN, 1)],
26 sub_type::KL_60MIN => vec![(sbit::KLINE_60MIN, 1)],
27 sub_type::KL_DAY => vec![(sbit::KLINE_DAY, 1)],
28 sub_type::KL_WEEK => vec![(sbit::KLINE_WEEK, 1)],
29 sub_type::KL_MONTH => vec![(sbit::KLINE_MONTH, 1)],
30 sub_type::KL_QUARTER => vec![(sbit::KLINE_QUARTER, 1)],
31 sub_type::KL_YEAR => vec![(sbit::KLINE_YEAR, 1)],
32 _ => vec![],
33 }
34}
35
36#[derive(Debug, Clone, Copy, Default)]
41pub struct SubBitOptions {
42 pub session: i32,
45 pub orderbook_detail: bool,
47 pub orderbook_full_depth: bool,
51 pub broker_detail: bool,
53 pub us_pre_after_detail: bool,
56 pub extended_time: bool,
59 pub merged_lv2_order_types: u32,
62 pub merged_lv2_order_types_v2: u32,
65 pub merged_lv2_order_types_v2_level40: u32,
68 pub backend_market: i32,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct SubscribeBitInfo {
79 pub bit: u32,
80 pub prob: i64,
81 pub prob2: Option<String>,
82 pub prob2_v2: Option<Vec<u8>>,
83}
84
85const ORDER_BOOK_40_PROB: i64 = 0;
87const ORDER_BOOK_ALL_PROB: i64 = 1;
88const ORDER_BOOK_ALL_WITH_DETAIL_PROB: i64 = 2;
89const ORDER_BOOK_SIMPLE_LV2_PROB: i64 = 8 | 16;
90const ORDER_BOOK_SGX_STOCK_PROB: i64 = 1;
91const ORDER_BOOK_SGX_STOCK_ODD_LOT_PROB: i64 = 2;
92const ORDER_BOOK_MYX_STOCK_PROB: i64 = 1;
93const ORDER_BOOK_MYX_STOCK_ODD_LOT_PROB: i64 = 16;
94
95fn sg_my_order_book_prob(backend_market: i32) -> Option<i64> {
96 match backend_market {
97 15 => Some(ORDER_BOOK_SGX_STOCK_PROB | ORDER_BOOK_SGX_STOCK_ODD_LOT_PROB),
102 27 => Some(ORDER_BOOK_MYX_STOCK_PROB | ORDER_BOOK_MYX_STOCK_ODD_LOT_PROB),
103 _ => None,
104 }
105}
106
107fn encode_us_lv2_order_sub_prob(lv2_type: u32, level: u32) -> Vec<u8> {
108 ft_cmd_stock_quote_sub_data::Uslv2OrderSubProb {
109 sub_items: vec![ft_cmd_stock_quote_sub_data::Uslv2OrderSubItem {
110 us_lv2_order_type: Some(lv2_type),
111 us_lv2_order_level: Some(level),
112 }],
113 }
114 .encode_to_vec()
115}
116
117fn for_each_type_mask_bit(mut mask: u32, mut f: impl FnMut(u32)) {
118 while mask != 0 {
119 let bit = mask & (!mask + 1);
120 f(bit);
121 mask &= !bit;
122 }
123}
124
125fn for_each_us_lv2_prob2_type(mask: u32, mut f: impl FnMut(u32)) {
126 for known in [
130 futu_cache::qot_right::US_LV2_ORDER_NASDAQ_TV,
131 futu_cache::qot_right::US_LV2_ORDER_ARCA,
132 ] {
133 if mask & known != 0 {
134 f(known);
135 }
136 }
137 let remaining = mask
138 & !(futu_cache::qot_right::US_LV2_ORDER_NASDAQ_TV
139 | futu_cache::qot_right::US_LV2_ORDER_ARCA);
140 for_each_type_mask_bit(remaining, f);
141}
142
143pub fn sub_type_to_bits_with_options(sub_type: i32, opts: SubBitOptions) -> Vec<(u32, i64)> {
148 sub_type_to_bit_infos_with_options(sub_type, opts)
149 .into_iter()
150 .map(|info| (info.bit, info.prob))
151 .collect()
152}
153
154pub fn sub_type_to_bit_infos_with_options(
155 sub_type: i32,
156 opts: SubBitOptions,
157) -> Vec<SubscribeBitInfo> {
158 let ticker_session_prob = match opts.session {
166 2 => 1 | 2 | 4,
167 3 => 1 | 2 | 4 | 8,
168 _ => 1,
169 };
170 let kline_session_prob = match opts.session {
174 2 => 2,
175 3 => 3,
176 _ => 1,
177 };
178 match sub_type {
179 sub_type::BASIC => {
180 let mut infos = vec![
181 SubscribeBitInfo::new(sbit::PRICE, 0),
182 SubscribeBitInfo::new(sbit::STOCK_STATE, 0),
183 SubscribeBitInfo::new(sbit::STOCK_TYPE_SPECIFIC, 0),
184 SubscribeBitInfo::new(sbit::DEAL_STATISTICS, 0),
185 ];
186 if opts.us_pre_after_detail {
187 infos.push(SubscribeBitInfo::new(sbit::US_PREMARKET_AFTERHOURS, 0));
190 }
191 infos
192 }
193 sub_type::ORDER_BOOK => {
194 let sg_my_prob = sg_my_order_book_prob(opts.backend_market);
195 let bit = if sg_my_prob.is_some() {
196 sbit::ORDER_BOOK
197 } else if opts.orderbook_detail {
198 sbit::ORDER_BOOK_DETAIL
199 } else {
200 sbit::ORDER_BOOK
201 };
202 let mut infos = Vec::new();
203 for_each_type_mask_bit(opts.merged_lv2_order_types_v2_level40, |lv2_type| {
204 infos.push(SubscribeBitInfo {
205 bit: sbit::MEGER_LV2_ORDER,
206 prob: 0,
207 prob2: None,
208 prob2_v2: Some(encode_us_lv2_order_sub_prob(lv2_type, 40)),
209 });
210 });
211 for_each_type_mask_bit(opts.merged_lv2_order_types_v2, |lv2_type| {
212 infos.push(SubscribeBitInfo {
213 bit: sbit::MEGER_LV2_ORDER,
214 prob: 0,
215 prob2: None,
216 prob2_v2: Some(encode_us_lv2_order_sub_prob(lv2_type, 60)),
217 });
218 });
219 for_each_us_lv2_prob2_type(opts.merged_lv2_order_types, |lv2_type| {
220 let bytes = encode_us_lv2_order_sub_prob(lv2_type, 60);
221 let (prob2, prob2_v2) = encode_legacy_or_bytes_prob2(bytes);
222 infos.push(SubscribeBitInfo {
223 bit: sbit::MEGER_LV2_ORDER,
224 prob: 0,
225 prob2,
226 prob2_v2,
227 });
228 });
229 let prob = if let Some(prob) = sg_my_prob {
230 prob
231 } else if opts.orderbook_full_depth {
232 if opts.orderbook_detail {
233 ORDER_BOOK_ALL_WITH_DETAIL_PROB
234 } else {
235 ORDER_BOOK_ALL_PROB
236 }
237 } else if opts.merged_lv2_order_types != 0
238 || opts.merged_lv2_order_types_v2 != 0
239 || opts.merged_lv2_order_types_v2_level40 != 0
240 {
241 ORDER_BOOK_SIMPLE_LV2_PROB
242 } else {
243 ORDER_BOOK_40_PROB
244 };
245 infos.push(SubscribeBitInfo::new(bit, prob));
246 infos
247 }
248 sub_type::ORDER_BOOK_ODD => {
249 let prob = sg_my_order_book_prob(opts.backend_market).unwrap_or(0);
250 vec![SubscribeBitInfo::new(sbit::ORDER_BOOK, prob)]
251 }
252 sub_type::TICKER => vec![SubscribeBitInfo::new(sbit::TICK, ticker_session_prob)],
253 sub_type::RT => vec![
254 SubscribeBitInfo::new(sbit::STOCK_STATE, 0),
255 SubscribeBitInfo::new(sbit::TIME_SHARING, 0),
256 ],
257 sub_type::BROKER => {
258 let bit = if opts.broker_detail {
259 sbit::HK_BROKER_DETAIL
260 } else {
261 sbit::HK_BROKER_QUEUE
262 };
263 vec![SubscribeBitInfo::new(bit, 0)]
264 }
265 sub_type::KL_1MIN => vec![SubscribeBitInfo::new(sbit::KLINE_1MIN, kline_session_prob)],
266 sub_type::KL_3MIN => vec![SubscribeBitInfo::new(sbit::KLINE_3MIN, kline_session_prob)],
267 sub_type::KL_5MIN => vec![SubscribeBitInfo::new(sbit::KLINE_5MIN, kline_session_prob)],
268 sub_type::KL_15MIN => vec![SubscribeBitInfo::new(sbit::KLINE_15MIN, kline_session_prob)],
269 sub_type::KL_30MIN => vec![SubscribeBitInfo::new(sbit::KLINE_30MIN, kline_session_prob)],
270 sub_type::KL_60MIN => vec![SubscribeBitInfo::new(sbit::KLINE_60MIN, kline_session_prob)],
271 sub_type::KL_DAY => vec![SubscribeBitInfo::new(sbit::KLINE_DAY, kline_session_prob)],
272 sub_type::KL_WEEK => vec![SubscribeBitInfo::new(sbit::KLINE_WEEK, kline_session_prob)],
273 sub_type::KL_MONTH => vec![SubscribeBitInfo::new(sbit::KLINE_MONTH, kline_session_prob)],
274 sub_type::KL_QUARTER => vec![SubscribeBitInfo::new(
275 sbit::KLINE_QUARTER,
276 kline_session_prob,
277 )],
278 sub_type::KL_YEAR => vec![SubscribeBitInfo::new(sbit::KLINE_YEAR, kline_session_prob)],
279 _ => vec![],
280 }
281}
282
283impl SubscribeBitInfo {
284 pub(crate) fn new(bit: u32, prob: i64) -> Self {
285 Self {
286 bit,
287 prob,
288 prob2: None,
289 prob2_v2: None,
290 }
291 }
292}
293
294fn encode_legacy_or_bytes_prob2(bytes: Vec<u8>) -> (Option<String>, Option<Vec<u8>>) {
295 match String::from_utf8(bytes) {
296 Ok(prob2) => (Some(prob2), None),
297 Err(err) => (None, Some(err.into_bytes())),
298 }
299}