1use longbridge_candlesticks::CandlestickComponents;
2use longbridge_proto::quote::{self, Period, TradeStatus};
3use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use strum_macros::{Display, EnumString};
7use time::{Date, OffsetDateTime, Time};
8
9use crate::{
10 Error, Market, Result,
11 quote::{SubFlags, utils::parse_date},
12 serde_utils,
13};
14
15#[derive(Debug, Default, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
17pub enum TradeSession {
18 #[default]
20 Intraday,
21 Pre,
23 Post,
25 Overnight,
27}
28
29impl longbridge_candlesticks::TradeSessionType for TradeSession {
30 #[inline]
31 fn kind(&self) -> longbridge_candlesticks::TradeSessionKind {
32 match self {
33 TradeSession::Intraday => longbridge_candlesticks::TRADE_SESSION_INTRADAY,
34 TradeSession::Pre => longbridge_candlesticks::TRADE_SESSION_PRE,
35 TradeSession::Post => longbridge_candlesticks::TRADE_SESSION_POST,
36 TradeSession::Overnight => longbridge_candlesticks::TRADE_SESSION_OVERNIGHT,
37 }
38 }
39}
40
41impl From<longbridge_proto::quote::TradeSession> for TradeSession {
42 #[inline]
43 fn from(value: longbridge_proto::quote::TradeSession) -> Self {
44 match value {
45 longbridge_proto::quote::TradeSession::NormalTrade => Self::Intraday,
46 longbridge_proto::quote::TradeSession::PreTrade => Self::Pre,
47 longbridge_proto::quote::TradeSession::PostTrade => Self::Post,
48 longbridge_proto::quote::TradeSession::OvernightTrade => Self::Overnight,
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct Subscription {
56 pub symbol: String,
58 pub sub_types: SubFlags,
60 pub candlesticks: Vec<Period>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct Depth {
67 pub position: i32,
69 pub price: Option<Decimal>,
71 pub volume: i64,
73 pub order_num: i64,
75}
76
77impl TryFrom<quote::Depth> for Depth {
78 type Error = Error;
79
80 fn try_from(depth: quote::Depth) -> Result<Self> {
81 Ok(Self {
82 position: depth.position,
83 price: depth.price.parse().ok(),
84 volume: depth.volume,
85 order_num: depth.order_num,
86 })
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct Brokers {
93 pub position: i32,
95 pub broker_ids: Vec<i32>,
97}
98
99impl From<quote::Brokers> for Brokers {
100 fn from(brokers: quote::Brokers) -> Self {
101 Self {
102 position: brokers.position,
103 broker_ids: brokers.broker_ids,
104 }
105 }
106}
107
108#[derive(Debug, FromPrimitive, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
110#[repr(i32)]
111pub enum TradeDirection {
112 #[num_enum(default)]
114 Neutral = 0,
115 Down = 1,
117 Up = 2,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct Trade {
124 pub price: Decimal,
126 pub volume: i64,
128 #[serde(with = "time::serde::rfc3339")]
130 pub timestamp: OffsetDateTime,
131 pub trade_type: String,
164 pub direction: TradeDirection,
166 pub trade_session: TradeSession,
168}
169
170impl TryFrom<quote::Trade> for Trade {
171 type Error = Error;
172
173 fn try_from(trade: quote::Trade) -> Result<Self> {
174 Ok(Self {
175 price: trade.price.parse().unwrap_or_default(),
176 volume: trade.volume,
177 timestamp: OffsetDateTime::from_unix_timestamp(trade.timestamp)
178 .map_err(|err| Error::parse_field_error("timestamp", err))?,
179 trade_type: trade.trade_type,
180 direction: trade.direction.into(),
181 trade_session: longbridge_proto::quote::TradeSession::try_from(trade.trade_session)
182 .unwrap_or_default()
183 .into(),
184 })
185 }
186}
187
188impl longbridge_candlesticks::TradeType for Trade {
189 type PriceType = Decimal;
190 type VolumeType = i64;
191 type TurnoverType = Decimal;
192 type TradeSessionType = TradeSession;
193
194 #[inline]
195 fn time(&self) -> OffsetDateTime {
196 self.timestamp
197 }
198
199 #[inline]
200 fn price(&self) -> Self::PriceType {
201 self.price
202 }
203
204 #[inline]
205 fn volume(&self) -> Self::VolumeType {
206 self.volume
207 }
208
209 #[inline]
210 fn turnover(&self, lot_size: i32) -> Self::TurnoverType {
211 self.price * Decimal::from(self.volume * lot_size as i64)
212 }
213
214 #[inline]
215 fn trade_session(&self) -> TradeSession {
216 self.trade_session
217 }
218}
219
220bitflags::bitflags! {
221 #[derive(Debug, Copy, Clone, Serialize,Deserialize)]
223 pub struct DerivativeType: u8 {
224 const OPTION = 0x1;
226
227 const WARRANT = 0x2;
229 }
230}
231
232#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display, Serialize, Deserialize)]
234#[allow(clippy::upper_case_acronyms)]
235pub enum SecurityBoard {
236 Unknown,
238 USMain,
240 USPink,
242 USDJI,
244 USNSDQ,
246 USSector,
248 USOption,
250 USOptionS,
252 HKEquity,
254 HKPreIPO,
256 HKWarrant,
258 HKHS,
260 HKSector,
262 SHMainConnect,
264 SHMainNonConnect,
266 SHSTAR,
268 CNIX,
270 CNSector,
272 SZMainConnect,
274 SZMainNonConnect,
276 SZGEMConnect,
278 SZGEMNonConnect,
280 SGMain,
282 STI,
284 SGSector,
286 SPXIndex,
288 VIXIndex,
290}
291
292#[derive(Debug, Serialize, Deserialize)]
294pub struct SecurityStaticInfo {
295 pub symbol: String,
297 pub name_cn: String,
299 pub name_en: String,
301 pub name_hk: String,
303 pub exchange: String,
305 pub currency: String,
307 pub lot_size: i32,
309 pub total_shares: i64,
311 pub circulating_shares: i64,
313 pub hk_shares: i64,
315 pub eps: Decimal,
317 pub eps_ttm: Decimal,
319 pub bps: Decimal,
321 pub dividend_yield: Decimal,
323 pub stock_derivatives: DerivativeType,
325 pub board: SecurityBoard,
327}
328
329impl TryFrom<quote::StaticInfo> for SecurityStaticInfo {
330 type Error = Error;
331
332 fn try_from(resp: quote::StaticInfo) -> Result<Self> {
333 Ok(SecurityStaticInfo {
334 symbol: resp.symbol,
335 name_cn: resp.name_cn,
336 name_en: resp.name_en,
337 name_hk: resp.name_hk,
338 exchange: resp.exchange,
339 currency: resp.currency,
340 lot_size: resp.lot_size,
341 total_shares: resp.total_shares,
342 circulating_shares: resp.circulating_shares,
343 hk_shares: resp.hk_shares,
344 eps: resp.eps.parse().unwrap_or_default(),
345 eps_ttm: resp.eps_ttm.parse().unwrap_or_default(),
346 bps: resp.bps.parse().unwrap_or_default(),
347 dividend_yield: resp.dividend_yield.parse().unwrap_or_default(),
348 stock_derivatives: resp.stock_derivatives.into_iter().fold(
349 DerivativeType::empty(),
350 |acc, value| match value {
351 1 => acc | DerivativeType::OPTION,
352 2 => acc | DerivativeType::WARRANT,
353 _ => acc,
354 },
355 ),
356 board: resp.board.parse().unwrap_or(SecurityBoard::Unknown),
357 })
358 }
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct RealtimeQuote {
364 pub symbol: String,
366 pub last_done: Decimal,
368 pub open: Decimal,
370 pub high: Decimal,
372 pub low: Decimal,
374 pub timestamp: OffsetDateTime,
376 pub volume: i64,
378 pub turnover: Decimal,
380 pub trade_status: TradeStatus,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct PrePostQuote {
387 pub last_done: Decimal,
389 #[serde(with = "time::serde::rfc3339")]
391 pub timestamp: OffsetDateTime,
392 pub volume: i64,
394 pub turnover: Decimal,
396 pub high: Decimal,
398 pub low: Decimal,
400 pub prev_close: Decimal,
402}
403
404impl TryFrom<quote::PrePostQuote> for PrePostQuote {
405 type Error = Error;
406
407 fn try_from(quote: quote::PrePostQuote) -> Result<Self> {
408 Ok(Self {
409 last_done: quote.last_done.parse().unwrap_or_default(),
410 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
411 .map_err(|err| Error::parse_field_error("timestamp", err))?,
412 volume: quote.volume,
413 turnover: quote.turnover.parse().unwrap_or_default(),
414 high: quote.high.parse().unwrap_or_default(),
415 low: quote.low.parse().unwrap_or_default(),
416 prev_close: quote.prev_close.parse().unwrap_or_default(),
417 })
418 }
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct SecurityQuote {
424 pub symbol: String,
426 pub last_done: Decimal,
428 pub prev_close: Decimal,
430 pub open: Decimal,
432 pub high: Decimal,
434 pub low: Decimal,
436 #[serde(with = "time::serde::rfc3339")]
438 pub timestamp: OffsetDateTime,
439 pub volume: i64,
441 pub turnover: Decimal,
443 pub trade_status: TradeStatus,
445 pub pre_market_quote: Option<PrePostQuote>,
447 pub post_market_quote: Option<PrePostQuote>,
449 pub overnight_quote: Option<PrePostQuote>,
451}
452
453impl TryFrom<quote::SecurityQuote> for SecurityQuote {
454 type Error = Error;
455
456 fn try_from(quote: quote::SecurityQuote) -> Result<Self> {
457 Ok(Self {
458 symbol: quote.symbol,
459 last_done: quote.last_done.parse().unwrap_or_default(),
460 prev_close: quote.prev_close.parse().unwrap_or_default(),
461 open: quote.open.parse().unwrap_or_default(),
462 high: quote.high.parse().unwrap_or_default(),
463 low: quote.low.parse().unwrap_or_default(),
464 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
465 .map_err(|err| Error::parse_field_error("timestamp", err))?,
466 volume: quote.volume,
467 turnover: quote.turnover.parse().unwrap_or_default(),
468 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
469 pre_market_quote: quote.pre_market_quote.map(TryInto::try_into).transpose()?,
470 post_market_quote: quote.post_market_quote.map(TryInto::try_into).transpose()?,
471 overnight_quote: quote.over_night_quote.map(TryInto::try_into).transpose()?,
472 })
473 }
474}
475
476#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
478pub enum OptionType {
479 Unknown,
481 #[strum(serialize = "A")]
483 American,
484 #[strum(serialize = "U")]
486 Europe,
487}
488
489#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
491pub enum OptionDirection {
492 Unknown,
494 #[strum(serialize = "P")]
496 Put,
497 #[strum(serialize = "C")]
499 Call,
500}
501
502#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
504pub enum OptionExpiryCycleType {
505 Unknown,
507 #[strum(serialize = "")]
509 Monthly,
510 #[strum(serialize = "W")]
512 Weekly,
513 #[strum(serialize = "Q")]
515 Quarterly,
516}
517
518#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
521pub enum OptionStandardAttr {
522 Unknown,
524 #[strum(serialize = "")]
526 Normal,
527 #[strum(serialize = "old")]
529 Old,
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
534pub struct OptionQuote {
535 pub symbol: String,
537 pub last_done: Decimal,
539 pub prev_close: Decimal,
541 pub open: Decimal,
543 pub high: Decimal,
545 pub low: Decimal,
547 #[serde(with = "time::serde::rfc3339")]
549 pub timestamp: OffsetDateTime,
550 pub volume: i64,
552 pub turnover: Decimal,
554 pub trade_status: TradeStatus,
556 pub implied_volatility: Decimal,
558 pub open_interest: i64,
560 pub expiry_date: Date,
562 pub strike_price: Decimal,
564 pub contract_multiplier: Decimal,
566 pub contract_type: OptionType,
568 pub contract_size: Decimal,
570 pub direction: OptionDirection,
572 pub historical_volatility: Decimal,
574 pub underlying_symbol: String,
576}
577
578impl TryFrom<quote::OptionQuote> for OptionQuote {
579 type Error = Error;
580
581 fn try_from(quote: quote::OptionQuote) -> Result<Self> {
582 let option_extend = quote.option_extend.unwrap_or_default();
583
584 Ok(Self {
585 symbol: quote.symbol,
586 last_done: quote.last_done.parse().unwrap_or_default(),
587 prev_close: quote.prev_close.parse().unwrap_or_default(),
588 open: quote.open.parse().unwrap_or_default(),
589 high: quote.high.parse().unwrap_or_default(),
590 low: quote.low.parse().unwrap_or_default(),
591 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
592 .map_err(|err| Error::parse_field_error("timestamp", err))?,
593 volume: quote.volume,
594 turnover: quote.turnover.parse().unwrap_or_default(),
595 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
596 implied_volatility: option_extend.implied_volatility.parse().unwrap_or_default(),
597 open_interest: option_extend.open_interest,
598 expiry_date: parse_date(&option_extend.expiry_date)
599 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
600 strike_price: option_extend.strike_price.parse().unwrap_or_default(),
601 contract_multiplier: option_extend
602 .contract_multiplier
603 .parse()
604 .unwrap_or_default(),
605 contract_type: option_extend.contract_type.parse().unwrap_or_default(),
606 contract_size: option_extend.contract_size.parse().unwrap_or_default(),
607 direction: option_extend.direction.parse().unwrap_or_default(),
608 historical_volatility: option_extend
609 .historical_volatility
610 .parse()
611 .unwrap_or_default(),
612 underlying_symbol: option_extend.underlying_symbol,
613 })
614 }
615}
616
617#[derive(
619 Debug,
620 Copy,
621 Clone,
622 Hash,
623 Eq,
624 PartialEq,
625 EnumString,
626 IntoPrimitive,
627 TryFromPrimitive,
628 Serialize,
629 Deserialize,
630)]
631#[repr(i32)]
632pub enum WarrantType {
633 Unknown = -1,
635 Call = 0,
637 Put = 1,
639 Bull = 2,
641 Bear = 3,
643 Inline = 4,
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize)]
649pub struct WarrantQuote {
650 pub symbol: String,
652 pub last_done: Decimal,
654 pub prev_close: Decimal,
656 pub open: Decimal,
658 pub high: Decimal,
660 pub low: Decimal,
662 #[serde(with = "time::serde::rfc3339")]
664 pub timestamp: OffsetDateTime,
665 pub volume: i64,
667 pub turnover: Decimal,
669 pub trade_status: TradeStatus,
671 pub implied_volatility: Decimal,
673 pub expiry_date: Date,
675 pub last_trade_date: Date,
677 pub outstanding_ratio: Decimal,
679 pub outstanding_quantity: i64,
681 pub conversion_ratio: Decimal,
683 pub category: WarrantType,
685 pub strike_price: Decimal,
687 pub upper_strike_price: Decimal,
689 pub lower_strike_price: Decimal,
691 pub call_price: Decimal,
693 pub underlying_symbol: String,
695}
696
697impl TryFrom<quote::WarrantQuote> for WarrantQuote {
698 type Error = Error;
699
700 fn try_from(quote: quote::WarrantQuote) -> Result<Self> {
701 let warrant_extend = quote.warrant_extend.unwrap_or_default();
702
703 Ok(Self {
704 symbol: quote.symbol,
705 last_done: quote.last_done.parse().unwrap_or_default(),
706 prev_close: quote.prev_close.parse().unwrap_or_default(),
707 open: quote.open.parse().unwrap_or_default(),
708 high: quote.high.parse().unwrap_or_default(),
709 low: quote.low.parse().unwrap_or_default(),
710 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
711 .map_err(|err| Error::parse_field_error("timestamp", err))?,
712 volume: quote.volume,
713 turnover: quote.turnover.parse().unwrap_or_default(),
714 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
715 implied_volatility: warrant_extend
716 .implied_volatility
717 .parse()
718 .unwrap_or_default(),
719 expiry_date: parse_date(&warrant_extend.expiry_date)
720 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
721 last_trade_date: parse_date(&warrant_extend.last_trade_date)
722 .map_err(|err| Error::parse_field_error("last_trade_date", err))?,
723 outstanding_ratio: warrant_extend.outstanding_ratio.parse().unwrap_or_default(),
724 outstanding_quantity: warrant_extend.outstanding_qty,
725 conversion_ratio: warrant_extend.conversion_ratio.parse().unwrap_or_default(),
726 category: warrant_extend.category.parse().unwrap_or_default(),
727 strike_price: warrant_extend.strike_price.parse().unwrap_or_default(),
728 upper_strike_price: warrant_extend
729 .upper_strike_price
730 .parse()
731 .unwrap_or_default(),
732 lower_strike_price: warrant_extend
733 .lower_strike_price
734 .parse()
735 .unwrap_or_default(),
736 call_price: warrant_extend.call_price.parse().unwrap_or_default(),
737 underlying_symbol: warrant_extend.underlying_symbol,
738 })
739 }
740}
741
742#[derive(Debug, Clone, Default, Serialize, Deserialize)]
744pub struct SecurityDepth {
745 pub asks: Vec<Depth>,
747 pub bids: Vec<Depth>,
749}
750
751#[derive(Debug, Clone, Default, Serialize, Deserialize)]
753pub struct SecurityBrokers {
754 pub ask_brokers: Vec<Brokers>,
756 pub bid_brokers: Vec<Brokers>,
758}
759
760#[derive(Debug, Clone, Serialize, Deserialize)]
762pub struct ParticipantInfo {
763 pub broker_ids: Vec<i32>,
765 pub name_cn: String,
767 pub name_en: String,
769 pub name_hk: String,
771}
772
773impl From<quote::ParticipantInfo> for ParticipantInfo {
774 fn from(info: quote::ParticipantInfo) -> Self {
775 Self {
776 broker_ids: info.broker_ids,
777 name_cn: info.participant_name_cn,
778 name_en: info.participant_name_en,
779 name_hk: info.participant_name_hk,
780 }
781 }
782}
783
784#[derive(Debug, Clone, Serialize, Deserialize)]
786pub struct IntradayLine {
787 pub price: Decimal,
789 #[serde(with = "time::serde::rfc3339")]
791 pub timestamp: OffsetDateTime,
792 pub volume: i64,
794 pub turnover: Decimal,
796 pub avg_price: Decimal,
798}
799
800impl TryFrom<quote::Line> for IntradayLine {
801 type Error = Error;
802
803 fn try_from(value: quote::Line) -> Result<Self> {
804 Ok(Self {
805 price: value.price.parse().unwrap_or_default(),
806 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
807 .map_err(|err| Error::parse_field_error("timestamp", err))?,
808 volume: value.volume,
809 turnover: value.turnover.parse().unwrap_or_default(),
810 avg_price: value.avg_price.parse().unwrap_or_default(),
811 })
812 }
813}
814
815#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
817pub struct Candlestick {
818 pub close: Decimal,
820 pub open: Decimal,
822 pub low: Decimal,
824 pub high: Decimal,
826 pub volume: i64,
828 pub turnover: Decimal,
830 #[serde(with = "time::serde::rfc3339")]
832 pub timestamp: OffsetDateTime,
833 pub trade_session: TradeSession,
835 open_updated: bool,
836}
837
838impl TryFrom<quote::Candlestick> for Candlestick {
839 type Error = Error;
840
841 fn try_from(value: quote::Candlestick) -> Result<Self> {
842 Ok(Self {
843 close: value.close.parse().unwrap_or_default(),
844 open: value.open.parse().unwrap_or_default(),
845 low: value.low.parse().unwrap_or_default(),
846 high: value.high.parse().unwrap_or_default(),
847 volume: value.volume,
848 turnover: value.turnover.parse().unwrap_or_default(),
849 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
850 .map_err(|err| Error::parse_field_error("timestamp", err))?,
851 trade_session: longbridge_proto::quote::TradeSession::try_from(value.trade_session)
852 .map_err(|err| Error::parse_field_error("trade_session", err))?
853 .into(),
854 open_updated: true,
855 })
856 }
857}
858
859impl longbridge_candlesticks::CandlestickType for Candlestick {
860 type PriceType = Decimal;
861 type VolumeType = i64;
862 type TurnoverType = Decimal;
863 type TradeSessionType = TradeSession;
864
865 #[inline]
866 fn new(
867 components: CandlestickComponents<
868 Self::PriceType,
869 Self::VolumeType,
870 Self::TurnoverType,
871 Self::TradeSessionType,
872 >,
873 ) -> Self {
874 Self {
875 timestamp: components.time,
876 open: components.open,
877 high: components.high,
878 low: components.low,
879 close: components.close,
880 volume: components.volume,
881 turnover: components.turnover,
882 trade_session: components.trade_session,
883 open_updated: components.open_updated,
884 }
885 }
886
887 #[inline]
888 fn time(&self) -> OffsetDateTime {
889 self.timestamp
890 }
891
892 #[inline]
893 fn set_time(&mut self, time: OffsetDateTime) {
894 self.timestamp = time;
895 }
896
897 #[inline]
898 fn open(&self) -> Self::PriceType {
899 self.open
900 }
901
902 #[inline]
903 fn set_open(&mut self, open: Self::PriceType) {
904 self.open = open;
905 }
906
907 #[inline]
908 fn high(&self) -> Self::PriceType {
909 self.high
910 }
911
912 #[inline]
913 fn set_high(&mut self, high: Self::PriceType) {
914 self.high = high;
915 }
916
917 #[inline]
918 fn low(&self) -> Self::PriceType {
919 self.low
920 }
921
922 #[inline]
923 fn set_low(&mut self, low: Self::PriceType) {
924 self.low = low;
925 }
926
927 #[inline]
928 fn close(&self) -> Self::PriceType {
929 self.close
930 }
931
932 #[inline]
933 fn set_close(&mut self, close: Self::PriceType) {
934 self.close = close;
935 }
936
937 #[inline]
938 fn volume(&self) -> Self::VolumeType {
939 self.volume
940 }
941
942 #[inline]
943 fn set_volume(&mut self, volume: Self::VolumeType) {
944 self.volume = volume;
945 }
946
947 #[inline]
948 fn turnover(&self) -> Self::TurnoverType {
949 self.turnover
950 }
951
952 #[inline]
953 fn set_turnover(&mut self, turnover: Self::TurnoverType) {
954 self.turnover = turnover;
955 }
956
957 #[inline]
958 fn trade_session(&self) -> Self::TradeSessionType {
959 self.trade_session
960 }
961
962 #[inline]
963 fn set_open_updated(&mut self, updated: bool) {
964 self.open_updated = updated;
965 }
966
967 #[inline]
968 fn open_updated(&self) -> bool {
969 self.open_updated
970 }
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize)]
978pub struct OptionChainContract {
979 pub symbol: String,
981 pub expiry_date: Date,
983 pub strike_price: Decimal,
985 pub direction: OptionDirection,
987 pub option_type: OptionExpiryCycleType,
989 pub standard_attr: OptionStandardAttr,
992 pub days_to_expiry: i32,
998}
999
1000#[derive(Debug, Clone, Serialize, Deserialize)]
1002pub struct IssuerInfo {
1003 pub issuer_id: i32,
1005 pub name_cn: String,
1007 pub name_en: String,
1009 pub name_hk: String,
1011}
1012
1013impl From<quote::IssuerInfo> for IssuerInfo {
1014 fn from(info: quote::IssuerInfo) -> Self {
1015 Self {
1016 issuer_id: info.id,
1017 name_cn: info.name_cn,
1018 name_en: info.name_en,
1019 name_hk: info.name_hk,
1020 }
1021 }
1022}
1023
1024#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1026#[repr(i32)]
1027pub enum SortOrderType {
1028 Ascending = 0,
1030 Descending = 1,
1032}
1033
1034#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1036#[repr(i32)]
1037pub enum WarrantSortBy {
1038 LastDone = 0,
1040 ChangeRate = 1,
1042 ChangeValue = 2,
1044 Volume = 3,
1046 Turnover = 4,
1048 ExpiryDate = 5,
1050 StrikePrice = 6,
1052 UpperStrikePrice = 7,
1054 LowerStrikePrice = 8,
1056 OutstandingQuantity = 9,
1058 OutstandingRatio = 10,
1060 Premium = 11,
1062 ItmOtm = 12,
1064 ImpliedVolatility = 13,
1066 Delta = 14,
1068 CallPrice = 15,
1070 ToCallPrice = 16,
1072 EffectiveLeverage = 17,
1074 LeverageRatio = 18,
1076 ConversionRatio = 19,
1078 BalancePoint = 20,
1080 Status = 21,
1082}
1083
1084#[allow(non_camel_case_types)]
1086#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1087#[repr(i32)]
1088pub enum FilterWarrantExpiryDate {
1089 LT_3 = 1,
1091 Between_3_6 = 2,
1093 Between_6_12 = 3,
1095 GT_12 = 4,
1097}
1098
1099#[allow(non_camel_case_types)]
1101#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1102#[repr(i32)]
1103pub enum FilterWarrantInOutBoundsType {
1104 In = 1,
1106 Out = 2,
1108}
1109
1110#[derive(
1112 Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive, TryFromPrimitive, Serialize, Deserialize,
1113)]
1114#[repr(i32)]
1115pub enum WarrantStatus {
1116 Unknown = 0,
1121 Suspend = 2,
1123 PrepareList = 3,
1125 Normal = 4,
1127}
1128
1129#[derive(Debug, Clone, Serialize, Deserialize)]
1131pub struct WarrantInfo {
1132 pub symbol: String,
1134 pub warrant_type: WarrantType,
1136 pub name: String,
1138 pub last_done: Decimal,
1140 pub change_rate: Decimal,
1142 pub change_value: Decimal,
1144 pub volume: i64,
1146 pub turnover: Decimal,
1148 pub expiry_date: Option<Date>,
1152 pub strike_price: Option<Decimal>,
1154 pub upper_strike_price: Option<Decimal>,
1156 pub lower_strike_price: Option<Decimal>,
1158 pub outstanding_qty: i64,
1160 pub outstanding_ratio: Decimal,
1162 pub premium: Decimal,
1164 pub itm_otm: Option<Decimal>,
1166 pub implied_volatility: Option<Decimal>,
1168 pub delta: Option<Decimal>,
1170 pub call_price: Option<Decimal>,
1172 pub to_call_price: Option<Decimal>,
1174 pub effective_leverage: Option<Decimal>,
1176 pub leverage_ratio: Decimal,
1178 pub conversion_ratio: Option<Decimal>,
1180 pub balance_point: Option<Decimal>,
1182 pub status: WarrantStatus,
1184}
1185
1186impl TryFrom<quote::FilterWarrant> for WarrantInfo {
1187 type Error = Error;
1188
1189 fn try_from(info: quote::FilterWarrant) -> Result<Self> {
1190 let r#type = WarrantType::try_from(info.r#type)
1191 .map_err(|err| Error::parse_field_error("type", err))?;
1192
1193 match r#type {
1194 WarrantType::Unknown => unreachable!(),
1195 WarrantType::Call | WarrantType::Put => Ok(Self {
1196 symbol: info.symbol,
1197 warrant_type: r#type,
1198 name: info.name,
1199 last_done: info.last_done.parse().unwrap_or_default(),
1200 change_rate: info.change_rate.parse().unwrap_or_default(),
1201 change_value: info.change_val.parse().unwrap_or_default(),
1202 volume: info.volume,
1203 turnover: info.turnover.parse().unwrap_or_default(),
1204 expiry_date: parse_date(&info.expiry_date).ok(),
1205 strike_price: info.strike_price.parse().ok(),
1206 upper_strike_price: info.upper_strike_price.parse().ok(),
1207 lower_strike_price: info.lower_strike_price.parse().ok(),
1208 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1209 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1210 premium: info.premium.parse().unwrap_or_default(),
1211 itm_otm: info.itm_otm.parse().ok(),
1212 implied_volatility: info.implied_volatility.parse().ok(),
1213 delta: info.delta.parse().ok(),
1214 call_price: info.call_price.parse().ok(),
1215 to_call_price: info.to_call_price.parse().ok(),
1216 effective_leverage: info.effective_leverage.parse().ok(),
1217 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1218 conversion_ratio: info.conversion_ratio.parse().ok(),
1219 balance_point: info.balance_point.parse().ok(),
1220 status: WarrantStatus::try_from(info.status).unwrap_or(WarrantStatus::Unknown),
1221 }),
1222 WarrantType::Bull | WarrantType::Bear => Ok(Self {
1223 symbol: info.symbol,
1224 warrant_type: r#type,
1225 name: info.name,
1226 last_done: info.last_done.parse().unwrap_or_default(),
1227 change_rate: info.change_rate.parse().unwrap_or_default(),
1228 change_value: info.change_val.parse().unwrap_or_default(),
1229 volume: info.volume,
1230 turnover: info.turnover.parse().unwrap_or_default(),
1231 expiry_date: parse_date(&info.expiry_date).ok(),
1232 strike_price: Some(info.strike_price.parse().unwrap_or_default()),
1233 upper_strike_price: None,
1234 lower_strike_price: None,
1235 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1236 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1237 premium: info.premium.parse().unwrap_or_default(),
1238 itm_otm: Some(info.itm_otm.parse().unwrap_or_default()),
1239 implied_volatility: None,
1240 delta: None,
1241 call_price: Some(info.call_price.parse().unwrap_or_default()),
1242 to_call_price: Some(info.to_call_price.parse().unwrap_or_default()),
1243 effective_leverage: None,
1244 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1245 conversion_ratio: Some(info.conversion_ratio.parse().unwrap_or_default()),
1246 balance_point: Some(info.balance_point.parse().unwrap_or_default()),
1247 status: WarrantStatus::try_from(info.status).unwrap_or(WarrantStatus::Unknown),
1248 }),
1249 WarrantType::Inline => Ok(Self {
1250 symbol: info.symbol,
1251 warrant_type: r#type,
1252 name: info.name,
1253 last_done: info.last_done.parse().unwrap_or_default(),
1254 change_rate: info.change_rate.parse().unwrap_or_default(),
1255 change_value: info.change_val.parse().unwrap_or_default(),
1256 volume: info.volume,
1257 turnover: info.turnover.parse().unwrap_or_default(),
1258 expiry_date: parse_date(&info.expiry_date).ok(),
1259 strike_price: None,
1260 upper_strike_price: Some(info.upper_strike_price.parse().unwrap_or_default()),
1261 lower_strike_price: Some(info.lower_strike_price.parse().unwrap_or_default()),
1262 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1263 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1264 premium: info.premium.parse().unwrap_or_default(),
1265 itm_otm: None,
1266 implied_volatility: None,
1267 delta: None,
1268 call_price: None,
1269 to_call_price: None,
1270 effective_leverage: None,
1271 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1272 conversion_ratio: None,
1273 balance_point: None,
1274 status: WarrantStatus::try_from(info.status).unwrap_or(WarrantStatus::Unknown),
1275 }),
1276 }
1277 }
1278}
1279
1280#[derive(Debug, Clone, Serialize, Deserialize)]
1282pub struct TradingSessionInfo {
1283 pub begin_time: Time,
1285 pub end_time: Time,
1287 pub trade_session: TradeSession,
1289}
1290
1291impl TryFrom<quote::TradePeriod> for TradingSessionInfo {
1292 type Error = Error;
1293
1294 fn try_from(value: quote::TradePeriod) -> Result<Self> {
1295 #[inline]
1296 fn parse_time(value: i32) -> ::std::result::Result<Time, time::error::ComponentRange> {
1297 Time::from_hms(((value / 100) % 100) as u8, (value % 100) as u8, 0)
1298 }
1299
1300 Ok(Self {
1301 begin_time: parse_time(value.beg_time)
1302 .map_err(|err| Error::parse_field_error("beg_time", err))?,
1303 end_time: parse_time(value.end_time)
1304 .map_err(|err| Error::parse_field_error("end_time", err))?,
1305 trade_session: longbridge_proto::quote::TradeSession::try_from(value.trade_session)
1306 .unwrap_or_default()
1307 .into(),
1308 })
1309 }
1310}
1311
1312#[derive(Debug, Clone, Serialize, Deserialize)]
1314pub struct MarketTradingSession {
1315 pub market: Market,
1317 pub trade_sessions: Vec<TradingSessionInfo>,
1319}
1320
1321impl TryFrom<quote::MarketTradePeriod> for MarketTradingSession {
1322 type Error = Error;
1323
1324 fn try_from(value: quote::MarketTradePeriod) -> Result<Self> {
1325 Ok(Self {
1326 market: value.market.parse().unwrap_or_default(),
1327 trade_sessions: value
1328 .trade_session
1329 .into_iter()
1330 .map(TryInto::try_into)
1331 .collect::<Result<Vec<_>>>()?,
1332 })
1333 }
1334}
1335
1336#[derive(Debug, Clone, Serialize, Deserialize)]
1338pub struct MarketTradingDays {
1339 pub trading_days: Vec<Date>,
1341 pub half_trading_days: Vec<Date>,
1343}
1344
1345#[derive(Debug, Clone, Serialize, Deserialize)]
1347pub struct CapitalFlowLine {
1348 pub inflow: Decimal,
1350 pub timestamp: OffsetDateTime,
1352}
1353
1354impl TryFrom<quote::capital_flow_intraday_response::CapitalFlowLine> for CapitalFlowLine {
1355 type Error = Error;
1356
1357 fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result<Self> {
1358 Ok(Self {
1359 inflow: value.inflow.parse().unwrap_or_default(),
1360 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1361 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1362 })
1363 }
1364}
1365
1366#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1368pub struct CapitalDistribution {
1369 pub large: Decimal,
1371 pub medium: Decimal,
1373 pub small: Decimal,
1375}
1376
1377impl TryFrom<quote::capital_distribution_response::CapitalDistribution> for CapitalDistribution {
1378 type Error = Error;
1379
1380 fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result<Self> {
1381 Ok(Self {
1382 large: value.large.parse().unwrap_or_default(),
1383 medium: value.medium.parse().unwrap_or_default(),
1384 small: value.small.parse().unwrap_or_default(),
1385 })
1386 }
1387}
1388
1389#[derive(Debug, Clone, Serialize, Deserialize)]
1391pub struct CapitalDistributionResponse {
1392 pub timestamp: OffsetDateTime,
1394 pub capital_in: CapitalDistribution,
1396 pub capital_out: CapitalDistribution,
1398}
1399
1400impl TryFrom<quote::CapitalDistributionResponse> for CapitalDistributionResponse {
1401 type Error = Error;
1402
1403 fn try_from(value: quote::CapitalDistributionResponse) -> Result<Self> {
1404 Ok(Self {
1405 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1406 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1407 capital_in: value
1408 .capital_in
1409 .map(TryInto::try_into)
1410 .transpose()?
1411 .unwrap_or_default(),
1412 capital_out: value
1413 .capital_out
1414 .map(TryInto::try_into)
1415 .transpose()?
1416 .unwrap_or_default(),
1417 })
1418 }
1419}
1420
1421#[derive(Debug, Clone, Serialize, Deserialize)]
1423pub struct WatchlistSecurity {
1424 pub symbol: String,
1426 pub market: Market,
1428 pub name: String,
1430 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
1432 pub watched_price: Option<Decimal>,
1433 #[serde(
1435 serialize_with = "time::serde::rfc3339::serialize",
1436 deserialize_with = "serde_utils::timestamp::deserialize"
1437 )]
1438 pub watched_at: OffsetDateTime,
1439 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1441 pub is_pinned: bool,
1442}
1443
1444#[derive(Debug, Clone, Serialize, Deserialize)]
1446pub struct WatchlistGroup {
1447 #[serde(with = "serde_utils::int64_str")]
1449 pub id: i64,
1450 pub name: String,
1452 pub securities: Vec<WatchlistSecurity>,
1454}
1455
1456#[derive(Debug, Clone)]
1458pub struct RequestCreateWatchlistGroup {
1459 pub name: String,
1461 pub securities: Option<Vec<String>>,
1463}
1464
1465impl RequestCreateWatchlistGroup {
1466 pub fn new(name: impl Into<String>) -> Self {
1468 Self {
1469 name: name.into(),
1470 securities: None,
1471 }
1472 }
1473
1474 pub fn securities<I, T>(self, securities: I) -> Self
1476 where
1477 I: IntoIterator<Item = T>,
1478 T: Into<String>,
1479 {
1480 Self {
1481 securities: Some(securities.into_iter().map(Into::into).collect()),
1482 ..self
1483 }
1484 }
1485}
1486
1487#[derive(Debug, Copy, Clone, Default, Serialize)]
1489#[serde(rename_all = "lowercase")]
1490pub enum SecuritiesUpdateMode {
1491 Add,
1493 Remove,
1495 #[default]
1497 Replace,
1498}
1499
1500#[derive(Debug, Clone)]
1502pub struct RequestUpdateWatchlistGroup {
1503 pub id: i64,
1505 pub name: Option<String>,
1507 pub securities: Option<Vec<String>>,
1509 pub mode: SecuritiesUpdateMode,
1511}
1512
1513impl RequestUpdateWatchlistGroup {
1514 #[inline]
1516 pub fn new(id: i64) -> Self {
1517 Self {
1518 id,
1519 name: None,
1520 securities: None,
1521 mode: SecuritiesUpdateMode::default(),
1522 }
1523 }
1524
1525 pub fn name(self, name: impl Into<String>) -> Self {
1527 Self {
1528 name: Some(name.into()),
1529 ..self
1530 }
1531 }
1532
1533 pub fn securities<I, T>(self, securities: I) -> Self
1535 where
1536 I: IntoIterator<Item = T>,
1537 T: Into<String>,
1538 {
1539 Self {
1540 securities: Some(securities.into_iter().map(Into::into).collect()),
1541 ..self
1542 }
1543 }
1544
1545 pub fn mode(self, mode: SecuritiesUpdateMode) -> Self {
1547 Self { mode, ..self }
1548 }
1549}
1550
1551#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1553pub enum CalcIndex {
1554 LastDone,
1556 ChangeValue,
1558 ChangeRate,
1560 Volume,
1562 Turnover,
1564 YtdChangeRate,
1566 TurnoverRate,
1568 TotalMarketValue,
1570 CapitalFlow,
1572 Amplitude,
1574 VolumeRatio,
1576 PeTtmRatio,
1578 PbRatio,
1580 DividendRatioTtm,
1582 FiveDayChangeRate,
1584 TenDayChangeRate,
1586 HalfYearChangeRate,
1588 FiveMinutesChangeRate,
1590 ExpiryDate,
1592 StrikePrice,
1594 UpperStrikePrice,
1596 LowerStrikePrice,
1598 OutstandingQty,
1600 OutstandingRatio,
1602 Premium,
1604 ItmOtm,
1606 ImpliedVolatility,
1608 WarrantDelta,
1610 CallPrice,
1612 ToCallPrice,
1614 EffectiveLeverage,
1616 LeverageRatio,
1618 ConversionRatio,
1620 BalancePoint,
1622 OpenInterest,
1624 Delta,
1626 Gamma,
1628 Theta,
1630 Vega,
1632 Rho,
1634}
1635
1636impl From<CalcIndex> for longbridge_proto::quote::CalcIndex {
1637 fn from(value: CalcIndex) -> Self {
1638 use longbridge_proto::quote::CalcIndex::*;
1639
1640 match value {
1641 CalcIndex::LastDone => CalcindexLastDone,
1642 CalcIndex::ChangeValue => CalcindexChangeVal,
1643 CalcIndex::ChangeRate => CalcindexChangeRate,
1644 CalcIndex::Volume => CalcindexVolume,
1645 CalcIndex::Turnover => CalcindexTurnover,
1646 CalcIndex::YtdChangeRate => CalcindexYtdChangeRate,
1647 CalcIndex::TurnoverRate => CalcindexTurnoverRate,
1648 CalcIndex::TotalMarketValue => CalcindexTotalMarketValue,
1649 CalcIndex::CapitalFlow => CalcindexCapitalFlow,
1650 CalcIndex::Amplitude => CalcindexAmplitude,
1651 CalcIndex::VolumeRatio => CalcindexVolumeRatio,
1652 CalcIndex::PeTtmRatio => CalcindexPeTtmRatio,
1653 CalcIndex::PbRatio => CalcindexPbRatio,
1654 CalcIndex::DividendRatioTtm => CalcindexDividendRatioTtm,
1655 CalcIndex::FiveDayChangeRate => CalcindexFiveDayChangeRate,
1656 CalcIndex::TenDayChangeRate => CalcindexTenDayChangeRate,
1657 CalcIndex::HalfYearChangeRate => CalcindexHalfYearChangeRate,
1658 CalcIndex::FiveMinutesChangeRate => CalcindexFiveMinutesChangeRate,
1659 CalcIndex::ExpiryDate => CalcindexExpiryDate,
1660 CalcIndex::StrikePrice => CalcindexStrikePrice,
1661 CalcIndex::UpperStrikePrice => CalcindexUpperStrikePrice,
1662 CalcIndex::LowerStrikePrice => CalcindexLowerStrikePrice,
1663 CalcIndex::OutstandingQty => CalcindexOutstandingQty,
1664 CalcIndex::OutstandingRatio => CalcindexOutstandingRatio,
1665 CalcIndex::Premium => CalcindexPremium,
1666 CalcIndex::ItmOtm => CalcindexItmOtm,
1667 CalcIndex::ImpliedVolatility => CalcindexImpliedVolatility,
1668 CalcIndex::WarrantDelta => CalcindexWarrantDelta,
1669 CalcIndex::CallPrice => CalcindexCallPrice,
1670 CalcIndex::ToCallPrice => CalcindexToCallPrice,
1671 CalcIndex::EffectiveLeverage => CalcindexEffectiveLeverage,
1672 CalcIndex::LeverageRatio => CalcindexLeverageRatio,
1673 CalcIndex::ConversionRatio => CalcindexConversionRatio,
1674 CalcIndex::BalancePoint => CalcindexBalancePoint,
1675 CalcIndex::OpenInterest => CalcindexOpenInterest,
1676 CalcIndex::Delta => CalcindexDelta,
1677 CalcIndex::Gamma => CalcindexGamma,
1678 CalcIndex::Theta => CalcindexTheta,
1679 CalcIndex::Vega => CalcindexVega,
1680 CalcIndex::Rho => CalcindexRho,
1681 }
1682 }
1683}
1684
1685#[derive(Debug, Clone, Serialize, Deserialize)]
1687pub struct SecurityCalcIndex {
1688 pub symbol: String,
1690 pub last_done: Option<Decimal>,
1692 pub change_value: Option<Decimal>,
1694 pub change_rate: Option<Decimal>,
1696 pub volume: Option<i64>,
1698 pub turnover: Option<Decimal>,
1700 pub ytd_change_rate: Option<Decimal>,
1702 pub turnover_rate: Option<Decimal>,
1704 pub total_market_value: Option<Decimal>,
1706 pub capital_flow: Option<Decimal>,
1708 pub amplitude: Option<Decimal>,
1710 pub volume_ratio: Option<Decimal>,
1712 pub pe_ttm_ratio: Option<Decimal>,
1714 pub pb_ratio: Option<Decimal>,
1716 pub dividend_ratio_ttm: Option<Decimal>,
1718 pub five_day_change_rate: Option<Decimal>,
1720 pub ten_day_change_rate: Option<Decimal>,
1722 pub half_year_change_rate: Option<Decimal>,
1724 pub five_minutes_change_rate: Option<Decimal>,
1726 pub expiry_date: Option<Date>,
1728 pub strike_price: Option<Decimal>,
1730 pub upper_strike_price: Option<Decimal>,
1732 pub lower_strike_price: Option<Decimal>,
1734 pub outstanding_qty: Option<i64>,
1736 pub outstanding_ratio: Option<Decimal>,
1738 pub premium: Option<Decimal>,
1740 pub itm_otm: Option<Decimal>,
1742 pub implied_volatility: Option<Decimal>,
1744 pub warrant_delta: Option<Decimal>,
1746 pub call_price: Option<Decimal>,
1748 pub to_call_price: Option<Decimal>,
1750 pub effective_leverage: Option<Decimal>,
1752 pub leverage_ratio: Option<Decimal>,
1754 pub conversion_ratio: Option<Decimal>,
1756 pub balance_point: Option<Decimal>,
1758 pub open_interest: Option<i64>,
1760 pub delta: Option<Decimal>,
1763 pub gamma: Option<Decimal>,
1766 pub theta: Option<Decimal>,
1770 pub vega: Option<Decimal>,
1774 pub rho: Option<Decimal>,
1778}
1779
1780impl SecurityCalcIndex {
1781 pub(crate) fn from_proto(
1782 resp: longbridge_proto::quote::SecurityCalcIndex,
1783 indexes: &[CalcIndex],
1784 ) -> Self {
1785 let mut output = SecurityCalcIndex {
1786 symbol: resp.symbol,
1787 last_done: None,
1788 change_value: None,
1789 change_rate: None,
1790 volume: None,
1791 turnover: None,
1792 ytd_change_rate: None,
1793 turnover_rate: None,
1794 total_market_value: None,
1795 capital_flow: None,
1796 amplitude: None,
1797 volume_ratio: None,
1798 pe_ttm_ratio: None,
1799 pb_ratio: None,
1800 dividend_ratio_ttm: None,
1801 five_day_change_rate: None,
1802 ten_day_change_rate: None,
1803 half_year_change_rate: None,
1804 five_minutes_change_rate: None,
1805 expiry_date: None,
1806 strike_price: None,
1807 upper_strike_price: None,
1808 lower_strike_price: None,
1809 outstanding_qty: None,
1810 outstanding_ratio: None,
1811 premium: None,
1812 itm_otm: None,
1813 implied_volatility: None,
1814 warrant_delta: None,
1815 call_price: None,
1816 to_call_price: None,
1817 effective_leverage: None,
1818 leverage_ratio: None,
1819 conversion_ratio: None,
1820 balance_point: None,
1821 open_interest: None,
1822 delta: None,
1823 gamma: None,
1824 theta: None,
1825 vega: None,
1826 rho: None,
1827 };
1828
1829 for index in indexes {
1830 match index {
1831 CalcIndex::LastDone => output.last_done = resp.last_done.parse().ok(),
1832 CalcIndex::ChangeValue => output.change_value = resp.change_val.parse().ok(),
1833 CalcIndex::ChangeRate => output.change_rate = resp.change_rate.parse().ok(),
1834 CalcIndex::Volume => output.volume = Some(resp.volume),
1835 CalcIndex::Turnover => output.turnover = resp.turnover.parse().ok(),
1836 CalcIndex::YtdChangeRate => {
1837 output.ytd_change_rate = resp.ytd_change_rate.parse().ok()
1838 }
1839 CalcIndex::TurnoverRate => output.turnover_rate = resp.turnover_rate.parse().ok(),
1840 CalcIndex::TotalMarketValue => {
1841 output.total_market_value = resp.total_market_value.parse().ok()
1842 }
1843 CalcIndex::CapitalFlow => output.capital_flow = resp.capital_flow.parse().ok(),
1844 CalcIndex::Amplitude => output.amplitude = resp.amplitude.parse().ok(),
1845 CalcIndex::VolumeRatio => output.volume_ratio = resp.volume_ratio.parse().ok(),
1846 CalcIndex::PeTtmRatio => output.pe_ttm_ratio = resp.pe_ttm_ratio.parse().ok(),
1847 CalcIndex::PbRatio => output.pb_ratio = resp.pb_ratio.parse().ok(),
1848 CalcIndex::DividendRatioTtm => {
1849 output.dividend_ratio_ttm = resp.dividend_ratio_ttm.parse().ok()
1850 }
1851 CalcIndex::FiveDayChangeRate => {
1852 output.five_day_change_rate = resp.five_day_change_rate.parse().ok()
1853 }
1854 CalcIndex::TenDayChangeRate => {
1855 output.ten_day_change_rate = resp.ten_day_change_rate.parse().ok()
1856 }
1857 CalcIndex::HalfYearChangeRate => {
1858 output.half_year_change_rate = resp.half_year_change_rate.parse().ok()
1859 }
1860 CalcIndex::FiveMinutesChangeRate => {
1861 output.five_minutes_change_rate = resp.five_minutes_change_rate.parse().ok()
1862 }
1863 CalcIndex::ExpiryDate => output.expiry_date = parse_date(&resp.expiry_date).ok(),
1864 CalcIndex::StrikePrice => output.strike_price = resp.strike_price.parse().ok(),
1865 CalcIndex::UpperStrikePrice => {
1866 output.upper_strike_price = resp.upper_strike_price.parse().ok()
1867 }
1868 CalcIndex::LowerStrikePrice => {
1869 output.lower_strike_price = resp.lower_strike_price.parse().ok()
1870 }
1871 CalcIndex::OutstandingQty => output.outstanding_qty = Some(resp.outstanding_qty),
1872 CalcIndex::OutstandingRatio => {
1873 output.outstanding_ratio = resp.outstanding_ratio.parse().ok()
1874 }
1875 CalcIndex::Premium => output.premium = resp.premium.parse().ok(),
1876 CalcIndex::ItmOtm => output.itm_otm = resp.itm_otm.parse().ok(),
1877 CalcIndex::ImpliedVolatility => {
1878 output.implied_volatility = resp.implied_volatility.parse().ok()
1879 }
1880 CalcIndex::WarrantDelta => output.warrant_delta = resp.warrant_delta.parse().ok(),
1881 CalcIndex::CallPrice => output.call_price = resp.call_price.parse().ok(),
1882 CalcIndex::ToCallPrice => output.to_call_price = resp.to_call_price.parse().ok(),
1883 CalcIndex::EffectiveLeverage => {
1884 output.effective_leverage = resp.effective_leverage.parse().ok()
1885 }
1886 CalcIndex::LeverageRatio => {
1887 output.leverage_ratio = resp.leverage_ratio.parse().ok()
1888 }
1889 CalcIndex::ConversionRatio => {
1890 output.conversion_ratio = resp.conversion_ratio.parse().ok()
1891 }
1892 CalcIndex::BalancePoint => output.balance_point = resp.balance_point.parse().ok(),
1893 CalcIndex::OpenInterest => output.open_interest = Some(resp.open_interest),
1894 CalcIndex::Delta => output.delta = resp.delta.parse().ok(),
1895 CalcIndex::Gamma => output.gamma = resp.gamma.parse().ok(),
1896 CalcIndex::Theta => output.theta = resp.theta.parse().ok(),
1897 CalcIndex::Vega => output.vega = resp.vega.parse().ok(),
1898 CalcIndex::Rho => output.rho = resp.rho.parse().ok(),
1899 }
1900 }
1901
1902 output
1903 }
1904}
1905
1906#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1908pub enum SecurityListCategory {
1909 Overnight,
1911}
1912
1913impl_serialize_for_enum_string!(SecurityListCategory);
1914
1915#[derive(Debug, Serialize, Deserialize)]
1917pub struct Security {
1918 pub symbol: String,
1920 pub name_cn: String,
1922 pub name_en: String,
1924 pub name_hk: String,
1926}
1927
1928#[derive(Debug, Clone)]
1930pub struct QuotePackageDetail {
1931 pub key: String,
1933 pub name: String,
1935 pub description: String,
1937 pub start_at: OffsetDateTime,
1939 pub end_at: OffsetDateTime,
1941}
1942
1943impl TryFrom<quote::user_quote_level_detail::PackageDetail> for QuotePackageDetail {
1944 type Error = Error;
1945
1946 fn try_from(quote: quote::user_quote_level_detail::PackageDetail) -> Result<Self> {
1947 Ok(Self {
1948 key: quote.key,
1949 name: quote.name,
1950 description: quote.description,
1951 start_at: OffsetDateTime::from_unix_timestamp(quote.start)
1952 .map_err(|err| Error::parse_field_error("start_at", err))?,
1953 end_at: OffsetDateTime::from_unix_timestamp(quote.end)
1954 .map_err(|err| Error::parse_field_error("end_at", err))?,
1955 })
1956 }
1957}
1958
1959#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1961#[repr(i32)]
1962pub enum TradeSessions {
1963 Intraday = 0,
1965 All = 100,
1967}
1968
1969impl TradeSessions {
1970 #[inline]
1971 pub(crate) fn contains(&self, session: TradeSession) -> bool {
1972 match self {
1973 TradeSessions::Intraday => session == TradeSession::Intraday,
1974 TradeSessions::All => true,
1975 }
1976 }
1977}
1978
1979#[derive(Debug, Clone, Serialize, Deserialize)]
1981pub struct MarketTemperature {
1982 pub temperature: i32,
1984 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1986 pub description: String,
1987 pub valuation: i32,
1989 pub sentiment: i32,
1991 #[serde(
1993 serialize_with = "time::serde::rfc3339::serialize",
1994 deserialize_with = "serde_utils::timestamp::deserialize",
1995 alias = "updated_at"
1996 )]
1997 pub timestamp: OffsetDateTime,
1998}
1999
2000#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
2002pub enum Granularity {
2003 Unknown,
2005 #[strum(serialize = "daily")]
2007 Daily,
2008 #[strum(serialize = "weekly")]
2010 Weekly,
2011 #[strum(serialize = "monthly")]
2013 Monthly,
2014}
2015
2016#[derive(Debug, Clone, Serialize, Deserialize)]
2018pub struct HistoryMarketTemperatureResponse {
2019 #[serde(rename = "type")]
2021 pub granularity: Granularity,
2022 #[serde(rename = "list")]
2024 pub records: Vec<MarketTemperature>,
2025}
2026
2027#[derive(Debug, Clone, Serialize, Deserialize)]
2029pub struct FilingItem {
2030 pub id: String,
2032 pub title: String,
2034 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2036 pub description: String,
2037 pub file_name: String,
2039 pub file_urls: Vec<String>,
2041 #[serde(
2043 rename = "publish_at",
2044 serialize_with = "time::serde::rfc3339::serialize",
2045 deserialize_with = "crate::serde_utils::timestamp::deserialize"
2046 )]
2047 pub published_at: OffsetDateTime,
2048}
2049
2050impl_serde_for_enum_string!(Granularity);
2051impl_default_for_enum_string!(
2052 OptionType,
2053 OptionDirection,
2054 OptionExpiryCycleType,
2055 OptionStandardAttr,
2056 WarrantType,
2057 SecurityBoard,
2058 Granularity
2059);
2060
2061#[derive(Debug, Clone, Serialize, Deserialize)]
2065pub struct ShortPositionsItem {
2066 pub timestamp: String,
2068 pub rate: String,
2070 pub close: String,
2072 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2074 pub current_shares_short: String,
2075 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2077 pub avg_daily_share_volume: String,
2078 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2080 pub days_to_cover: String,
2081 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2083 pub amount: String,
2084 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2086 pub balance: String,
2087 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2089 pub cost: String,
2090}
2091
2092#[derive(Debug, Clone, Serialize, Deserialize)]
2094pub struct ShortPositionsResponse {
2095 pub data: Vec<ShortPositionsItem>,
2097}
2098
2099#[derive(Debug, Clone, Serialize, Deserialize)]
2103pub struct OptionVolumeStats {
2104 pub symbol: String,
2106 pub call_volume: i64,
2108 pub put_volume: i64,
2110}
2111
2112#[derive(Debug, Clone, Serialize, Deserialize)]
2116pub struct OptionVolumeDaily {
2117 pub symbol: String,
2119 pub stats: Vec<OptionVolumeDailyStat>,
2121}
2122
2123#[derive(Debug, Clone, Serialize, Deserialize)]
2125pub struct OptionVolumeDailyStat {
2126 pub symbol: String,
2128 pub date: Date,
2130 pub call_volume: i64,
2132 pub put_volume: i64,
2134 pub call_open_interest: i64,
2136 pub put_open_interest: i64,
2138 pub total_volume: i64,
2140 pub total_open_interest: i64,
2142 pub pc_vol: f64,
2144 pub pc_oi: f64,
2146}
2147
2148#[derive(Debug, Clone, Serialize, Deserialize)]
2152pub struct ShortTradesItem {
2153 pub timestamp: String,
2155 pub rate: String,
2157 pub close: String,
2159 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2161 pub nus_amount: String,
2162 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2164 pub ny_amount: String,
2165 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2167 pub total_amount: String,
2168 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2170 pub amount: String,
2171 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2173 pub balance: String,
2174}
2175
2176#[derive(Debug, Clone, Serialize, Deserialize)]
2178pub struct ShortTradesResponse {
2179 pub data: Vec<ShortTradesItem>,
2181}
2182
2183#[derive(Debug, Clone, Serialize, Deserialize)]
2187#[serde(rename_all = "lowercase")]
2188pub enum PinnedMode {
2189 Add,
2191 Remove,
2193}
2194
2195#[derive(Debug, Clone, Serialize, Deserialize)]
2202pub struct USCryptoOverview {
2203 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2205 pub name: String,
2206 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2208 pub ticker: String,
2209 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2211 pub currency: String,
2212 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2214 pub all_time_high: String,
2215 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2217 pub all_time_high_date: String,
2218 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2220 pub all_time_low: String,
2221 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2223 pub all_time_low_date: String,
2224 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2226 pub ipo_date: String,
2227 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2229 pub issue_price: String,
2230 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2232 pub shares: String,
2233 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2235 pub symbol: String,
2236 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2238 pub base_asset: String,
2239 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2241 pub official_web_address: String,
2242 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2244 pub logo: String,
2245 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2247 pub wiki_url: String,
2248 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2250 pub profile: String,
2251}
2252
2253#[cfg(test)]
2254mod tests {
2255 use serde::Deserialize;
2256
2257 use crate::{Market, quote::WatchlistGroup};
2258
2259 #[test]
2260 fn watch_list() {
2261 #[derive(Debug, Deserialize)]
2262 struct Response {
2263 groups: Vec<WatchlistGroup>,
2264 }
2265
2266 let json = r#"
2267 {
2268 "groups": [
2269 {
2270 "id": "1",
2271 "name": "Test",
2272 "securities": [
2273 {
2274 "symbol": "AAPL",
2275 "market": "US",
2276 "name": "Apple Inc.",
2277 "watched_price": "150.0",
2278 "watched_at": "1633036800"
2279 }
2280 ]
2281 }
2282 ]
2283 }
2284 "#;
2285
2286 let response: Response = serde_json::from_str(json).unwrap();
2287 assert_eq!(response.groups.len(), 1);
2288 assert_eq!(response.groups[0].id, 1);
2289 assert_eq!(response.groups[0].name, "Test");
2290 assert_eq!(response.groups[0].securities.len(), 1);
2291 assert_eq!(response.groups[0].securities[0].symbol, "AAPL");
2292 assert_eq!(response.groups[0].securities[0].market, Market::US);
2293 assert_eq!(response.groups[0].securities[0].name, "Apple Inc.");
2294 assert_eq!(
2295 response.groups[0].securities[0].watched_price,
2296 Some(decimal!(150.0))
2297 );
2298 assert_eq!(
2299 response.groups[0].securities[0].watched_at.unix_timestamp(),
2300 1633036800
2301 );
2302 }
2303}