1#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct Security {
7 pub market: QotMarket,
9 pub code: String,
11}
12
13impl Security {
14 pub fn new(market: QotMarket, code: impl Into<String>) -> Self {
16 Self {
17 market,
18 code: code.into(),
19 }
20 }
21
22 pub fn from_proto(s: &futu_proto::qot_common::Security) -> Self {
24 Self {
25 market: QotMarket::from_i32(s.market),
26 code: s.code.clone(),
27 }
28 }
29
30 pub fn to_proto(&self) -> futu_proto::qot_common::Security {
32 futu_proto::qot_common::Security {
33 market: self.market as i32,
34 code: self.code.clone(),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[repr(i32)]
42#[non_exhaustive]
43pub enum QotMarket {
44 Unknown = 0,
45 HkSecurity = 1,
46 HkFuture = 2,
47 UsSecurity = 11,
48 CnshSecurity = 21,
49 CnszSecurity = 22,
50 SgSecurity = 31,
51 JpSecurity = 41,
52 AuSecurity = 51,
53 MySecurity = 61,
54 CaSecurity = 71,
55 FxSecurity = 81,
56 Crypto = 91,
60}
61
62impl QotMarket {
63 pub fn from_i32(v: i32) -> Self {
65 match v {
66 1 => Self::HkSecurity,
67 2 => Self::HkFuture,
68 11 => Self::UsSecurity,
69 21 => Self::CnshSecurity,
70 22 => Self::CnszSecurity,
71 31 => Self::SgSecurity,
72 41 => Self::JpSecurity,
73 51 => Self::AuSecurity,
74 61 => Self::MySecurity,
75 71 => Self::CaSecurity,
76 81 => Self::FxSecurity,
77 91 => Self::Crypto,
78 _ => Self::Unknown,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85#[repr(i32)]
86#[non_exhaustive]
87pub enum SubType {
88 None = 0,
89 Basic = 1,
90 OrderBook = 2,
91 OrderBookOdd = 22,
92 Ticker = 4,
93 RT = 5,
94 KLDay = 6,
95 KL5Min = 7,
96 KL15Min = 8,
97 KL30Min = 9,
98 KL60Min = 10,
99 KL1Min = 11,
100 KLWeek = 12,
101 KLMonth = 13,
102 Broker = 14,
103 KLQuarter = 15,
104 KLYear = 16,
105 KL3Min = 17,
106 KL10Min = 18,
107 KL120Min = 19,
108 KL180Min = 20,
109 KL240Min = 21,
110 #[deprecated(
118 since = "1.4.113",
119 note = "OrderDetail is not a Qot_Common.SubType; use the order-detail API surface"
120 )]
121 OrderDetail = -3016,
122}
123
124impl SubType {
125 pub const VALID_PUBLIC: &'static [Self] = &[
130 Self::Basic,
131 Self::OrderBook,
132 Self::Ticker,
133 Self::RT,
134 Self::KLDay,
135 Self::KL5Min,
136 Self::KL15Min,
137 Self::KL30Min,
138 Self::KL60Min,
139 Self::KL1Min,
140 Self::KLWeek,
141 Self::KLMonth,
142 Self::Broker,
143 Self::KLQuarter,
144 Self::KLYear,
145 Self::KL3Min,
146 Self::KL10Min,
147 Self::KL120Min,
148 Self::KL180Min,
149 Self::KL240Min,
150 Self::OrderBookOdd,
151 ];
152
153 pub const VALID_PUBLIC_INT_VALUES: &'static [i32] = &[
154 Self::Basic as i32,
155 Self::OrderBook as i32,
156 Self::Ticker as i32,
157 Self::RT as i32,
158 Self::KLDay as i32,
159 Self::KL5Min as i32,
160 Self::KL15Min as i32,
161 Self::KL30Min as i32,
162 Self::KL60Min as i32,
163 Self::KL1Min as i32,
164 Self::KLWeek as i32,
165 Self::KLMonth as i32,
166 Self::Broker as i32,
167 Self::KLQuarter as i32,
168 Self::KLYear as i32,
169 Self::KL3Min as i32,
170 Self::KL10Min as i32,
171 Self::KL120Min as i32,
172 Self::KL180Min as i32,
173 Self::KL240Min as i32,
174 Self::OrderBookOdd as i32,
175 ];
176
177 pub const fn as_i32(self) -> i32 {
178 self as i32
179 }
180
181 pub const fn from_i32(v: i32) -> Option<Self> {
182 Some(match v {
183 0 => Self::None,
184 1 => Self::Basic,
185 2 => Self::OrderBook,
186 4 => Self::Ticker,
187 5 => Self::RT,
188 6 => Self::KLDay,
189 7 => Self::KL5Min,
190 8 => Self::KL15Min,
191 9 => Self::KL30Min,
192 10 => Self::KL60Min,
193 11 => Self::KL1Min,
194 12 => Self::KLWeek,
195 13 => Self::KLMonth,
196 14 => Self::Broker,
197 15 => Self::KLQuarter,
198 16 => Self::KLYear,
199 17 => Self::KL3Min,
200 18 => Self::KL10Min,
201 19 => Self::KL120Min,
202 20 => Self::KL180Min,
203 21 => Self::KL240Min,
204 22 => Self::OrderBookOdd,
205 _ => return None,
206 })
207 }
208
209 pub const fn from_public_i32(v: i32) -> Option<Self> {
210 match Self::from_i32(v) {
211 Some(Self::None) | None => None,
212 Some(sub_type) => Some(sub_type),
213 }
214 }
215
216 pub fn from_str_alias(s: &str) -> Option<Self> {
217 Some(match s.trim().to_ascii_lowercase().as_str() {
218 "basic" => Self::Basic,
219 "orderbook" | "order_book" => Self::OrderBook,
220 "orderbookodd" | "orderbook_odd" | "order-book-odd" | "order_book_odd"
221 | "odd_orderbook" | "odd-lot-orderbook" | "odd_lot_orderbook" => Self::OrderBookOdd,
222 "ticker" => Self::Ticker,
223 "rt" => Self::RT,
224 "kl_day" | "kl-day" | "day" => Self::KLDay,
225 "kl_1min" | "kl-1min" | "1min" => Self::KL1Min,
226 "kl_3min" | "kl-3min" | "3min" => Self::KL3Min,
227 "kl_5min" | "kl-5min" | "5min" => Self::KL5Min,
228 "kl_10min" | "kl-10min" | "10min" => Self::KL10Min,
229 "kl_15min" | "kl-15min" | "15min" => Self::KL15Min,
230 "kl_30min" | "kl-30min" | "30min" => Self::KL30Min,
231 "kl_60min" | "kl-60min" | "60min" => Self::KL60Min,
232 "kl_120min" | "kl-120min" | "120min" => Self::KL120Min,
233 "kl_180min" | "kl-180min" | "180min" => Self::KL180Min,
234 "kl_240min" | "kl-240min" | "240min" => Self::KL240Min,
235 "kl_week" | "kl-week" | "week" => Self::KLWeek,
236 "kl_month" | "kl-month" | "month" => Self::KLMonth,
237 "kl_quarter" | "kl-quarter" | "quarter" => Self::KLQuarter,
238 "kl_year" | "kl-year" | "year" => Self::KLYear,
239 "broker" => Self::Broker,
240 _ => return None,
241 })
242 }
243
244 pub const fn is_kline(self) -> bool {
245 matches!(
246 self,
247 Self::KLDay
248 | Self::KL5Min
249 | Self::KL15Min
250 | Self::KL30Min
251 | Self::KL60Min
252 | Self::KL1Min
253 | Self::KLWeek
254 | Self::KLMonth
255 | Self::KLQuarter
256 | Self::KLYear
257 | Self::KL3Min
258 | Self::KL10Min
259 | Self::KL120Min
260 | Self::KL180Min
261 | Self::KL240Min
262 )
263 }
264
265 pub const fn kl_type(self) -> Option<KLType> {
266 Some(match self {
267 Self::KL1Min => KLType::Min1,
268 Self::KLDay => KLType::Day,
269 Self::KLWeek => KLType::Week,
270 Self::KLMonth => KLType::Month,
271 Self::KLYear => KLType::Year,
272 Self::KL5Min => KLType::Min5,
273 Self::KL15Min => KLType::Min15,
274 Self::KL30Min => KLType::Min30,
275 Self::KL60Min => KLType::Min60,
276 Self::KL3Min => KLType::Min3,
277 Self::KLQuarter => KLType::Quarter,
278 Self::KL10Min => KLType::Min10,
279 Self::KL120Min => KLType::Min120,
280 Self::KL180Min => KLType::Min180,
281 Self::KL240Min => KLType::Min240,
282 _ => return None,
283 })
284 }
285
286 pub const fn short_label(self) -> Option<&'static str> {
287 Some(match self {
288 Self::Basic => "basic",
289 Self::OrderBook => "orderbook",
290 Self::OrderBookOdd => "orderbook_odd",
291 Self::Ticker => "ticker",
292 Self::RT => "rt",
293 Self::KLDay => "kl_day",
294 Self::KL1Min => "kl_1min",
295 Self::KL3Min => "kl_3min",
296 Self::KL5Min => "kl_5min",
297 Self::KL10Min => "kl_10min",
298 Self::KL15Min => "kl_15min",
299 Self::KL30Min => "kl_30min",
300 Self::KL60Min => "kl_60min",
301 Self::KL120Min => "kl_120min",
302 Self::KL180Min => "kl_180min",
303 Self::KL240Min => "kl_240min",
304 Self::KLWeek => "kl_week",
305 Self::KLMonth => "kl_month",
306 Self::KLQuarter => "kl_quarter",
307 Self::KLYear => "kl_year",
308 Self::Broker => "broker",
309 _ => return None,
310 })
311 }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
316#[repr(i32)]
317#[non_exhaustive]
318pub enum KLType {
319 Unknown = 0,
320 Min1 = 1,
321 Day = 2,
322 Week = 3,
323 Month = 4,
324 Year = 5,
325 Min5 = 6,
326 Min15 = 7,
327 Min30 = 8,
328 Min60 = 9,
329 Min3 = 10,
330 Quarter = 11,
331 Min10 = 12,
332 Min120 = 13,
333 Min180 = 14,
334 Min240 = 15,
335}
336
337impl KLType {
338 pub const VALID_PUBLIC: &'static [Self] = &[
339 Self::Min1,
340 Self::Day,
341 Self::Week,
342 Self::Month,
343 Self::Year,
344 Self::Min5,
345 Self::Min15,
346 Self::Min30,
347 Self::Min60,
348 Self::Min3,
349 Self::Quarter,
350 Self::Min10,
351 Self::Min120,
352 Self::Min180,
353 Self::Min240,
354 ];
355
356 pub const fn as_i32(self) -> i32 {
357 self as i32
358 }
359
360 pub const fn from_i32(v: i32) -> Option<Self> {
361 Some(match v {
362 0 => Self::Unknown,
363 1 => Self::Min1,
364 2 => Self::Day,
365 3 => Self::Week,
366 4 => Self::Month,
367 5 => Self::Year,
368 6 => Self::Min5,
369 7 => Self::Min15,
370 8 => Self::Min30,
371 9 => Self::Min60,
372 10 => Self::Min3,
373 11 => Self::Quarter,
374 12 => Self::Min10,
375 13 => Self::Min120,
376 14 => Self::Min180,
377 15 => Self::Min240,
378 _ => return None,
379 })
380 }
381
382 pub const fn from_public_i32(v: i32) -> Option<Self> {
383 match Self::from_i32(v) {
384 Some(Self::Unknown) | None => None,
385 Some(kl_type) => Some(kl_type),
386 }
387 }
388
389 pub fn from_str_alias(s: &str) -> Option<Self> {
390 Some(match s.trim().to_ascii_lowercase().as_str() {
391 "day" => Self::Day,
392 "week" => Self::Week,
393 "month" => Self::Month,
394 "quarter" => Self::Quarter,
395 "year" => Self::Year,
396 "1min" => Self::Min1,
397 "3min" => Self::Min3,
398 "5min" => Self::Min5,
399 "10min" => Self::Min10,
400 "15min" => Self::Min15,
401 "30min" => Self::Min30,
402 "60min" => Self::Min60,
403 "120min" => Self::Min120,
404 "180min" => Self::Min180,
405 "240min" => Self::Min240,
406 _ => return None,
407 })
408 }
409
410 pub const fn is_minute(self) -> bool {
411 matches!(
412 self,
413 Self::Min1
414 | Self::Min3
415 | Self::Min5
416 | Self::Min10
417 | Self::Min15
418 | Self::Min30
419 | Self::Min60
420 | Self::Min120
421 | Self::Min180
422 | Self::Min240
423 )
424 }
425
426 pub const fn sub_type(self) -> Option<SubType> {
427 Some(match self {
428 Self::Min1 => SubType::KL1Min,
429 Self::Day => SubType::KLDay,
430 Self::Week => SubType::KLWeek,
431 Self::Month => SubType::KLMonth,
432 Self::Year => SubType::KLYear,
433 Self::Min5 => SubType::KL5Min,
434 Self::Min15 => SubType::KL15Min,
435 Self::Min30 => SubType::KL30Min,
436 Self::Min60 => SubType::KL60Min,
437 Self::Min3 => SubType::KL3Min,
438 Self::Quarter => SubType::KLQuarter,
439 Self::Min10 => SubType::KL10Min,
440 Self::Min120 => SubType::KL120Min,
441 Self::Min180 => SubType::KL180Min,
442 Self::Min240 => SubType::KL240Min,
443 Self::Unknown => return None,
444 })
445 }
446
447 pub const fn backend_kline_type(self) -> Option<u32> {
452 Some(match self {
453 Self::Min1 => 1,
454 Self::Day => 2,
455 Self::Week => 3,
456 Self::Month => 4,
457 Self::Year => 5,
458 Self::Min5 => 6,
459 Self::Min15 => 7,
460 Self::Min30 => 8,
461 Self::Min60 => 9,
462 Self::Min3 => 10,
463 Self::Quarter => 11,
464 Self::Min10 => 26,
465 Self::Min120 => 14,
466 Self::Min180 => 29,
467 Self::Min240 => 15,
468 Self::Unknown => return None,
469 })
470 }
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq)]
475#[repr(i32)]
476#[non_exhaustive]
477pub enum RehabType {
478 None = 0,
479 Forward = 1,
480 Backward = 2,
481}
482
483#[derive(Debug, Clone)]
485pub struct KLine {
486 pub time: String,
488 pub is_blank: bool,
490 pub high_price: f64,
492 pub open_price: f64,
494 pub low_price: f64,
496 pub close_price: f64,
498 pub last_close_price: f64,
500 pub volume: i64,
502 pub turnover: f64,
504 pub turnover_rate: f64,
506 pub pe: f64,
508 pub change_rate: f64,
510 pub timestamp: f64,
512}
513
514impl KLine {
515 pub fn from_proto(k: &futu_proto::qot_common::KLine) -> Self {
516 Self {
517 time: k.time.clone(),
518 is_blank: k.is_blank,
519 high_price: k.high_price.unwrap_or(0.0),
520 open_price: k.open_price.unwrap_or(0.0),
521 low_price: k.low_price.unwrap_or(0.0),
522 close_price: k.close_price.unwrap_or(0.0),
523 last_close_price: k.last_close_price.unwrap_or(0.0),
524 volume: k.volume.unwrap_or(0),
525 turnover: k.turnover.unwrap_or(0.0),
526 turnover_rate: k.turnover_rate.unwrap_or(0.0),
527 pe: k.pe.unwrap_or(0.0),
528 change_rate: k.change_rate.unwrap_or(0.0),
529 timestamp: k.timestamp.unwrap_or(0.0),
530 }
531 }
532}
533
534#[derive(Debug, Clone)]
536pub struct BasicQot {
537 pub security: Security,
539 pub is_suspended: bool,
541 pub list_time: String,
543 pub price_spread: f64,
545 pub update_time: String,
547 pub high_price: f64,
549 pub open_price: f64,
551 pub low_price: f64,
553 pub cur_price: f64,
555 pub last_close_price: f64,
557 pub volume: i64,
559 pub turnover: f64,
561 pub turnover_rate: f64,
563 pub amplitude: f64,
565}
566
567impl BasicQot {
568 pub fn from_proto(q: &futu_proto::qot_common::BasicQot) -> Self {
569 Self {
570 security: Security::from_proto(&q.security),
571 is_suspended: q.is_suspended,
572 list_time: q.list_time.clone(),
573 price_spread: q.price_spread,
574 update_time: q.update_time.clone(),
575 high_price: q.high_price,
576 open_price: q.open_price,
577 low_price: q.low_price,
578 cur_price: q.cur_price,
579 last_close_price: q.last_close_price,
580 volume: q.volume,
581 turnover: q.turnover,
582 turnover_rate: q.turnover_rate,
583 amplitude: q.amplitude,
584 }
585 }
586}
587
588#[derive(Debug, Clone)]
590pub struct OrderBookEntry {
591 pub price: f64,
593 pub volume: i64,
595 pub order_count: i32,
597}
598
599impl OrderBookEntry {
600 pub fn from_proto(ob: &futu_proto::qot_common::OrderBook) -> Self {
601 Self {
602 price: ob.price,
603 volume: ob.volume,
604 order_count: ob.oreder_count, }
606 }
607}
608
609#[derive(Debug, Clone)]
611pub struct OrderBookData {
612 pub security: Security,
614 pub ask_list: Vec<OrderBookEntry>,
616 pub bid_list: Vec<OrderBookEntry>,
618}
619
620pub fn market_state_label(s: i32) -> &'static str {
629 match s {
630 0 => "None", 1 => "Auction", 2 => "WaitingOpen", 3 => "Morning", 4 => "Rest", 5 => "Afternoon", 6 => "Closed", 8 => "PreMarketBegin", 9 => "PreMarketEnd", 10 => "AfterHoursBegin", 11 => "AfterHoursEnd", 12 => "FutuSwitchDate", 13 => "NightOpen", 14 => "NightEnd", 15 => "FutureDayOpen", 16 => "FutureDayBreak", 17 => "FutureDayClose", 18 => "FutureDayWaitForOpen", 19 => "HkCas", 20 => "FutureNightWait", 21 => "FutureAfternoon", 22 => "FutureSwitchDate", 23 => "FutureOpen", 24 => "FutureBreak", 25 => "FutureBreakOver", 26 => "FutureClose", 27 => "StibAfterHoursWait", 28 => "StibAfterHoursBegin", 29 => "StibAfterHoursEnd", 30 => "CloseAuction", 31 => "AfternoonEnd", 32 => "Night", 33 => "OvernightBegin", 34 => "OvernightEnd", 35 => "TradeAtLast", 36 => "TradeAuction", 37 => "Overnight", _ => "Unknown",
668 }
669}
670
671#[cfg(test)]
672mod tests;