1use longport_candlesticks::CandlestickComponents;
2use longport_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 longport_candlesticks::TradeSessionType for TradeSession {
30 #[inline]
31 fn kind(&self) -> longport_candlesticks::TradeSessionKind {
32 match self {
33 TradeSession::Intraday => longport_candlesticks::TRADE_SESSION_INTRADAY,
34 TradeSession::Pre => longport_candlesticks::TRADE_SESSION_PRE,
35 TradeSession::Post => longport_candlesticks::TRADE_SESSION_POST,
36 TradeSession::Overnight => longport_candlesticks::TRADE_SESSION_OVERNIGHT,
37 }
38 }
39}
40
41impl From<longport_proto::quote::TradeSession> for TradeSession {
42 #[inline]
43 fn from(value: longport_proto::quote::TradeSession) -> Self {
44 match value {
45 longport_proto::quote::TradeSession::NormalTrade => Self::Intraday,
46 longport_proto::quote::TradeSession::PreTrade => Self::Pre,
47 longport_proto::quote::TradeSession::PostTrade => Self::Post,
48 longport_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: longport_proto::quote::TradeSession::try_from(trade.trade_session)
182 .unwrap_or_default()
183 .into(),
184 })
185 }
186}
187
188impl longport_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, Clone, Serialize, Deserialize)]
504pub struct OptionQuote {
505 pub symbol: String,
507 pub last_done: Decimal,
509 pub prev_close: Decimal,
511 pub open: Decimal,
513 pub high: Decimal,
515 pub low: Decimal,
517 #[serde(with = "time::serde::rfc3339")]
519 pub timestamp: OffsetDateTime,
520 pub volume: i64,
522 pub turnover: Decimal,
524 pub trade_status: TradeStatus,
526 pub implied_volatility: Decimal,
528 pub open_interest: i64,
530 pub expiry_date: Date,
532 pub strike_price: Decimal,
534 pub contract_multiplier: Decimal,
536 pub contract_type: OptionType,
538 pub contract_size: Decimal,
540 pub direction: OptionDirection,
542 pub historical_volatility: Decimal,
544 pub underlying_symbol: String,
546}
547
548impl TryFrom<quote::OptionQuote> for OptionQuote {
549 type Error = Error;
550
551 fn try_from(quote: quote::OptionQuote) -> Result<Self> {
552 let option_extend = quote.option_extend.unwrap_or_default();
553
554 Ok(Self {
555 symbol: quote.symbol,
556 last_done: quote.last_done.parse().unwrap_or_default(),
557 prev_close: quote.prev_close.parse().unwrap_or_default(),
558 open: quote.open.parse().unwrap_or_default(),
559 high: quote.high.parse().unwrap_or_default(),
560 low: quote.low.parse().unwrap_or_default(),
561 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
562 .map_err(|err| Error::parse_field_error("timestamp", err))?,
563 volume: quote.volume,
564 turnover: quote.turnover.parse().unwrap_or_default(),
565 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
566 implied_volatility: option_extend.implied_volatility.parse().unwrap_or_default(),
567 open_interest: option_extend.open_interest,
568 expiry_date: parse_date(&option_extend.expiry_date)
569 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
570 strike_price: option_extend.strike_price.parse().unwrap_or_default(),
571 contract_multiplier: option_extend
572 .contract_multiplier
573 .parse()
574 .unwrap_or_default(),
575 contract_type: option_extend.contract_type.parse().unwrap_or_default(),
576 contract_size: option_extend.contract_size.parse().unwrap_or_default(),
577 direction: option_extend.direction.parse().unwrap_or_default(),
578 historical_volatility: option_extend
579 .historical_volatility
580 .parse()
581 .unwrap_or_default(),
582 underlying_symbol: option_extend.underlying_symbol,
583 })
584 }
585}
586
587#[derive(
589 Debug,
590 Copy,
591 Clone,
592 Hash,
593 Eq,
594 PartialEq,
595 EnumString,
596 IntoPrimitive,
597 TryFromPrimitive,
598 Serialize,
599 Deserialize,
600)]
601#[repr(i32)]
602pub enum WarrantType {
603 Unknown = -1,
605 Call = 0,
607 Put = 1,
609 Bull = 2,
611 Bear = 3,
613 Inline = 4,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct WarrantQuote {
620 pub symbol: String,
622 pub last_done: Decimal,
624 pub prev_close: Decimal,
626 pub open: Decimal,
628 pub high: Decimal,
630 pub low: Decimal,
632 #[serde(with = "time::serde::rfc3339")]
634 pub timestamp: OffsetDateTime,
635 pub volume: i64,
637 pub turnover: Decimal,
639 pub trade_status: TradeStatus,
641 pub implied_volatility: Decimal,
643 pub expiry_date: Date,
645 pub last_trade_date: Date,
647 pub outstanding_ratio: Decimal,
649 pub outstanding_quantity: i64,
651 pub conversion_ratio: Decimal,
653 pub category: WarrantType,
655 pub strike_price: Decimal,
657 pub upper_strike_price: Decimal,
659 pub lower_strike_price: Decimal,
661 pub call_price: Decimal,
663 pub underlying_symbol: String,
665}
666
667impl TryFrom<quote::WarrantQuote> for WarrantQuote {
668 type Error = Error;
669
670 fn try_from(quote: quote::WarrantQuote) -> Result<Self> {
671 let warrant_extend = quote.warrant_extend.unwrap_or_default();
672
673 Ok(Self {
674 symbol: quote.symbol,
675 last_done: quote.last_done.parse().unwrap_or_default(),
676 prev_close: quote.prev_close.parse().unwrap_or_default(),
677 open: quote.open.parse().unwrap_or_default(),
678 high: quote.high.parse().unwrap_or_default(),
679 low: quote.low.parse().unwrap_or_default(),
680 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
681 .map_err(|err| Error::parse_field_error("timestamp", err))?,
682 volume: quote.volume,
683 turnover: quote.turnover.parse().unwrap_or_default(),
684 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
685 implied_volatility: warrant_extend
686 .implied_volatility
687 .parse()
688 .unwrap_or_default(),
689 expiry_date: parse_date(&warrant_extend.expiry_date)
690 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
691 last_trade_date: parse_date(&warrant_extend.last_trade_date)
692 .map_err(|err| Error::parse_field_error("last_trade_date", err))?,
693 outstanding_ratio: warrant_extend.outstanding_ratio.parse().unwrap_or_default(),
694 outstanding_quantity: warrant_extend.outstanding_qty,
695 conversion_ratio: warrant_extend.conversion_ratio.parse().unwrap_or_default(),
696 category: warrant_extend.category.parse().unwrap_or_default(),
697 strike_price: warrant_extend.strike_price.parse().unwrap_or_default(),
698 upper_strike_price: warrant_extend
699 .upper_strike_price
700 .parse()
701 .unwrap_or_default(),
702 lower_strike_price: warrant_extend
703 .lower_strike_price
704 .parse()
705 .unwrap_or_default(),
706 call_price: warrant_extend.call_price.parse().unwrap_or_default(),
707 underlying_symbol: warrant_extend.underlying_symbol,
708 })
709 }
710}
711
712#[derive(Debug, Clone, Default, Serialize, Deserialize)]
714pub struct SecurityDepth {
715 pub asks: Vec<Depth>,
717 pub bids: Vec<Depth>,
719}
720
721#[derive(Debug, Clone, Default, Serialize, Deserialize)]
723pub struct SecurityBrokers {
724 pub ask_brokers: Vec<Brokers>,
726 pub bid_brokers: Vec<Brokers>,
728}
729
730#[derive(Debug, Clone, Serialize, Deserialize)]
732pub struct ParticipantInfo {
733 pub broker_ids: Vec<i32>,
735 pub name_cn: String,
737 pub name_en: String,
739 pub name_hk: String,
741}
742
743impl From<quote::ParticipantInfo> for ParticipantInfo {
744 fn from(info: quote::ParticipantInfo) -> Self {
745 Self {
746 broker_ids: info.broker_ids,
747 name_cn: info.participant_name_cn,
748 name_en: info.participant_name_en,
749 name_hk: info.participant_name_hk,
750 }
751 }
752}
753
754#[derive(Debug, Clone, Serialize, Deserialize)]
756pub struct IntradayLine {
757 pub price: Decimal,
759 #[serde(with = "time::serde::rfc3339")]
761 pub timestamp: OffsetDateTime,
762 pub volume: i64,
764 pub turnover: Decimal,
766 pub avg_price: Decimal,
768}
769
770impl TryFrom<quote::Line> for IntradayLine {
771 type Error = Error;
772
773 fn try_from(value: quote::Line) -> Result<Self> {
774 Ok(Self {
775 price: value.price.parse().unwrap_or_default(),
776 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
777 .map_err(|err| Error::parse_field_error("timestamp", err))?,
778 volume: value.volume,
779 turnover: value.turnover.parse().unwrap_or_default(),
780 avg_price: value.avg_price.parse().unwrap_or_default(),
781 })
782 }
783}
784
785#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
787pub struct Candlestick {
788 pub close: Decimal,
790 pub open: Decimal,
792 pub low: Decimal,
794 pub high: Decimal,
796 pub volume: i64,
798 pub turnover: Decimal,
800 #[serde(with = "time::serde::rfc3339")]
802 pub timestamp: OffsetDateTime,
803 pub trade_session: TradeSession,
805 open_updated: bool,
806}
807
808impl TryFrom<quote::Candlestick> for Candlestick {
809 type Error = Error;
810
811 fn try_from(value: quote::Candlestick) -> Result<Self> {
812 Ok(Self {
813 close: value.close.parse().unwrap_or_default(),
814 open: value.open.parse().unwrap_or_default(),
815 low: value.low.parse().unwrap_or_default(),
816 high: value.high.parse().unwrap_or_default(),
817 volume: value.volume,
818 turnover: value.turnover.parse().unwrap_or_default(),
819 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
820 .map_err(|err| Error::parse_field_error("timestamp", err))?,
821 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
822 .map_err(|err| Error::parse_field_error("trade_session", err))?
823 .into(),
824 open_updated: true,
825 })
826 }
827}
828
829impl longport_candlesticks::CandlestickType for Candlestick {
830 type PriceType = Decimal;
831 type VolumeType = i64;
832 type TurnoverType = Decimal;
833 type TradeSessionType = TradeSession;
834
835 #[inline]
836 fn new(
837 components: CandlestickComponents<
838 Self::PriceType,
839 Self::VolumeType,
840 Self::TurnoverType,
841 Self::TradeSessionType,
842 >,
843 ) -> Self {
844 Self {
845 timestamp: components.time,
846 open: components.open,
847 high: components.high,
848 low: components.low,
849 close: components.close,
850 volume: components.volume,
851 turnover: components.turnover,
852 trade_session: components.trade_session,
853 open_updated: components.open_updated,
854 }
855 }
856
857 #[inline]
858 fn time(&self) -> OffsetDateTime {
859 self.timestamp
860 }
861
862 #[inline]
863 fn set_time(&mut self, time: OffsetDateTime) {
864 self.timestamp = time;
865 }
866
867 #[inline]
868 fn open(&self) -> Self::PriceType {
869 self.open
870 }
871
872 #[inline]
873 fn set_open(&mut self, open: Self::PriceType) {
874 self.open = open;
875 }
876
877 #[inline]
878 fn high(&self) -> Self::PriceType {
879 self.high
880 }
881
882 #[inline]
883 fn set_high(&mut self, high: Self::PriceType) {
884 self.high = high;
885 }
886
887 #[inline]
888 fn low(&self) -> Self::PriceType {
889 self.low
890 }
891
892 #[inline]
893 fn set_low(&mut self, low: Self::PriceType) {
894 self.low = low;
895 }
896
897 #[inline]
898 fn close(&self) -> Self::PriceType {
899 self.close
900 }
901
902 #[inline]
903 fn set_close(&mut self, close: Self::PriceType) {
904 self.close = close;
905 }
906
907 #[inline]
908 fn volume(&self) -> Self::VolumeType {
909 self.volume
910 }
911
912 #[inline]
913 fn set_volume(&mut self, volume: Self::VolumeType) {
914 self.volume = volume;
915 }
916
917 #[inline]
918 fn turnover(&self) -> Self::TurnoverType {
919 self.turnover
920 }
921
922 #[inline]
923 fn set_turnover(&mut self, turnover: Self::TurnoverType) {
924 self.turnover = turnover;
925 }
926
927 #[inline]
928 fn trade_session(&self) -> Self::TradeSessionType {
929 self.trade_session
930 }
931
932 #[inline]
933 fn set_open_updated(&mut self, updated: bool) {
934 self.open_updated = updated;
935 }
936
937 #[inline]
938 fn open_updated(&self) -> bool {
939 self.open_updated
940 }
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
945pub struct StrikePriceInfo {
946 pub price: Decimal,
948 pub call_symbol: String,
950 pub put_symbol: String,
952 pub standard: bool,
954}
955
956impl TryFrom<quote::StrikePriceInfo> for StrikePriceInfo {
957 type Error = Error;
958
959 fn try_from(value: quote::StrikePriceInfo) -> Result<Self> {
960 Ok(Self {
961 price: value.price.parse().unwrap_or_default(),
962 call_symbol: value.call_symbol,
963 put_symbol: value.put_symbol,
964 standard: value.standard,
965 })
966 }
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize)]
971pub struct IssuerInfo {
972 pub issuer_id: i32,
974 pub name_cn: String,
976 pub name_en: String,
978 pub name_hk: String,
980}
981
982impl From<quote::IssuerInfo> for IssuerInfo {
983 fn from(info: quote::IssuerInfo) -> Self {
984 Self {
985 issuer_id: info.id,
986 name_cn: info.name_cn,
987 name_en: info.name_en,
988 name_hk: info.name_hk,
989 }
990 }
991}
992
993#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
995#[repr(i32)]
996pub enum SortOrderType {
997 Ascending = 0,
999 Descending = 1,
1001}
1002
1003#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1005#[repr(i32)]
1006pub enum WarrantSortBy {
1007 LastDone = 0,
1009 ChangeRate = 1,
1011 ChangeValue = 2,
1013 Volume = 3,
1015 Turnover = 4,
1017 ExpiryDate = 5,
1019 StrikePrice = 6,
1021 UpperStrikePrice = 7,
1023 LowerStrikePrice = 8,
1025 OutstandingQuantity = 9,
1027 OutstandingRatio = 10,
1029 Premium = 11,
1031 ItmOtm = 12,
1033 ImpliedVolatility = 13,
1035 Delta = 14,
1037 CallPrice = 15,
1039 ToCallPrice = 16,
1041 EffectiveLeverage = 17,
1043 LeverageRatio = 18,
1045 ConversionRatio = 19,
1047 BalancePoint = 20,
1049 Status = 21,
1051}
1052
1053#[allow(non_camel_case_types)]
1055#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1056#[repr(i32)]
1057pub enum FilterWarrantExpiryDate {
1058 LT_3 = 1,
1060 Between_3_6 = 2,
1062 Between_6_12 = 3,
1064 GT_12 = 4,
1066}
1067
1068#[allow(non_camel_case_types)]
1070#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1071#[repr(i32)]
1072pub enum FilterWarrantInOutBoundsType {
1073 In = 1,
1075 Out = 2,
1077}
1078
1079#[derive(
1081 Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive, TryFromPrimitive, Serialize, Deserialize,
1082)]
1083#[repr(i32)]
1084pub enum WarrantStatus {
1085 Suspend = 2,
1087 PrepareList = 3,
1089 Normal = 4,
1091}
1092
1093#[derive(Debug, Clone, Serialize, Deserialize)]
1095pub struct WarrantInfo {
1096 pub symbol: String,
1098 pub warrant_type: WarrantType,
1100 pub name: String,
1102 pub last_done: Decimal,
1104 pub change_rate: Decimal,
1106 pub change_value: Decimal,
1108 pub volume: i64,
1110 pub turnover: Decimal,
1112 pub expiry_date: Date,
1114 pub strike_price: Option<Decimal>,
1116 pub upper_strike_price: Option<Decimal>,
1118 pub lower_strike_price: Option<Decimal>,
1120 pub outstanding_qty: i64,
1122 pub outstanding_ratio: Decimal,
1124 pub premium: Decimal,
1126 pub itm_otm: Option<Decimal>,
1128 pub implied_volatility: Option<Decimal>,
1130 pub delta: Option<Decimal>,
1132 pub call_price: Option<Decimal>,
1134 pub to_call_price: Option<Decimal>,
1136 pub effective_leverage: Option<Decimal>,
1138 pub leverage_ratio: Decimal,
1140 pub conversion_ratio: Option<Decimal>,
1142 pub balance_point: Option<Decimal>,
1144 pub status: WarrantStatus,
1146}
1147
1148impl TryFrom<quote::FilterWarrant> for WarrantInfo {
1149 type Error = Error;
1150
1151 fn try_from(info: quote::FilterWarrant) -> Result<Self> {
1152 let r#type = WarrantType::try_from(info.r#type)
1153 .map_err(|err| Error::parse_field_error("type", err))?;
1154
1155 match r#type {
1156 WarrantType::Unknown => unreachable!(),
1157 WarrantType::Call | WarrantType::Put => Ok(Self {
1158 symbol: info.symbol,
1159 warrant_type: r#type,
1160 name: info.name,
1161 last_done: info.last_done.parse().unwrap_or_default(),
1162 change_rate: info.change_rate.parse().unwrap_or_default(),
1163 change_value: info.change_val.parse().unwrap_or_default(),
1164 volume: info.volume,
1165 turnover: info.turnover.parse().unwrap_or_default(),
1166 expiry_date: parse_date(&info.expiry_date)
1167 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1168 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1169 upper_strike_price: None,
1170 lower_strike_price: None,
1171 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1172 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1173 premium: info.premium.parse().unwrap_or_default(),
1174 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1175 implied_volatility: Some(info.last_done.parse().unwrap_or_default()),
1176 delta: Some(info.last_done.parse().unwrap_or_default()),
1177 call_price: None,
1178 to_call_price: None,
1179 effective_leverage: Some(info.last_done.parse().unwrap_or_default()),
1180 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1181 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1182 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1183 status: WarrantStatus::try_from(info.status)
1184 .map_err(|err| Error::parse_field_error("state", err))?,
1185 }),
1186 WarrantType::Bull | WarrantType::Bear => Ok(Self {
1187 symbol: info.symbol,
1188 warrant_type: r#type,
1189 name: info.name,
1190 last_done: info.last_done.parse().unwrap_or_default(),
1191 change_rate: info.change_rate.parse().unwrap_or_default(),
1192 change_value: info.change_val.parse().unwrap_or_default(),
1193 volume: info.volume,
1194 turnover: info.turnover.parse().unwrap_or_default(),
1195 expiry_date: parse_date(&info.expiry_date)
1196 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1197 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1198 upper_strike_price: None,
1199 lower_strike_price: None,
1200 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1201 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1202 premium: info.premium.parse().unwrap_or_default(),
1203 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1204 implied_volatility: None,
1205 delta: None,
1206 call_price: Some(info.call_price.parse().unwrap_or_default()),
1207 to_call_price: Some(info.to_call_price.parse().unwrap_or_default()),
1208 effective_leverage: None,
1209 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1210 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1211 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1212 status: WarrantStatus::try_from(info.status)
1213 .map_err(|err| Error::parse_field_error("state", err))?,
1214 }),
1215 WarrantType::Inline => Ok(Self {
1216 symbol: info.symbol,
1217 warrant_type: r#type,
1218 name: info.name,
1219 last_done: info.last_done.parse().unwrap_or_default(),
1220 change_rate: info.change_rate.parse().unwrap_or_default(),
1221 change_value: info.change_val.parse().unwrap_or_default(),
1222 volume: info.volume,
1223 turnover: info.turnover.parse().unwrap_or_default(),
1224 expiry_date: parse_date(&info.expiry_date)
1225 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1226 strike_price: None,
1227 upper_strike_price: Some(info.upper_strike_price.parse().unwrap_or_default()),
1228 lower_strike_price: Some(info.lower_strike_price.parse().unwrap_or_default()),
1229 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1230 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1231 premium: info.premium.parse().unwrap_or_default(),
1232 itm_otm: None,
1233 implied_volatility: None,
1234 delta: None,
1235 call_price: None,
1236 to_call_price: None,
1237 effective_leverage: None,
1238 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1239 conversion_ratio: None,
1240 balance_point: None,
1241 status: WarrantStatus::try_from(info.status)
1242 .map_err(|err| Error::parse_field_error("state", err))?,
1243 }),
1244 }
1245 }
1246}
1247
1248#[derive(Debug, Clone, Serialize, Deserialize)]
1250pub struct TradingSessionInfo {
1251 pub begin_time: Time,
1253 pub end_time: Time,
1255 pub trade_session: TradeSession,
1257}
1258
1259impl TryFrom<quote::TradePeriod> for TradingSessionInfo {
1260 type Error = Error;
1261
1262 fn try_from(value: quote::TradePeriod) -> Result<Self> {
1263 #[inline]
1264 fn parse_time(value: i32) -> ::std::result::Result<Time, time::error::ComponentRange> {
1265 Time::from_hms(((value / 100) % 100) as u8, (value % 100) as u8, 0)
1266 }
1267
1268 Ok(Self {
1269 begin_time: parse_time(value.beg_time)
1270 .map_err(|err| Error::parse_field_error("beg_time", err))?,
1271 end_time: parse_time(value.end_time)
1272 .map_err(|err| Error::parse_field_error("end_time", err))?,
1273 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
1274 .unwrap_or_default()
1275 .into(),
1276 })
1277 }
1278}
1279
1280#[derive(Debug, Clone, Serialize, Deserialize)]
1282pub struct MarketTradingSession {
1283 pub market: Market,
1285 pub trade_sessions: Vec<TradingSessionInfo>,
1287}
1288
1289impl TryFrom<quote::MarketTradePeriod> for MarketTradingSession {
1290 type Error = Error;
1291
1292 fn try_from(value: quote::MarketTradePeriod) -> Result<Self> {
1293 Ok(Self {
1294 market: value.market.parse().unwrap_or_default(),
1295 trade_sessions: value
1296 .trade_session
1297 .into_iter()
1298 .map(TryInto::try_into)
1299 .collect::<Result<Vec<_>>>()?,
1300 })
1301 }
1302}
1303
1304#[derive(Debug, Clone, Serialize, Deserialize)]
1306pub struct MarketTradingDays {
1307 pub trading_days: Vec<Date>,
1309 pub half_trading_days: Vec<Date>,
1311}
1312
1313#[derive(Debug, Clone, Serialize, Deserialize)]
1315pub struct CapitalFlowLine {
1316 pub inflow: Decimal,
1318 pub timestamp: OffsetDateTime,
1320}
1321
1322impl TryFrom<quote::capital_flow_intraday_response::CapitalFlowLine> for CapitalFlowLine {
1323 type Error = Error;
1324
1325 fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result<Self> {
1326 Ok(Self {
1327 inflow: value.inflow.parse().unwrap_or_default(),
1328 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1329 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1330 })
1331 }
1332}
1333
1334#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1336pub struct CapitalDistribution {
1337 pub large: Decimal,
1339 pub medium: Decimal,
1341 pub small: Decimal,
1343}
1344
1345impl TryFrom<quote::capital_distribution_response::CapitalDistribution> for CapitalDistribution {
1346 type Error = Error;
1347
1348 fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result<Self> {
1349 Ok(Self {
1350 large: value.large.parse().unwrap_or_default(),
1351 medium: value.medium.parse().unwrap_or_default(),
1352 small: value.small.parse().unwrap_or_default(),
1353 })
1354 }
1355}
1356
1357#[derive(Debug, Clone, Serialize, Deserialize)]
1359pub struct CapitalDistributionResponse {
1360 pub timestamp: OffsetDateTime,
1362 pub capital_in: CapitalDistribution,
1364 pub capital_out: CapitalDistribution,
1366}
1367
1368impl TryFrom<quote::CapitalDistributionResponse> for CapitalDistributionResponse {
1369 type Error = Error;
1370
1371 fn try_from(value: quote::CapitalDistributionResponse) -> Result<Self> {
1372 Ok(Self {
1373 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1374 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1375 capital_in: value
1376 .capital_in
1377 .map(TryInto::try_into)
1378 .transpose()?
1379 .unwrap_or_default(),
1380 capital_out: value
1381 .capital_out
1382 .map(TryInto::try_into)
1383 .transpose()?
1384 .unwrap_or_default(),
1385 })
1386 }
1387}
1388
1389#[derive(Debug, Clone, Serialize, Deserialize)]
1391pub struct WatchlistSecurity {
1392 pub symbol: String,
1394 pub market: Market,
1396 pub name: String,
1398 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
1400 pub watched_price: Option<Decimal>,
1401 #[serde(
1403 serialize_with = "time::serde::rfc3339::serialize",
1404 deserialize_with = "serde_utils::timestamp::deserialize"
1405 )]
1406 pub watched_at: OffsetDateTime,
1407}
1408
1409#[derive(Debug, Clone, Serialize, Deserialize)]
1411pub struct WatchlistGroup {
1412 #[serde(with = "serde_utils::int64_str")]
1414 pub id: i64,
1415 pub name: String,
1417 pub securities: Vec<WatchlistSecurity>,
1419}
1420
1421#[derive(Debug, Clone)]
1423pub struct RequestCreateWatchlistGroup {
1424 pub name: String,
1426 pub securities: Option<Vec<String>>,
1428}
1429
1430impl RequestCreateWatchlistGroup {
1431 pub fn new(name: impl Into<String>) -> Self {
1433 Self {
1434 name: name.into(),
1435 securities: None,
1436 }
1437 }
1438
1439 pub fn securities<I, T>(self, securities: I) -> Self
1441 where
1442 I: IntoIterator<Item = T>,
1443 T: Into<String>,
1444 {
1445 Self {
1446 securities: Some(securities.into_iter().map(Into::into).collect()),
1447 ..self
1448 }
1449 }
1450}
1451
1452#[derive(Debug, Copy, Clone, Default, Serialize)]
1454#[serde(rename_all = "lowercase")]
1455pub enum SecuritiesUpdateMode {
1456 Add,
1458 Remove,
1460 #[default]
1462 Replace,
1463}
1464
1465#[derive(Debug, Clone)]
1467pub struct RequestUpdateWatchlistGroup {
1468 pub id: i64,
1470 pub name: Option<String>,
1472 pub securities: Option<Vec<String>>,
1474 pub mode: SecuritiesUpdateMode,
1476}
1477
1478impl RequestUpdateWatchlistGroup {
1479 #[inline]
1481 pub fn new(id: i64) -> Self {
1482 Self {
1483 id,
1484 name: None,
1485 securities: None,
1486 mode: SecuritiesUpdateMode::default(),
1487 }
1488 }
1489
1490 pub fn name(self, name: impl Into<String>) -> Self {
1492 Self {
1493 name: Some(name.into()),
1494 ..self
1495 }
1496 }
1497
1498 pub fn securities<I, T>(self, securities: I) -> Self
1500 where
1501 I: IntoIterator<Item = T>,
1502 T: Into<String>,
1503 {
1504 Self {
1505 securities: Some(securities.into_iter().map(Into::into).collect()),
1506 ..self
1507 }
1508 }
1509
1510 pub fn mode(self, mode: SecuritiesUpdateMode) -> Self {
1512 Self { mode, ..self }
1513 }
1514}
1515
1516#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1518pub enum CalcIndex {
1519 LastDone,
1521 ChangeValue,
1523 ChangeRate,
1525 Volume,
1527 Turnover,
1529 YtdChangeRate,
1531 TurnoverRate,
1533 TotalMarketValue,
1535 CapitalFlow,
1537 Amplitude,
1539 VolumeRatio,
1541 PeTtmRatio,
1543 PbRatio,
1545 DividendRatioTtm,
1547 FiveDayChangeRate,
1549 TenDayChangeRate,
1551 HalfYearChangeRate,
1553 FiveMinutesChangeRate,
1555 ExpiryDate,
1557 StrikePrice,
1559 UpperStrikePrice,
1561 LowerStrikePrice,
1563 OutstandingQty,
1565 OutstandingRatio,
1567 Premium,
1569 ItmOtm,
1571 ImpliedVolatility,
1573 WarrantDelta,
1575 CallPrice,
1577 ToCallPrice,
1579 EffectiveLeverage,
1581 LeverageRatio,
1583 ConversionRatio,
1585 BalancePoint,
1587 OpenInterest,
1589 Delta,
1591 Gamma,
1593 Theta,
1595 Vega,
1597 Rho,
1599}
1600
1601impl From<CalcIndex> for longport_proto::quote::CalcIndex {
1602 fn from(value: CalcIndex) -> Self {
1603 use longport_proto::quote::CalcIndex::*;
1604
1605 match value {
1606 CalcIndex::LastDone => CalcindexLastDone,
1607 CalcIndex::ChangeValue => CalcindexChangeVal,
1608 CalcIndex::ChangeRate => CalcindexChangeRate,
1609 CalcIndex::Volume => CalcindexVolume,
1610 CalcIndex::Turnover => CalcindexTurnover,
1611 CalcIndex::YtdChangeRate => CalcindexYtdChangeRate,
1612 CalcIndex::TurnoverRate => CalcindexTurnoverRate,
1613 CalcIndex::TotalMarketValue => CalcindexTotalMarketValue,
1614 CalcIndex::CapitalFlow => CalcindexCapitalFlow,
1615 CalcIndex::Amplitude => CalcindexAmplitude,
1616 CalcIndex::VolumeRatio => CalcindexVolumeRatio,
1617 CalcIndex::PeTtmRatio => CalcindexPeTtmRatio,
1618 CalcIndex::PbRatio => CalcindexPbRatio,
1619 CalcIndex::DividendRatioTtm => CalcindexDividendRatioTtm,
1620 CalcIndex::FiveDayChangeRate => CalcindexFiveDayChangeRate,
1621 CalcIndex::TenDayChangeRate => CalcindexTenDayChangeRate,
1622 CalcIndex::HalfYearChangeRate => CalcindexHalfYearChangeRate,
1623 CalcIndex::FiveMinutesChangeRate => CalcindexFiveMinutesChangeRate,
1624 CalcIndex::ExpiryDate => CalcindexExpiryDate,
1625 CalcIndex::StrikePrice => CalcindexStrikePrice,
1626 CalcIndex::UpperStrikePrice => CalcindexUpperStrikePrice,
1627 CalcIndex::LowerStrikePrice => CalcindexLowerStrikePrice,
1628 CalcIndex::OutstandingQty => CalcindexOutstandingQty,
1629 CalcIndex::OutstandingRatio => CalcindexOutstandingRatio,
1630 CalcIndex::Premium => CalcindexPremium,
1631 CalcIndex::ItmOtm => CalcindexItmOtm,
1632 CalcIndex::ImpliedVolatility => CalcindexImpliedVolatility,
1633 CalcIndex::WarrantDelta => CalcindexWarrantDelta,
1634 CalcIndex::CallPrice => CalcindexCallPrice,
1635 CalcIndex::ToCallPrice => CalcindexToCallPrice,
1636 CalcIndex::EffectiveLeverage => CalcindexEffectiveLeverage,
1637 CalcIndex::LeverageRatio => CalcindexLeverageRatio,
1638 CalcIndex::ConversionRatio => CalcindexConversionRatio,
1639 CalcIndex::BalancePoint => CalcindexBalancePoint,
1640 CalcIndex::OpenInterest => CalcindexOpenInterest,
1641 CalcIndex::Delta => CalcindexDelta,
1642 CalcIndex::Gamma => CalcindexGamma,
1643 CalcIndex::Theta => CalcindexTheta,
1644 CalcIndex::Vega => CalcindexVega,
1645 CalcIndex::Rho => CalcindexRho,
1646 }
1647 }
1648}
1649
1650#[derive(Debug, Clone, Serialize, Deserialize)]
1652pub struct SecurityCalcIndex {
1653 pub symbol: String,
1655 pub last_done: Option<Decimal>,
1657 pub change_value: Option<Decimal>,
1659 pub change_rate: Option<Decimal>,
1661 pub volume: Option<i64>,
1663 pub turnover: Option<Decimal>,
1665 pub ytd_change_rate: Option<Decimal>,
1667 pub turnover_rate: Option<Decimal>,
1669 pub total_market_value: Option<Decimal>,
1671 pub capital_flow: Option<Decimal>,
1673 pub amplitude: Option<Decimal>,
1675 pub volume_ratio: Option<Decimal>,
1677 pub pe_ttm_ratio: Option<Decimal>,
1679 pub pb_ratio: Option<Decimal>,
1681 pub dividend_ratio_ttm: Option<Decimal>,
1683 pub five_day_change_rate: Option<Decimal>,
1685 pub ten_day_change_rate: Option<Decimal>,
1687 pub half_year_change_rate: Option<Decimal>,
1689 pub five_minutes_change_rate: Option<Decimal>,
1691 pub expiry_date: Option<Date>,
1693 pub strike_price: Option<Decimal>,
1695 pub upper_strike_price: Option<Decimal>,
1697 pub lower_strike_price: Option<Decimal>,
1699 pub outstanding_qty: Option<i64>,
1701 pub outstanding_ratio: Option<Decimal>,
1703 pub premium: Option<Decimal>,
1705 pub itm_otm: Option<Decimal>,
1707 pub implied_volatility: Option<Decimal>,
1709 pub warrant_delta: Option<Decimal>,
1711 pub call_price: Option<Decimal>,
1713 pub to_call_price: Option<Decimal>,
1715 pub effective_leverage: Option<Decimal>,
1717 pub leverage_ratio: Option<Decimal>,
1719 pub conversion_ratio: Option<Decimal>,
1721 pub balance_point: Option<Decimal>,
1723 pub open_interest: Option<i64>,
1725 pub delta: Option<Decimal>,
1727 pub gamma: Option<Decimal>,
1729 pub theta: Option<Decimal>,
1731 pub vega: Option<Decimal>,
1733 pub rho: Option<Decimal>,
1735}
1736
1737impl SecurityCalcIndex {
1738 pub(crate) fn from_proto(
1739 resp: longport_proto::quote::SecurityCalcIndex,
1740 indexes: &[CalcIndex],
1741 ) -> Self {
1742 let mut output = SecurityCalcIndex {
1743 symbol: resp.symbol,
1744 last_done: None,
1745 change_value: None,
1746 change_rate: None,
1747 volume: None,
1748 turnover: None,
1749 ytd_change_rate: None,
1750 turnover_rate: None,
1751 total_market_value: None,
1752 capital_flow: None,
1753 amplitude: None,
1754 volume_ratio: None,
1755 pe_ttm_ratio: None,
1756 pb_ratio: None,
1757 dividend_ratio_ttm: None,
1758 five_day_change_rate: None,
1759 ten_day_change_rate: None,
1760 half_year_change_rate: None,
1761 five_minutes_change_rate: None,
1762 expiry_date: None,
1763 strike_price: None,
1764 upper_strike_price: None,
1765 lower_strike_price: None,
1766 outstanding_qty: None,
1767 outstanding_ratio: None,
1768 premium: None,
1769 itm_otm: None,
1770 implied_volatility: None,
1771 warrant_delta: None,
1772 call_price: None,
1773 to_call_price: None,
1774 effective_leverage: None,
1775 leverage_ratio: None,
1776 conversion_ratio: None,
1777 balance_point: None,
1778 open_interest: None,
1779 delta: None,
1780 gamma: None,
1781 theta: None,
1782 vega: None,
1783 rho: None,
1784 };
1785
1786 for index in indexes {
1787 match index {
1788 CalcIndex::LastDone => output.last_done = resp.last_done.parse().ok(),
1789 CalcIndex::ChangeValue => output.change_value = resp.change_val.parse().ok(),
1790 CalcIndex::ChangeRate => output.change_rate = resp.change_rate.parse().ok(),
1791 CalcIndex::Volume => output.volume = Some(resp.volume),
1792 CalcIndex::Turnover => output.turnover = resp.turnover.parse().ok(),
1793 CalcIndex::YtdChangeRate => {
1794 output.ytd_change_rate = resp.ytd_change_rate.parse().ok()
1795 }
1796 CalcIndex::TurnoverRate => output.turnover_rate = resp.turnover_rate.parse().ok(),
1797 CalcIndex::TotalMarketValue => {
1798 output.total_market_value = resp.total_market_value.parse().ok()
1799 }
1800 CalcIndex::CapitalFlow => output.capital_flow = resp.capital_flow.parse().ok(),
1801 CalcIndex::Amplitude => output.amplitude = resp.amplitude.parse().ok(),
1802 CalcIndex::VolumeRatio => output.volume_ratio = resp.volume_ratio.parse().ok(),
1803 CalcIndex::PeTtmRatio => output.pe_ttm_ratio = resp.pe_ttm_ratio.parse().ok(),
1804 CalcIndex::PbRatio => output.pb_ratio = resp.pb_ratio.parse().ok(),
1805 CalcIndex::DividendRatioTtm => {
1806 output.dividend_ratio_ttm = resp.dividend_ratio_ttm.parse().ok()
1807 }
1808 CalcIndex::FiveDayChangeRate => {
1809 output.five_day_change_rate = resp.five_day_change_rate.parse().ok()
1810 }
1811 CalcIndex::TenDayChangeRate => {
1812 output.ten_day_change_rate = resp.ten_day_change_rate.parse().ok()
1813 }
1814 CalcIndex::HalfYearChangeRate => {
1815 output.half_year_change_rate = resp.half_year_change_rate.parse().ok()
1816 }
1817 CalcIndex::FiveMinutesChangeRate => {
1818 output.five_minutes_change_rate = resp.five_minutes_change_rate.parse().ok()
1819 }
1820 CalcIndex::ExpiryDate => output.expiry_date = parse_date(&resp.expiry_date).ok(),
1821 CalcIndex::StrikePrice => output.strike_price = resp.strike_price.parse().ok(),
1822 CalcIndex::UpperStrikePrice => {
1823 output.upper_strike_price = resp.upper_strike_price.parse().ok()
1824 }
1825 CalcIndex::LowerStrikePrice => {
1826 output.lower_strike_price = resp.lower_strike_price.parse().ok()
1827 }
1828 CalcIndex::OutstandingQty => output.outstanding_qty = Some(resp.outstanding_qty),
1829 CalcIndex::OutstandingRatio => {
1830 output.outstanding_ratio = resp.outstanding_ratio.parse().ok()
1831 }
1832 CalcIndex::Premium => output.premium = resp.premium.parse().ok(),
1833 CalcIndex::ItmOtm => output.itm_otm = resp.itm_otm.parse().ok(),
1834 CalcIndex::ImpliedVolatility => {
1835 output.implied_volatility = resp.implied_volatility.parse().ok()
1836 }
1837 CalcIndex::WarrantDelta => output.warrant_delta = resp.warrant_delta.parse().ok(),
1838 CalcIndex::CallPrice => output.call_price = resp.call_price.parse().ok(),
1839 CalcIndex::ToCallPrice => output.to_call_price = resp.to_call_price.parse().ok(),
1840 CalcIndex::EffectiveLeverage => {
1841 output.effective_leverage = resp.effective_leverage.parse().ok()
1842 }
1843 CalcIndex::LeverageRatio => {
1844 output.leverage_ratio = resp.leverage_ratio.parse().ok()
1845 }
1846 CalcIndex::ConversionRatio => {
1847 output.conversion_ratio = resp.conversion_ratio.parse().ok()
1848 }
1849 CalcIndex::BalancePoint => output.balance_point = resp.balance_point.parse().ok(),
1850 CalcIndex::OpenInterest => output.open_interest = Some(resp.open_interest),
1851 CalcIndex::Delta => output.delta = resp.delta.parse().ok(),
1852 CalcIndex::Gamma => output.gamma = resp.gamma.parse().ok(),
1853 CalcIndex::Theta => output.theta = resp.theta.parse().ok(),
1854 CalcIndex::Vega => output.vega = resp.vega.parse().ok(),
1855 CalcIndex::Rho => output.rho = resp.rho.parse().ok(),
1856 }
1857 }
1858
1859 output
1860 }
1861}
1862
1863#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1865pub enum SecurityListCategory {
1866 Overnight,
1868}
1869
1870impl_serialize_for_enum_string!(SecurityListCategory);
1871
1872#[derive(Debug, Serialize, Deserialize)]
1874pub struct Security {
1875 pub symbol: String,
1877 pub name_cn: String,
1879 pub name_en: String,
1881 pub name_hk: String,
1883}
1884
1885#[derive(Debug, Clone)]
1887pub struct QuotePackageDetail {
1888 pub key: String,
1890 pub name: String,
1892 pub description: String,
1894 pub start_at: OffsetDateTime,
1896 pub end_at: OffsetDateTime,
1898}
1899
1900impl TryFrom<quote::user_quote_level_detail::PackageDetail> for QuotePackageDetail {
1901 type Error = Error;
1902
1903 fn try_from(quote: quote::user_quote_level_detail::PackageDetail) -> Result<Self> {
1904 Ok(Self {
1905 key: quote.key,
1906 name: quote.name,
1907 description: quote.description,
1908 start_at: OffsetDateTime::from_unix_timestamp(quote.start)
1909 .map_err(|err| Error::parse_field_error("start_at", err))?,
1910 end_at: OffsetDateTime::from_unix_timestamp(quote.end)
1911 .map_err(|err| Error::parse_field_error("end_at", err))?,
1912 })
1913 }
1914}
1915
1916#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1918#[repr(i32)]
1919pub enum TradeSessions {
1920 Intraday = 0,
1922 All = 100,
1924}
1925
1926impl TradeSessions {
1927 #[inline]
1928 pub(crate) fn contains(&self, session: TradeSession) -> bool {
1929 match self {
1930 TradeSessions::Intraday => session == TradeSession::Intraday,
1931 TradeSessions::All => true,
1932 }
1933 }
1934}
1935
1936#[derive(Debug, Clone, Serialize, Deserialize)]
1938pub struct MarketTemperature {
1939 pub temperature: i32,
1941 #[serde(default)]
1943 pub description: String,
1944 pub valuation: i32,
1946 pub sentiment: i32,
1948 #[serde(
1950 serialize_with = "time::serde::rfc3339::serialize",
1951 deserialize_with = "serde_utils::timestamp::deserialize",
1952 alias = "updated_at"
1953 )]
1954 pub timestamp: OffsetDateTime,
1955}
1956
1957#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1959pub enum Granularity {
1960 Unknown,
1962 #[strum(serialize = "daily")]
1964 Daily,
1965 #[strum(serialize = "weekly")]
1967 Weekly,
1968 #[strum(serialize = "monthly")]
1970 Monthly,
1971}
1972
1973#[derive(Debug, Clone, Serialize, Deserialize)]
1975pub struct HistoryMarketTemperatureResponse {
1976 #[serde(rename = "type")]
1978 pub granularity: Granularity,
1979 #[serde(rename = "list")]
1981 pub records: Vec<MarketTemperature>,
1982}
1983
1984impl_serde_for_enum_string!(Granularity);
1985impl_default_for_enum_string!(
1986 OptionType,
1987 OptionDirection,
1988 WarrantType,
1989 SecurityBoard,
1990 Granularity
1991);
1992
1993#[cfg(test)]
1994mod tests {
1995 use serde::Deserialize;
1996
1997 use crate::{Market, quote::WatchlistGroup};
1998
1999 #[test]
2000 fn watch_list() {
2001 #[derive(Debug, Deserialize)]
2002 struct Response {
2003 groups: Vec<WatchlistGroup>,
2004 }
2005
2006 let json = r#"
2007 {
2008 "groups": [
2009 {
2010 "id": "1",
2011 "name": "Test",
2012 "securities": [
2013 {
2014 "symbol": "AAPL",
2015 "market": "US",
2016 "name": "Apple Inc.",
2017 "watched_price": "150.0",
2018 "watched_at": "1633036800"
2019 }
2020 ]
2021 }
2022 ]
2023 }
2024 "#;
2025
2026 let response: Response = serde_json::from_str(json).unwrap();
2027 assert_eq!(response.groups.len(), 1);
2028 assert_eq!(response.groups[0].id, 1);
2029 assert_eq!(response.groups[0].name, "Test");
2030 assert_eq!(response.groups[0].securities.len(), 1);
2031 assert_eq!(response.groups[0].securities[0].symbol, "AAPL");
2032 assert_eq!(response.groups[0].securities[0].market, Market::US);
2033 assert_eq!(response.groups[0].securities[0].name, "Apple Inc.");
2034 assert_eq!(
2035 response.groups[0].securities[0].watched_price,
2036 Some(decimal!(150.0))
2037 );
2038 assert_eq!(
2039 response.groups[0].securities[0].watched_at.unix_timestamp(),
2040 1633036800
2041 );
2042 }
2043}