1use std::num::NonZeroU32;
2
3use crate::qot_stock_key::QotSecurityKey;
4
5pub const SECURITY_TYPE_DRVT: i32 = 8;
6pub const SECURITY_TYPE_FUTURE: i32 = 10;
7
8pub const BACKEND_MARKET_HK_OPTION: i32 = 9;
9pub const BACKEND_MARKET_US_OPTION: i32 = 15;
10
11pub const US_LV2_ORDER_ARCA: u32 = 1;
12pub const US_LV2_ORDER_NASDAQ_TV: u32 = 4;
13pub const US_LV2_ORDER_OVERNIGHT: u32 = 128;
14pub const LV2_ORDER_US_FUTURE: u32 = 1;
15pub const SG_LV2_ORDER_STOCK: u32 = 1;
16pub const SG_LV2_ORDER_STOCK_ODD_LOT: u32 = 2;
17pub const JP_LV2_ORDER_STOCK: u32 = 1;
18pub const JP_LV2_ORDER_STOCK_FULL: u32 = 2;
19
20pub const SUB_TYPE_NONE: i32 = 0;
21pub const SUB_TYPE_BASIC: i32 = 1;
22pub const SUB_TYPE_ORDER_BOOK: i32 = 2;
23pub const SUB_TYPE_TICKER: i32 = 4;
24pub const SUB_TYPE_RT: i32 = 5;
25pub const SUB_TYPE_KL_DAY: i32 = 6;
26pub const SUB_TYPE_KL_5MIN: i32 = 7;
27pub const SUB_TYPE_KL_15MIN: i32 = 8;
28pub const SUB_TYPE_KL_30MIN: i32 = 9;
29pub const SUB_TYPE_KL_60MIN: i32 = 10;
30pub const SUB_TYPE_KL_1MIN: i32 = 11;
31pub const SUB_TYPE_KL_WEEK: i32 = 12;
32pub const SUB_TYPE_KL_MONTH: i32 = 13;
33pub const SUB_TYPE_BROKER: i32 = 14;
34pub const SUB_TYPE_KL_QUARTER: i32 = 15;
35pub const SUB_TYPE_KL_YEAR: i32 = 16;
36pub const SUB_TYPE_KL_3MIN: i32 = 17;
37pub const SUB_TYPE_KL_10MIN: i32 = 18;
38pub const SUB_TYPE_KL_120MIN: i32 = 19;
39pub const SUB_TYPE_KL_180MIN: i32 = 20;
40pub const SUB_TYPE_KL_240MIN: i32 = 21;
41pub const SUB_TYPE_ORDER_BOOK_ODD: i32 = 22;
42
43pub const VALID_QOT_SUB_TYPES: &[i32] = &[
44 SUB_TYPE_BASIC,
45 SUB_TYPE_ORDER_BOOK,
46 SUB_TYPE_TICKER,
47 SUB_TYPE_RT,
48 SUB_TYPE_KL_DAY,
49 SUB_TYPE_KL_5MIN,
50 SUB_TYPE_KL_15MIN,
51 SUB_TYPE_KL_30MIN,
52 SUB_TYPE_KL_60MIN,
53 SUB_TYPE_KL_1MIN,
54 SUB_TYPE_KL_WEEK,
55 SUB_TYPE_KL_MONTH,
56 SUB_TYPE_BROKER,
57 SUB_TYPE_KL_QUARTER,
58 SUB_TYPE_KL_YEAR,
59 SUB_TYPE_KL_3MIN,
60 SUB_TYPE_KL_10MIN,
61 SUB_TYPE_KL_120MIN,
62 SUB_TYPE_KL_180MIN,
63 SUB_TYPE_KL_240MIN,
64 SUB_TYPE_ORDER_BOOK_ODD,
65];
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum EventContractWireRight {
69 Unknown,
70 Level1,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct Lv2OrderGroupPlan {
75 pub lv2_type: u32,
76 pub level: u32,
77 pub prob2_v2: bool,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum OrderBookWireProfile {
82 Generic,
83 Hk { sf: bool },
84 UsStock { lv2_groups: Vec<Lv2OrderGroupPlan> },
85 UsFuture { level2: bool },
86 JpStock { depth: Option<u32> },
87 SgStock { level2: bool },
88 MyStock,
89 Crypto { dynamic_prob: Option<u32> },
90 EventContract { right: EventContractWireRight },
91}
92
93#[derive(Debug, Clone, Default, PartialEq, Eq)]
94pub enum SubscriptionWireProfile {
95 #[default]
96 NonOrderBook,
97 OrderBook(OrderBookWireProfile),
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct BitInfoPlan {
102 pub bit: u32,
103 pub prob: Option<i64>,
104 pub payload: BitInfoPayloadPlan,
105}
106
107impl BitInfoPlan {
108 #[must_use]
109 pub const fn scalar(bit: u32, prob: Option<i64>) -> Self {
110 Self {
111 bit,
112 prob,
113 payload: BitInfoPayloadPlan::None,
114 }
115 }
116
117 #[must_use]
118 pub fn lv2(
119 bit: u32,
120 encoding: Lv2OrderEncoding,
121 first: Lv2OrderItemPlan,
122 additional: impl IntoIterator<Item = Lv2OrderItemPlan>,
123 ) -> Self {
124 let items = std::iter::once(first).chain(additional).collect();
125 Self {
126 bit,
127 prob: None,
128 payload: BitInfoPayloadPlan::Lv2Order { encoding, items },
129 }
130 }
131}
132
133#[derive(Clone, Debug, PartialEq, Eq)]
134pub enum BitInfoPayloadPlan {
135 None,
136 Lv2Order {
137 encoding: Lv2OrderEncoding,
138 items: Vec<Lv2OrderItemPlan>,
139 },
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum Lv2OrderEncoding {
144 LegacyProb2,
145 BytesV2,
146}
147
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149pub struct Lv2OrderItemPlan {
150 pub lv2_type: u32,
151 pub level: u32,
152}
153
154#[derive(Debug, Clone, Default, PartialEq, Eq)]
155pub struct SubBitOptions {
156 pub session: i32,
157 pub kline_us_market: bool,
161 pub orderbook_detail: bool,
162 pub orderbook_full_depth: bool,
163 pub broker_detail: bool,
164 pub us_pre_after_detail: bool,
165 pub extended_time: bool,
166 pub wire_profile: SubscriptionWireProfile,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct SecurityWithOpts {
172 pub stock_id: u64,
173 pub ftapi_market: i32,
174 pub sub_types_with_opts: Vec<(i32, SubBitOptions)>,
175 pub broker_id: Option<NonZeroU32>,
176}
177
178impl SecurityWithOpts {
179 #[must_use]
180 pub fn new(
181 stock_id: u64,
182 ftapi_market: i32,
183 sub_types_with_opts: Vec<(i32, SubBitOptions)>,
184 ) -> Self {
185 Self {
186 stock_id,
187 ftapi_market,
188 sub_types_with_opts,
189 broker_id: None,
190 }
191 }
192
193 #[must_use]
194 pub fn with_broker(
195 stock_id: u64,
196 ftapi_market: i32,
197 sub_types_with_opts: Vec<(i32, SubBitOptions)>,
198 broker_id: u32,
199 ) -> Self {
200 Self {
201 stock_id,
202 ftapi_market,
203 sub_types_with_opts,
204 broker_id: NonZeroU32::new(broker_id),
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
210pub struct EmptyDesiredMarket {
211 pub mkt_type: u8,
212 pub is_depth: bool,
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub struct SubscriptionSecurityFacts {
217 pub stock_id: u64,
218 pub sec_type: i32,
219 pub mkt_id: u32,
220 pub market: i32,
221}
222
223#[must_use]
224pub fn is_kl_sub_type(sub_type: i32) -> bool {
225 matches!(
226 sub_type,
227 SUB_TYPE_KL_DAY
228 | SUB_TYPE_KL_5MIN
229 | SUB_TYPE_KL_15MIN
230 | SUB_TYPE_KL_30MIN
231 | SUB_TYPE_KL_60MIN
232 | SUB_TYPE_KL_1MIN
233 | SUB_TYPE_KL_WEEK
234 | SUB_TYPE_KL_MONTH
235 | SUB_TYPE_KL_QUARTER
236 | SUB_TYPE_KL_YEAR
237 | SUB_TYPE_KL_3MIN
238 | SUB_TYPE_KL_10MIN
239 | SUB_TYPE_KL_120MIN
240 | SUB_TYPE_KL_180MIN
241 | SUB_TYPE_KL_240MIN
242 )
243}
244
245#[must_use]
246pub fn is_valid_sub_type(sub_type: i32) -> bool {
247 VALID_QOT_SUB_TYPES.contains(&sub_type)
248}
249
250#[must_use]
251pub fn qot_sub_type_from_str_alias(s: &str) -> Option<i32> {
252 Some(match s.trim().to_ascii_lowercase().as_str() {
253 "basic" => SUB_TYPE_BASIC,
254 "orderbook" | "order_book" => SUB_TYPE_ORDER_BOOK,
255 "orderbookodd" | "orderbook_odd" | "order-book-odd" | "order_book_odd"
256 | "odd_orderbook" | "odd-lot-orderbook" | "odd_lot_orderbook" => SUB_TYPE_ORDER_BOOK_ODD,
257 "ticker" => SUB_TYPE_TICKER,
258 "rt" => SUB_TYPE_RT,
259 "kl_day" | "kl-day" | "day" => SUB_TYPE_KL_DAY,
260 "kl_1min" | "kl-1min" | "1min" => SUB_TYPE_KL_1MIN,
261 "kl_3min" | "kl-3min" | "3min" => SUB_TYPE_KL_3MIN,
262 "kl_5min" | "kl-5min" | "5min" => SUB_TYPE_KL_5MIN,
263 "kl_10min" | "kl-10min" | "10min" => SUB_TYPE_KL_10MIN,
264 "kl_15min" | "kl-15min" | "15min" => SUB_TYPE_KL_15MIN,
265 "kl_30min" | "kl-30min" | "30min" => SUB_TYPE_KL_30MIN,
266 "kl_60min" | "kl-60min" | "60min" => SUB_TYPE_KL_60MIN,
267 "kl_120min" | "kl-120min" | "120min" => SUB_TYPE_KL_120MIN,
268 "kl_180min" | "kl-180min" | "180min" => SUB_TYPE_KL_180MIN,
269 "kl_240min" | "kl-240min" | "240min" => SUB_TYPE_KL_240MIN,
270 "kl_week" | "kl-week" | "week" => SUB_TYPE_KL_WEEK,
271 "kl_month" | "kl-month" | "month" => SUB_TYPE_KL_MONTH,
272 "kl_quarter" | "kl-quarter" | "quarter" => SUB_TYPE_KL_QUARTER,
273 "kl_year" | "kl-year" | "year" => SUB_TYPE_KL_YEAR,
274 "broker" => SUB_TYPE_BROKER,
275 _ => return None,
276 })
277}
278
279#[must_use]
280pub const fn qot_sub_type_short_label(sub_type: i32) -> Option<&'static str> {
281 Some(match sub_type {
282 SUB_TYPE_BASIC => "basic",
283 SUB_TYPE_ORDER_BOOK => "orderbook",
284 SUB_TYPE_ORDER_BOOK_ODD => "orderbook_odd",
285 SUB_TYPE_TICKER => "ticker",
286 SUB_TYPE_RT => "rt",
287 SUB_TYPE_KL_DAY => "kl_day",
288 SUB_TYPE_KL_1MIN => "kl_1min",
289 SUB_TYPE_KL_3MIN => "kl_3min",
290 SUB_TYPE_KL_5MIN => "kl_5min",
291 SUB_TYPE_KL_10MIN => "kl_10min",
292 SUB_TYPE_KL_15MIN => "kl_15min",
293 SUB_TYPE_KL_30MIN => "kl_30min",
294 SUB_TYPE_KL_60MIN => "kl_60min",
295 SUB_TYPE_KL_120MIN => "kl_120min",
296 SUB_TYPE_KL_180MIN => "kl_180min",
297 SUB_TYPE_KL_240MIN => "kl_240min",
298 SUB_TYPE_KL_WEEK => "kl_week",
299 SUB_TYPE_KL_MONTH => "kl_month",
300 SUB_TYPE_KL_QUARTER => "kl_quarter",
301 SUB_TYPE_KL_YEAR => "kl_year",
302 SUB_TYPE_BROKER => "broker",
303 _ => return None,
304 })
305}
306
307#[must_use]
308pub fn qot_kl_type_from_str_alias(s: &str) -> Option<i32> {
309 Some(match s.trim().to_ascii_lowercase().as_str() {
310 "day" => 2,
311 "week" => 3,
312 "month" => 4,
313 "quarter" => 11,
314 "year" => 5,
315 "1min" => 1,
316 "3min" => 10,
317 "5min" => 6,
318 "10min" => 12,
319 "15min" => 7,
320 "30min" => 8,
321 "60min" => 9,
322 "120min" => 13,
323 "180min" => 14,
324 "240min" => 15,
325 _ => return None,
326 })
327}
328
329#[must_use]
334pub const fn qot_kl_type_backend_kline_type(kl_type: i32) -> Option<u32> {
335 Some(match kl_type {
336 1 => 1,
337 2 => 2,
338 3 => 3,
339 4 => 4,
340 5 => 5,
341 6 => 6,
342 7 => 7,
343 8 => 8,
344 9 => 9,
345 10 => 10,
346 11 => 11,
347 12 => 26,
348 13 => 14,
349 14 => 29,
350 15 => 15,
351 _ => return None,
352 })
353}
354
355#[must_use]
356pub fn unsupported_option_sub_type_name(sub_type: i32) -> Option<&'static str> {
357 match sub_type {
358 SUB_TYPE_NONE => Some("None"),
359 SUB_TYPE_KL_30MIN => Some("KL_30Min"),
360 SUB_TYPE_KL_WEEK => Some("KL_Week"),
361 SUB_TYPE_KL_MONTH => Some("KL_Month"),
362 SUB_TYPE_KL_QUARTER => Some("KL_Quarter"),
363 SUB_TYPE_KL_YEAR => Some("KL_Year"),
364 SUB_TYPE_KL_3MIN => Some("KL_3Min"),
365 _ => None,
366 }
367}
368
369#[must_use]
370pub const fn kl_type_for_sub_type(sub_type: i32) -> Option<i32> {
371 Some(match sub_type {
372 SUB_TYPE_KL_1MIN => 1,
373 SUB_TYPE_KL_DAY => 2,
374 SUB_TYPE_KL_WEEK => 3,
375 SUB_TYPE_KL_MONTH => 4,
376 SUB_TYPE_KL_YEAR => 5,
377 SUB_TYPE_KL_5MIN => 6,
378 SUB_TYPE_KL_15MIN => 7,
379 SUB_TYPE_KL_30MIN => 8,
380 SUB_TYPE_KL_60MIN => 9,
381 SUB_TYPE_KL_3MIN => 10,
382 SUB_TYPE_KL_QUARTER => 11,
383 SUB_TYPE_KL_10MIN => 12,
384 SUB_TYPE_KL_120MIN => 13,
385 SUB_TYPE_KL_180MIN => 14,
386 SUB_TYPE_KL_240MIN => 15,
387 _ => return None,
388 })
389}
390
391#[must_use]
392pub const fn sub_type_for_kl_type(kl_type: i32) -> Option<i32> {
393 Some(match kl_type {
394 1 => SUB_TYPE_KL_1MIN,
395 2 => SUB_TYPE_KL_DAY,
396 3 => SUB_TYPE_KL_WEEK,
397 4 => SUB_TYPE_KL_MONTH,
398 5 => SUB_TYPE_KL_YEAR,
399 6 => SUB_TYPE_KL_5MIN,
400 7 => SUB_TYPE_KL_15MIN,
401 8 => SUB_TYPE_KL_30MIN,
402 9 => SUB_TYPE_KL_60MIN,
403 10 => SUB_TYPE_KL_3MIN,
404 11 => SUB_TYPE_KL_QUARTER,
405 12 => SUB_TYPE_KL_10MIN,
406 13 => SUB_TYPE_KL_120MIN,
407 14 => SUB_TYPE_KL_180MIN,
408 15 => SUB_TYPE_KL_240MIN,
409 _ => return None,
410 })
411}
412
413#[must_use]
414pub const fn is_minute_kl_type(kl_type: i32) -> bool {
415 matches!(kl_type, 1 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15)
416}
417
418#[must_use]
419pub fn is_valid_qot_market(market: i32) -> bool {
420 matches!(
421 market,
422 1 | 11 | 21 | 22 | 31 | 41 | 51 | 61 | 71 | 81 | 91 | 101
423 )
424}
425
426#[must_use]
427pub fn canonical_public_qot_market(market: i32) -> Option<i32> {
428 if market == 2 {
433 return Some(1);
434 }
435 if is_valid_qot_market(market) {
436 return Some(market);
437 }
438
439 match market {
440 3 => Some(21),
441 4 => Some(1),
442 5 => Some(1),
443 6 => Some(31),
444 7 => Some(91),
445 8 => Some(51),
446 15 => Some(41),
447 111 => Some(61),
448 112 => Some(71),
449 _ => None,
450 }
451}
452
453#[must_use]
457pub const fn legacy_trd_market_to_qot_market(market: i32) -> Option<i32> {
458 match market {
459 2 => Some(11),
460 3 => Some(21),
461 4 | 5 => Some(1),
462 6 => Some(31),
463 7 => Some(91),
464 8 => Some(51),
465 15 => Some(41),
466 111 => Some(61),
467 112 => Some(71),
468 _ => None,
469 }
470}
471
472#[must_use]
473pub fn ftapi_market_to_quote_mkt(market: i32) -> u8 {
474 match market {
475 1 => 1,
476 11 => 2,
477 21 => 3,
478 22 => 4,
479 5 => 5,
480 6 => 6,
481 9 => 9,
482 13 => 13,
483 31 => 15,
484 15 => 7,
485 14 => 8,
486 16 => 16,
487 41 => 25,
488 61 => 27,
489 23 => 10,
490 91 => 17,
491 101 => 35,
493 _ => 0,
494 }
495}
496
497#[must_use]
498pub fn is_depth_sub_type(sub_type: i32) -> bool {
499 matches!(
500 sub_type,
501 SUB_TYPE_ORDER_BOOK | SUB_TYPE_ORDER_BOOK_ODD | SUB_TYPE_BROKER
502 )
503}
504
505#[must_use]
506pub fn empty_desired_market_for_sub(
507 ftapi_market: i32,
508 sub_type: i32,
509) -> Option<EmptyDesiredMarket> {
510 let mkt_type = ftapi_market_to_quote_mkt(ftapi_market);
511 if mkt_type == 0 {
512 return None;
513 }
514 Some(EmptyDesiredMarket {
515 mkt_type,
516 is_depth: is_depth_sub_type(sub_type),
517 })
518}
519
520#[must_use]
521pub fn backend_subscribe_market_for_info(
522 requested_market: i32,
523 info: &SubscriptionSecurityFacts,
524) -> i32 {
525 backend_subscribe_market_for_facts(requested_market, info.sec_type, info.mkt_id)
526}
527
528#[must_use]
529pub fn backend_subscribe_market_for_facts(
530 requested_market: i32,
531 sec_type: i32,
532 mkt_id: u32,
533) -> i32 {
534 backend_subscribe_market_for_security(requested_market, sec_type, mkt_id)
535}
536
537#[must_use]
538pub fn backend_desired_key_for_info(
539 sec_key: &str,
540 info: &SubscriptionSecurityFacts,
541) -> Option<(u64, i32)> {
542 backend_desired_key_for_sec_key(sec_key, info.stock_id, info.sec_type, info.mkt_id)
543}
544
545#[must_use]
546pub fn backend_subscribe_market_for_security(
547 requested_market: i32,
548 sec_type: i32,
549 mkt_id: u32,
550) -> i32 {
551 if sec_type == SECURITY_TYPE_DRVT {
552 return match mkt_id {
553 7 | 8 | 570..=579 => BACKEND_MARKET_HK_OPTION,
554 41..=45 => BACKEND_MARKET_US_OPTION,
555 _ => match requested_market {
556 1 => BACKEND_MARKET_HK_OPTION,
557 11 => BACKEND_MARKET_US_OPTION,
558 _ => requested_market,
559 },
560 };
561 }
562
563 if sec_type != SECURITY_TYPE_FUTURE {
564 return requested_market;
565 }
566
567 match mkt_id {
568 5 => 5,
569 6 | 110..=119 => 6,
570 60..=109 => 14,
571 160..=179 => 13,
572 185..=194 => 16,
573 1400..=1449 => requested_market,
574 _ => match requested_market {
575 1 | 2 => 6,
576 11 => 14,
577 31 => 13,
578 41 => 16,
579 _ => requested_market,
580 },
581 }
582}
583
584#[must_use]
585pub fn public_market_from_sec_key(sec_key: &str) -> Option<i32> {
586 QotSecurityKey::parse_public_sec_key(sec_key).map(|key| key.market)
587}
588
589#[must_use]
590pub fn backend_desired_key_for_sec_key(
591 sec_key: &str,
592 stock_id: u64,
593 sec_type: i32,
594 mkt_id: u32,
595) -> Option<(u64, i32)> {
596 if stock_id == 0 {
597 return None;
598 }
599 let public_market = public_market_from_sec_key(sec_key)?;
600 Some((
601 stock_id,
602 backend_subscribe_market_for_security(public_market, sec_type, mkt_id),
603 ))
604}