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}
287
288#[derive(Debug, Serialize, Deserialize)]
290pub struct SecurityStaticInfo {
291 pub symbol: String,
293 pub name_cn: String,
295 pub name_en: String,
297 pub name_hk: String,
299 pub exchange: String,
301 pub currency: String,
303 pub lot_size: i32,
305 pub total_shares: i64,
307 pub circulating_shares: i64,
309 pub hk_shares: i64,
311 pub eps: Decimal,
313 pub eps_ttm: Decimal,
315 pub bps: Decimal,
317 pub dividend_yield: Decimal,
319 pub stock_derivatives: DerivativeType,
321 pub board: SecurityBoard,
323}
324
325impl TryFrom<quote::StaticInfo> for SecurityStaticInfo {
326 type Error = Error;
327
328 fn try_from(resp: quote::StaticInfo) -> Result<Self> {
329 Ok(SecurityStaticInfo {
330 symbol: resp.symbol,
331 name_cn: resp.name_cn,
332 name_en: resp.name_en,
333 name_hk: resp.name_hk,
334 exchange: resp.exchange,
335 currency: resp.currency,
336 lot_size: resp.lot_size,
337 total_shares: resp.total_shares,
338 circulating_shares: resp.circulating_shares,
339 hk_shares: resp.hk_shares,
340 eps: resp.eps.parse().unwrap_or_default(),
341 eps_ttm: resp.eps_ttm.parse().unwrap_or_default(),
342 bps: resp.bps.parse().unwrap_or_default(),
343 dividend_yield: resp.dividend_yield.parse().unwrap_or_default(),
344 stock_derivatives: resp.stock_derivatives.into_iter().fold(
345 DerivativeType::empty(),
346 |acc, value| match value {
347 1 => acc | DerivativeType::OPTION,
348 2 => acc | DerivativeType::WARRANT,
349 _ => acc,
350 },
351 ),
352 board: resp.board.parse().unwrap_or(SecurityBoard::Unknown),
353 })
354 }
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct RealtimeQuote {
360 pub symbol: String,
362 pub last_done: Decimal,
364 pub open: Decimal,
366 pub high: Decimal,
368 pub low: Decimal,
370 pub timestamp: OffsetDateTime,
372 pub volume: i64,
374 pub turnover: Decimal,
376 pub trade_status: TradeStatus,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct PrePostQuote {
383 pub last_done: Decimal,
385 #[serde(with = "time::serde::rfc3339")]
387 pub timestamp: OffsetDateTime,
388 pub volume: i64,
390 pub turnover: Decimal,
392 pub high: Decimal,
394 pub low: Decimal,
396 pub prev_close: Decimal,
398}
399
400impl TryFrom<quote::PrePostQuote> for PrePostQuote {
401 type Error = Error;
402
403 fn try_from(quote: quote::PrePostQuote) -> Result<Self> {
404 Ok(Self {
405 last_done: quote.last_done.parse().unwrap_or_default(),
406 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
407 .map_err(|err| Error::parse_field_error("timestamp", err))?,
408 volume: quote.volume,
409 turnover: quote.turnover.parse().unwrap_or_default(),
410 high: quote.high.parse().unwrap_or_default(),
411 low: quote.low.parse().unwrap_or_default(),
412 prev_close: quote.prev_close.parse().unwrap_or_default(),
413 })
414 }
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct SecurityQuote {
420 pub symbol: String,
422 pub last_done: Decimal,
424 pub prev_close: Decimal,
426 pub open: Decimal,
428 pub high: Decimal,
430 pub low: Decimal,
432 #[serde(with = "time::serde::rfc3339")]
434 pub timestamp: OffsetDateTime,
435 pub volume: i64,
437 pub turnover: Decimal,
439 pub trade_status: TradeStatus,
441 pub pre_market_quote: Option<PrePostQuote>,
443 pub post_market_quote: Option<PrePostQuote>,
445 pub overnight_quote: Option<PrePostQuote>,
447}
448
449impl TryFrom<quote::SecurityQuote> for SecurityQuote {
450 type Error = Error;
451
452 fn try_from(quote: quote::SecurityQuote) -> Result<Self> {
453 Ok(Self {
454 symbol: quote.symbol,
455 last_done: quote.last_done.parse().unwrap_or_default(),
456 prev_close: quote.prev_close.parse().unwrap_or_default(),
457 open: quote.open.parse().unwrap_or_default(),
458 high: quote.high.parse().unwrap_or_default(),
459 low: quote.low.parse().unwrap_or_default(),
460 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
461 .map_err(|err| Error::parse_field_error("timestamp", err))?,
462 volume: quote.volume,
463 turnover: quote.turnover.parse().unwrap_or_default(),
464 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
465 pre_market_quote: quote.pre_market_quote.map(TryInto::try_into).transpose()?,
466 post_market_quote: quote.post_market_quote.map(TryInto::try_into).transpose()?,
467 overnight_quote: quote.over_night_quote.map(TryInto::try_into).transpose()?,
468 })
469 }
470}
471
472#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
474pub enum OptionType {
475 Unknown,
477 #[strum(serialize = "A")]
479 American,
480 #[strum(serialize = "U")]
482 Europe,
483}
484
485#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
487pub enum OptionDirection {
488 Unknown,
490 #[strum(serialize = "P")]
492 Put,
493 #[strum(serialize = "C")]
495 Call,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct OptionQuote {
501 pub symbol: String,
503 pub last_done: Decimal,
505 pub prev_close: Decimal,
507 pub open: Decimal,
509 pub high: Decimal,
511 pub low: Decimal,
513 #[serde(with = "time::serde::rfc3339")]
515 pub timestamp: OffsetDateTime,
516 pub volume: i64,
518 pub turnover: Decimal,
520 pub trade_status: TradeStatus,
522 pub implied_volatility: Decimal,
524 pub open_interest: i64,
526 pub expiry_date: Date,
528 pub strike_price: Decimal,
530 pub contract_multiplier: Decimal,
532 pub contract_type: OptionType,
534 pub contract_size: Decimal,
536 pub direction: OptionDirection,
538 pub historical_volatility: Decimal,
540 pub underlying_symbol: String,
542}
543
544impl TryFrom<quote::OptionQuote> for OptionQuote {
545 type Error = Error;
546
547 fn try_from(quote: quote::OptionQuote) -> Result<Self> {
548 let option_extend = quote.option_extend.unwrap_or_default();
549
550 Ok(Self {
551 symbol: quote.symbol,
552 last_done: quote.last_done.parse().unwrap_or_default(),
553 prev_close: quote.prev_close.parse().unwrap_or_default(),
554 open: quote.open.parse().unwrap_or_default(),
555 high: quote.high.parse().unwrap_or_default(),
556 low: quote.low.parse().unwrap_or_default(),
557 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
558 .map_err(|err| Error::parse_field_error("timestamp", err))?,
559 volume: quote.volume,
560 turnover: quote.turnover.parse().unwrap_or_default(),
561 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
562 implied_volatility: option_extend.implied_volatility.parse().unwrap_or_default(),
563 open_interest: option_extend.open_interest,
564 expiry_date: parse_date(&option_extend.expiry_date)
565 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
566 strike_price: option_extend.strike_price.parse().unwrap_or_default(),
567 contract_multiplier: option_extend
568 .contract_multiplier
569 .parse()
570 .unwrap_or_default(),
571 contract_type: option_extend.contract_type.parse().unwrap_or_default(),
572 contract_size: option_extend.contract_size.parse().unwrap_or_default(),
573 direction: option_extend.direction.parse().unwrap_or_default(),
574 historical_volatility: option_extend
575 .historical_volatility
576 .parse()
577 .unwrap_or_default(),
578 underlying_symbol: option_extend.underlying_symbol,
579 })
580 }
581}
582
583#[derive(
585 Debug,
586 Copy,
587 Clone,
588 Hash,
589 Eq,
590 PartialEq,
591 EnumString,
592 IntoPrimitive,
593 TryFromPrimitive,
594 Serialize,
595 Deserialize,
596)]
597#[repr(i32)]
598pub enum WarrantType {
599 Unknown = -1,
601 Call = 0,
603 Put = 1,
605 Bull = 2,
607 Bear = 3,
609 Inline = 4,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct WarrantQuote {
616 pub symbol: String,
618 pub last_done: Decimal,
620 pub prev_close: Decimal,
622 pub open: Decimal,
624 pub high: Decimal,
626 pub low: Decimal,
628 #[serde(with = "time::serde::rfc3339")]
630 pub timestamp: OffsetDateTime,
631 pub volume: i64,
633 pub turnover: Decimal,
635 pub trade_status: TradeStatus,
637 pub implied_volatility: Decimal,
639 pub expiry_date: Date,
641 pub last_trade_date: Date,
643 pub outstanding_ratio: Decimal,
645 pub outstanding_quantity: i64,
647 pub conversion_ratio: Decimal,
649 pub category: WarrantType,
651 pub strike_price: Decimal,
653 pub upper_strike_price: Decimal,
655 pub lower_strike_price: Decimal,
657 pub call_price: Decimal,
659 pub underlying_symbol: String,
661}
662
663impl TryFrom<quote::WarrantQuote> for WarrantQuote {
664 type Error = Error;
665
666 fn try_from(quote: quote::WarrantQuote) -> Result<Self> {
667 let warrant_extend = quote.warrant_extend.unwrap_or_default();
668
669 Ok(Self {
670 symbol: quote.symbol,
671 last_done: quote.last_done.parse().unwrap_or_default(),
672 prev_close: quote.prev_close.parse().unwrap_or_default(),
673 open: quote.open.parse().unwrap_or_default(),
674 high: quote.high.parse().unwrap_or_default(),
675 low: quote.low.parse().unwrap_or_default(),
676 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
677 .map_err(|err| Error::parse_field_error("timestamp", err))?,
678 volume: quote.volume,
679 turnover: quote.turnover.parse().unwrap_or_default(),
680 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
681 implied_volatility: warrant_extend
682 .implied_volatility
683 .parse()
684 .unwrap_or_default(),
685 expiry_date: parse_date(&warrant_extend.expiry_date)
686 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
687 last_trade_date: parse_date(&warrant_extend.last_trade_date)
688 .map_err(|err| Error::parse_field_error("last_trade_date", err))?,
689 outstanding_ratio: warrant_extend.outstanding_ratio.parse().unwrap_or_default(),
690 outstanding_quantity: warrant_extend.outstanding_qty,
691 conversion_ratio: warrant_extend.conversion_ratio.parse().unwrap_or_default(),
692 category: warrant_extend.category.parse().unwrap_or_default(),
693 strike_price: warrant_extend.strike_price.parse().unwrap_or_default(),
694 upper_strike_price: warrant_extend
695 .upper_strike_price
696 .parse()
697 .unwrap_or_default(),
698 lower_strike_price: warrant_extend
699 .lower_strike_price
700 .parse()
701 .unwrap_or_default(),
702 call_price: warrant_extend.call_price.parse().unwrap_or_default(),
703 underlying_symbol: warrant_extend.underlying_symbol,
704 })
705 }
706}
707
708#[derive(Debug, Clone, Default, Serialize, Deserialize)]
710pub struct SecurityDepth {
711 pub asks: Vec<Depth>,
713 pub bids: Vec<Depth>,
715}
716
717#[derive(Debug, Clone, Default, Serialize, Deserialize)]
719pub struct SecurityBrokers {
720 pub ask_brokers: Vec<Brokers>,
722 pub bid_brokers: Vec<Brokers>,
724}
725
726#[derive(Debug, Clone, Serialize, Deserialize)]
728pub struct ParticipantInfo {
729 pub broker_ids: Vec<i32>,
731 pub name_cn: String,
733 pub name_en: String,
735 pub name_hk: String,
737}
738
739impl From<quote::ParticipantInfo> for ParticipantInfo {
740 fn from(info: quote::ParticipantInfo) -> Self {
741 Self {
742 broker_ids: info.broker_ids,
743 name_cn: info.participant_name_cn,
744 name_en: info.participant_name_en,
745 name_hk: info.participant_name_hk,
746 }
747 }
748}
749
750#[derive(Debug, Clone, Serialize, Deserialize)]
752pub struct IntradayLine {
753 pub price: Decimal,
755 #[serde(with = "time::serde::rfc3339")]
757 pub timestamp: OffsetDateTime,
758 pub volume: i64,
760 pub turnover: Decimal,
762 pub avg_price: Decimal,
764}
765
766impl TryFrom<quote::Line> for IntradayLine {
767 type Error = Error;
768
769 fn try_from(value: quote::Line) -> Result<Self> {
770 Ok(Self {
771 price: value.price.parse().unwrap_or_default(),
772 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
773 .map_err(|err| Error::parse_field_error("timestamp", err))?,
774 volume: value.volume,
775 turnover: value.turnover.parse().unwrap_or_default(),
776 avg_price: value.avg_price.parse().unwrap_or_default(),
777 })
778 }
779}
780
781#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
783pub struct Candlestick {
784 pub close: Decimal,
786 pub open: Decimal,
788 pub low: Decimal,
790 pub high: Decimal,
792 pub volume: i64,
794 pub turnover: Decimal,
796 #[serde(with = "time::serde::rfc3339")]
798 pub timestamp: OffsetDateTime,
799 pub trade_session: TradeSession,
801}
802
803impl TryFrom<quote::Candlestick> for Candlestick {
804 type Error = Error;
805
806 fn try_from(value: quote::Candlestick) -> Result<Self> {
807 Ok(Self {
808 close: value.close.parse().unwrap_or_default(),
809 open: value.open.parse().unwrap_or_default(),
810 low: value.low.parse().unwrap_or_default(),
811 high: value.high.parse().unwrap_or_default(),
812 volume: value.volume,
813 turnover: value.turnover.parse().unwrap_or_default(),
814 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
815 .map_err(|err| Error::parse_field_error("timestamp", err))?,
816 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
817 .map_err(|err| Error::parse_field_error("trade_session", err))?
818 .into(),
819 })
820 }
821}
822
823impl longport_candlesticks::CandlestickType for Candlestick {
824 type PriceType = Decimal;
825 type VolumeType = i64;
826 type TurnoverType = Decimal;
827 type TradeSessionType = TradeSession;
828
829 #[inline]
830 fn new(
831 components: CandlestickComponents<
832 Self::PriceType,
833 Self::VolumeType,
834 Self::TurnoverType,
835 Self::TradeSessionType,
836 >,
837 ) -> Self {
838 Self {
839 timestamp: components.time,
840 open: components.open,
841 high: components.high,
842 low: components.low,
843 close: components.close,
844 volume: components.volume,
845 turnover: components.turnover,
846 trade_session: components.trade_session,
847 }
848 }
849
850 #[inline]
851 fn time(&self) -> OffsetDateTime {
852 self.timestamp
853 }
854
855 #[inline]
856 fn set_time(&mut self, time: OffsetDateTime) {
857 self.timestamp = time;
858 }
859
860 #[inline]
861 fn open(&self) -> Self::PriceType {
862 self.open
863 }
864
865 #[inline]
866 fn set_open(&mut self, open: Self::PriceType) {
867 self.open = open;
868 }
869
870 #[inline]
871 fn high(&self) -> Self::PriceType {
872 self.high
873 }
874
875 #[inline]
876 fn set_high(&mut self, high: Self::PriceType) {
877 self.high = high;
878 }
879
880 #[inline]
881 fn low(&self) -> Self::PriceType {
882 self.low
883 }
884
885 #[inline]
886 fn set_low(&mut self, low: Self::PriceType) {
887 self.low = low;
888 }
889
890 #[inline]
891 fn close(&self) -> Self::PriceType {
892 self.close
893 }
894
895 #[inline]
896 fn set_close(&mut self, close: Self::PriceType) {
897 self.close = close;
898 }
899
900 #[inline]
901 fn volume(&self) -> Self::VolumeType {
902 self.volume
903 }
904
905 #[inline]
906 fn set_volume(&mut self, volume: Self::VolumeType) {
907 self.volume = volume;
908 }
909
910 #[inline]
911 fn turnover(&self) -> Self::TurnoverType {
912 self.turnover
913 }
914
915 #[inline]
916 fn set_turnover(&mut self, turnover: Self::TurnoverType) {
917 self.turnover = turnover;
918 }
919
920 #[inline]
921 fn trade_session(&self) -> Self::TradeSessionType {
922 self.trade_session
923 }
924}
925
926#[derive(Debug, Clone, Serialize, Deserialize)]
928pub struct StrikePriceInfo {
929 pub price: Decimal,
931 pub call_symbol: String,
933 pub put_symbol: String,
935 pub standard: bool,
937}
938
939impl TryFrom<quote::StrikePriceInfo> for StrikePriceInfo {
940 type Error = Error;
941
942 fn try_from(value: quote::StrikePriceInfo) -> Result<Self> {
943 Ok(Self {
944 price: value.price.parse().unwrap_or_default(),
945 call_symbol: value.call_symbol,
946 put_symbol: value.put_symbol,
947 standard: value.standard,
948 })
949 }
950}
951
952#[derive(Debug, Clone, Serialize, Deserialize)]
954pub struct IssuerInfo {
955 pub issuer_id: i32,
957 pub name_cn: String,
959 pub name_en: String,
961 pub name_hk: String,
963}
964
965impl From<quote::IssuerInfo> for IssuerInfo {
966 fn from(info: quote::IssuerInfo) -> Self {
967 Self {
968 issuer_id: info.id,
969 name_cn: info.name_cn,
970 name_en: info.name_en,
971 name_hk: info.name_hk,
972 }
973 }
974}
975
976#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
978#[repr(i32)]
979pub enum SortOrderType {
980 Ascending = 0,
982 Descending = 1,
984}
985
986#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
988#[repr(i32)]
989pub enum WarrantSortBy {
990 LastDone = 0,
992 ChangeRate = 1,
994 ChangeValue = 2,
996 Volume = 3,
998 Turnover = 4,
1000 ExpiryDate = 5,
1002 StrikePrice = 6,
1004 UpperStrikePrice = 7,
1006 LowerStrikePrice = 8,
1008 OutstandingQuantity = 9,
1010 OutstandingRatio = 10,
1012 Premium = 11,
1014 ItmOtm = 12,
1016 ImpliedVolatility = 13,
1018 Delta = 14,
1020 CallPrice = 15,
1022 ToCallPrice = 16,
1024 EffectiveLeverage = 17,
1026 LeverageRatio = 18,
1028 ConversionRatio = 19,
1030 BalancePoint = 20,
1032 Status = 21,
1034}
1035
1036#[allow(non_camel_case_types)]
1038#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1039#[repr(i32)]
1040pub enum FilterWarrantExpiryDate {
1041 LT_3 = 1,
1043 Between_3_6 = 2,
1045 Between_6_12 = 3,
1047 GT_12 = 4,
1049}
1050
1051#[allow(non_camel_case_types)]
1053#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1054#[repr(i32)]
1055pub enum FilterWarrantInOutBoundsType {
1056 In = 1,
1058 Out = 2,
1060}
1061
1062#[derive(
1064 Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive, TryFromPrimitive, Serialize, Deserialize,
1065)]
1066#[repr(i32)]
1067pub enum WarrantStatus {
1068 Suspend = 2,
1070 PrepareList = 3,
1072 Normal = 4,
1074}
1075
1076#[derive(Debug, Clone, Serialize, Deserialize)]
1078pub struct WarrantInfo {
1079 pub symbol: String,
1081 pub warrant_type: WarrantType,
1083 pub name: String,
1085 pub last_done: Decimal,
1087 pub change_rate: Decimal,
1089 pub change_value: Decimal,
1091 pub volume: i64,
1093 pub turnover: Decimal,
1095 pub expiry_date: Date,
1097 pub strike_price: Option<Decimal>,
1099 pub upper_strike_price: Option<Decimal>,
1101 pub lower_strike_price: Option<Decimal>,
1103 pub outstanding_qty: i64,
1105 pub outstanding_ratio: Decimal,
1107 pub premium: Decimal,
1109 pub itm_otm: Option<Decimal>,
1111 pub implied_volatility: Option<Decimal>,
1113 pub delta: Option<Decimal>,
1115 pub call_price: Option<Decimal>,
1117 pub to_call_price: Option<Decimal>,
1119 pub effective_leverage: Option<Decimal>,
1121 pub leverage_ratio: Decimal,
1123 pub conversion_ratio: Option<Decimal>,
1125 pub balance_point: Option<Decimal>,
1127 pub status: WarrantStatus,
1129}
1130
1131impl TryFrom<quote::FilterWarrant> for WarrantInfo {
1132 type Error = Error;
1133
1134 fn try_from(info: quote::FilterWarrant) -> Result<Self> {
1135 let r#type = WarrantType::try_from(info.r#type)
1136 .map_err(|err| Error::parse_field_error("type", err))?;
1137
1138 match r#type {
1139 WarrantType::Unknown => unreachable!(),
1140 WarrantType::Call | WarrantType::Put => Ok(Self {
1141 symbol: info.symbol,
1142 warrant_type: r#type,
1143 name: info.name,
1144 last_done: info.last_done.parse().unwrap_or_default(),
1145 change_rate: info.change_rate.parse().unwrap_or_default(),
1146 change_value: info.change_val.parse().unwrap_or_default(),
1147 volume: info.volume,
1148 turnover: info.turnover.parse().unwrap_or_default(),
1149 expiry_date: parse_date(&info.expiry_date)
1150 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1151 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1152 upper_strike_price: None,
1153 lower_strike_price: None,
1154 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1155 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1156 premium: info.premium.parse().unwrap_or_default(),
1157 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1158 implied_volatility: Some(info.last_done.parse().unwrap_or_default()),
1159 delta: Some(info.last_done.parse().unwrap_or_default()),
1160 call_price: None,
1161 to_call_price: None,
1162 effective_leverage: Some(info.last_done.parse().unwrap_or_default()),
1163 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1164 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1165 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1166 status: WarrantStatus::try_from(info.status)
1167 .map_err(|err| Error::parse_field_error("state", err))?,
1168 }),
1169 WarrantType::Bull | WarrantType::Bear => Ok(Self {
1170 symbol: info.symbol,
1171 warrant_type: r#type,
1172 name: info.name,
1173 last_done: info.last_done.parse().unwrap_or_default(),
1174 change_rate: info.change_rate.parse().unwrap_or_default(),
1175 change_value: info.change_val.parse().unwrap_or_default(),
1176 volume: info.volume,
1177 turnover: info.turnover.parse().unwrap_or_default(),
1178 expiry_date: parse_date(&info.expiry_date)
1179 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1180 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1181 upper_strike_price: None,
1182 lower_strike_price: None,
1183 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1184 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1185 premium: info.premium.parse().unwrap_or_default(),
1186 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1187 implied_volatility: None,
1188 delta: None,
1189 call_price: Some(info.call_price.parse().unwrap_or_default()),
1190 to_call_price: Some(info.to_call_price.parse().unwrap_or_default()),
1191 effective_leverage: None,
1192 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1193 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1194 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1195 status: WarrantStatus::try_from(info.status)
1196 .map_err(|err| Error::parse_field_error("state", err))?,
1197 }),
1198 WarrantType::Inline => Ok(Self {
1199 symbol: info.symbol,
1200 warrant_type: r#type,
1201 name: info.name,
1202 last_done: info.last_done.parse().unwrap_or_default(),
1203 change_rate: info.change_rate.parse().unwrap_or_default(),
1204 change_value: info.change_val.parse().unwrap_or_default(),
1205 volume: info.volume,
1206 turnover: info.turnover.parse().unwrap_or_default(),
1207 expiry_date: parse_date(&info.expiry_date)
1208 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1209 strike_price: None,
1210 upper_strike_price: Some(info.upper_strike_price.parse().unwrap_or_default()),
1211 lower_strike_price: Some(info.lower_strike_price.parse().unwrap_or_default()),
1212 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1213 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1214 premium: info.premium.parse().unwrap_or_default(),
1215 itm_otm: None,
1216 implied_volatility: None,
1217 delta: None,
1218 call_price: None,
1219 to_call_price: None,
1220 effective_leverage: None,
1221 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1222 conversion_ratio: None,
1223 balance_point: None,
1224 status: WarrantStatus::try_from(info.status)
1225 .map_err(|err| Error::parse_field_error("state", err))?,
1226 }),
1227 }
1228 }
1229}
1230
1231#[derive(Debug, Clone, Serialize, Deserialize)]
1233pub struct TradingSessionInfo {
1234 pub begin_time: Time,
1236 pub end_time: Time,
1238 pub trade_session: TradeSession,
1240}
1241
1242impl TryFrom<quote::TradePeriod> for TradingSessionInfo {
1243 type Error = Error;
1244
1245 fn try_from(value: quote::TradePeriod) -> Result<Self> {
1246 #[inline]
1247 fn parse_time(value: i32) -> ::std::result::Result<Time, time::error::ComponentRange> {
1248 Time::from_hms(((value / 100) % 100) as u8, (value % 100) as u8, 0)
1249 }
1250
1251 Ok(Self {
1252 begin_time: parse_time(value.beg_time)
1253 .map_err(|err| Error::parse_field_error("beg_time", err))?,
1254 end_time: parse_time(value.end_time)
1255 .map_err(|err| Error::parse_field_error("end_time", err))?,
1256 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
1257 .unwrap_or_default()
1258 .into(),
1259 })
1260 }
1261}
1262
1263#[derive(Debug, Clone, Serialize, Deserialize)]
1265pub struct MarketTradingSession {
1266 pub market: Market,
1268 pub trade_sessions: Vec<TradingSessionInfo>,
1270}
1271
1272impl TryFrom<quote::MarketTradePeriod> for MarketTradingSession {
1273 type Error = Error;
1274
1275 fn try_from(value: quote::MarketTradePeriod) -> Result<Self> {
1276 Ok(Self {
1277 market: value.market.parse().unwrap_or_default(),
1278 trade_sessions: value
1279 .trade_session
1280 .into_iter()
1281 .map(TryInto::try_into)
1282 .collect::<Result<Vec<_>>>()?,
1283 })
1284 }
1285}
1286
1287#[derive(Debug, Clone, Serialize, Deserialize)]
1289pub struct MarketTradingDays {
1290 pub trading_days: Vec<Date>,
1292 pub half_trading_days: Vec<Date>,
1294}
1295
1296#[derive(Debug, Clone, Serialize, Deserialize)]
1298pub struct CapitalFlowLine {
1299 pub inflow: Decimal,
1301 pub timestamp: OffsetDateTime,
1303}
1304
1305impl TryFrom<quote::capital_flow_intraday_response::CapitalFlowLine> for CapitalFlowLine {
1306 type Error = Error;
1307
1308 fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result<Self> {
1309 Ok(Self {
1310 inflow: value.inflow.parse().unwrap_or_default(),
1311 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1312 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1313 })
1314 }
1315}
1316
1317#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1319pub struct CapitalDistribution {
1320 pub large: Decimal,
1322 pub medium: Decimal,
1324 pub small: Decimal,
1326}
1327
1328impl TryFrom<quote::capital_distribution_response::CapitalDistribution> for CapitalDistribution {
1329 type Error = Error;
1330
1331 fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result<Self> {
1332 Ok(Self {
1333 large: value.large.parse().unwrap_or_default(),
1334 medium: value.medium.parse().unwrap_or_default(),
1335 small: value.small.parse().unwrap_or_default(),
1336 })
1337 }
1338}
1339
1340#[derive(Debug, Clone, Serialize, Deserialize)]
1342pub struct CapitalDistributionResponse {
1343 pub timestamp: OffsetDateTime,
1345 pub capital_in: CapitalDistribution,
1347 pub capital_out: CapitalDistribution,
1349}
1350
1351impl TryFrom<quote::CapitalDistributionResponse> for CapitalDistributionResponse {
1352 type Error = Error;
1353
1354 fn try_from(value: quote::CapitalDistributionResponse) -> Result<Self> {
1355 Ok(Self {
1356 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1357 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1358 capital_in: value
1359 .capital_in
1360 .map(TryInto::try_into)
1361 .transpose()?
1362 .unwrap_or_default(),
1363 capital_out: value
1364 .capital_out
1365 .map(TryInto::try_into)
1366 .transpose()?
1367 .unwrap_or_default(),
1368 })
1369 }
1370}
1371
1372#[derive(Debug, Clone, Serialize, Deserialize)]
1374pub struct WatchlistSecurity {
1375 pub symbol: String,
1377 pub market: Market,
1379 pub name: String,
1381 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
1383 pub watched_price: Option<Decimal>,
1384 #[serde(
1386 serialize_with = "time::serde::rfc3339::serialize",
1387 deserialize_with = "serde_utils::timestamp::deserialize"
1388 )]
1389 pub watched_at: OffsetDateTime,
1390}
1391
1392#[derive(Debug, Clone, Serialize, Deserialize)]
1394pub struct WatchlistGroup {
1395 #[serde(with = "serde_utils::int64_str")]
1397 pub id: i64,
1398 pub name: String,
1400 pub securities: Vec<WatchlistSecurity>,
1402}
1403
1404#[derive(Debug, Clone)]
1406pub struct RequestCreateWatchlistGroup {
1407 pub name: String,
1409 pub securities: Option<Vec<String>>,
1411}
1412
1413impl RequestCreateWatchlistGroup {
1414 pub fn new(name: impl Into<String>) -> Self {
1416 Self {
1417 name: name.into(),
1418 securities: None,
1419 }
1420 }
1421
1422 pub fn securities<I, T>(self, securities: I) -> Self
1424 where
1425 I: IntoIterator<Item = T>,
1426 T: Into<String>,
1427 {
1428 Self {
1429 securities: Some(securities.into_iter().map(Into::into).collect()),
1430 ..self
1431 }
1432 }
1433}
1434
1435#[derive(Debug, Copy, Clone, Default, Serialize)]
1437#[serde(rename_all = "lowercase")]
1438pub enum SecuritiesUpdateMode {
1439 Add,
1441 Remove,
1443 #[default]
1445 Replace,
1446}
1447
1448#[derive(Debug, Clone)]
1450pub struct RequestUpdateWatchlistGroup {
1451 pub id: i64,
1453 pub name: Option<String>,
1455 pub securities: Option<Vec<String>>,
1457 pub mode: SecuritiesUpdateMode,
1459}
1460
1461impl RequestUpdateWatchlistGroup {
1462 #[inline]
1464 pub fn new(id: i64) -> Self {
1465 Self {
1466 id,
1467 name: None,
1468 securities: None,
1469 mode: SecuritiesUpdateMode::default(),
1470 }
1471 }
1472
1473 pub fn name(self, name: impl Into<String>) -> Self {
1475 Self {
1476 name: Some(name.into()),
1477 ..self
1478 }
1479 }
1480
1481 pub fn securities<I, T>(self, securities: I) -> Self
1483 where
1484 I: IntoIterator<Item = T>,
1485 T: Into<String>,
1486 {
1487 Self {
1488 securities: Some(securities.into_iter().map(Into::into).collect()),
1489 ..self
1490 }
1491 }
1492
1493 pub fn mode(self, mode: SecuritiesUpdateMode) -> Self {
1495 Self { mode, ..self }
1496 }
1497}
1498
1499#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1501pub enum CalcIndex {
1502 LastDone,
1504 ChangeValue,
1506 ChangeRate,
1508 Volume,
1510 Turnover,
1512 YtdChangeRate,
1514 TurnoverRate,
1516 TotalMarketValue,
1518 CapitalFlow,
1520 Amplitude,
1522 VolumeRatio,
1524 PeTtmRatio,
1526 PbRatio,
1528 DividendRatioTtm,
1530 FiveDayChangeRate,
1532 TenDayChangeRate,
1534 HalfYearChangeRate,
1536 FiveMinutesChangeRate,
1538 ExpiryDate,
1540 StrikePrice,
1542 UpperStrikePrice,
1544 LowerStrikePrice,
1546 OutstandingQty,
1548 OutstandingRatio,
1550 Premium,
1552 ItmOtm,
1554 ImpliedVolatility,
1556 WarrantDelta,
1558 CallPrice,
1560 ToCallPrice,
1562 EffectiveLeverage,
1564 LeverageRatio,
1566 ConversionRatio,
1568 BalancePoint,
1570 OpenInterest,
1572 Delta,
1574 Gamma,
1576 Theta,
1578 Vega,
1580 Rho,
1582}
1583
1584impl From<CalcIndex> for longport_proto::quote::CalcIndex {
1585 fn from(value: CalcIndex) -> Self {
1586 use longport_proto::quote::CalcIndex::*;
1587
1588 match value {
1589 CalcIndex::LastDone => CalcindexLastDone,
1590 CalcIndex::ChangeValue => CalcindexChangeVal,
1591 CalcIndex::ChangeRate => CalcindexChangeRate,
1592 CalcIndex::Volume => CalcindexVolume,
1593 CalcIndex::Turnover => CalcindexTurnover,
1594 CalcIndex::YtdChangeRate => CalcindexYtdChangeRate,
1595 CalcIndex::TurnoverRate => CalcindexTurnoverRate,
1596 CalcIndex::TotalMarketValue => CalcindexTotalMarketValue,
1597 CalcIndex::CapitalFlow => CalcindexCapitalFlow,
1598 CalcIndex::Amplitude => CalcindexAmplitude,
1599 CalcIndex::VolumeRatio => CalcindexVolumeRatio,
1600 CalcIndex::PeTtmRatio => CalcindexPeTtmRatio,
1601 CalcIndex::PbRatio => CalcindexPbRatio,
1602 CalcIndex::DividendRatioTtm => CalcindexDividendRatioTtm,
1603 CalcIndex::FiveDayChangeRate => CalcindexFiveDayChangeRate,
1604 CalcIndex::TenDayChangeRate => CalcindexTenDayChangeRate,
1605 CalcIndex::HalfYearChangeRate => CalcindexHalfYearChangeRate,
1606 CalcIndex::FiveMinutesChangeRate => CalcindexFiveMinutesChangeRate,
1607 CalcIndex::ExpiryDate => CalcindexExpiryDate,
1608 CalcIndex::StrikePrice => CalcindexStrikePrice,
1609 CalcIndex::UpperStrikePrice => CalcindexUpperStrikePrice,
1610 CalcIndex::LowerStrikePrice => CalcindexLowerStrikePrice,
1611 CalcIndex::OutstandingQty => CalcindexOutstandingQty,
1612 CalcIndex::OutstandingRatio => CalcindexOutstandingRatio,
1613 CalcIndex::Premium => CalcindexPremium,
1614 CalcIndex::ItmOtm => CalcindexItmOtm,
1615 CalcIndex::ImpliedVolatility => CalcindexImpliedVolatility,
1616 CalcIndex::WarrantDelta => CalcindexWarrantDelta,
1617 CalcIndex::CallPrice => CalcindexCallPrice,
1618 CalcIndex::ToCallPrice => CalcindexToCallPrice,
1619 CalcIndex::EffectiveLeverage => CalcindexEffectiveLeverage,
1620 CalcIndex::LeverageRatio => CalcindexLeverageRatio,
1621 CalcIndex::ConversionRatio => CalcindexConversionRatio,
1622 CalcIndex::BalancePoint => CalcindexBalancePoint,
1623 CalcIndex::OpenInterest => CalcindexOpenInterest,
1624 CalcIndex::Delta => CalcindexDelta,
1625 CalcIndex::Gamma => CalcindexGamma,
1626 CalcIndex::Theta => CalcindexTheta,
1627 CalcIndex::Vega => CalcindexVega,
1628 CalcIndex::Rho => CalcindexRho,
1629 }
1630 }
1631}
1632
1633#[derive(Debug, Clone, Serialize, Deserialize)]
1635pub struct SecurityCalcIndex {
1636 pub symbol: String,
1638 pub last_done: Option<Decimal>,
1640 pub change_value: Option<Decimal>,
1642 pub change_rate: Option<Decimal>,
1644 pub volume: Option<i64>,
1646 pub turnover: Option<Decimal>,
1648 pub ytd_change_rate: Option<Decimal>,
1650 pub turnover_rate: Option<Decimal>,
1652 pub total_market_value: Option<Decimal>,
1654 pub capital_flow: Option<Decimal>,
1656 pub amplitude: Option<Decimal>,
1658 pub volume_ratio: Option<Decimal>,
1660 pub pe_ttm_ratio: Option<Decimal>,
1662 pub pb_ratio: Option<Decimal>,
1664 pub dividend_ratio_ttm: Option<Decimal>,
1666 pub five_day_change_rate: Option<Decimal>,
1668 pub ten_day_change_rate: Option<Decimal>,
1670 pub half_year_change_rate: Option<Decimal>,
1672 pub five_minutes_change_rate: Option<Decimal>,
1674 pub expiry_date: Option<Date>,
1676 pub strike_price: Option<Decimal>,
1678 pub upper_strike_price: Option<Decimal>,
1680 pub lower_strike_price: Option<Decimal>,
1682 pub outstanding_qty: Option<i64>,
1684 pub outstanding_ratio: Option<Decimal>,
1686 pub premium: Option<Decimal>,
1688 pub itm_otm: Option<Decimal>,
1690 pub implied_volatility: Option<Decimal>,
1692 pub warrant_delta: Option<Decimal>,
1694 pub call_price: Option<Decimal>,
1696 pub to_call_price: Option<Decimal>,
1698 pub effective_leverage: Option<Decimal>,
1700 pub leverage_ratio: Option<Decimal>,
1702 pub conversion_ratio: Option<Decimal>,
1704 pub balance_point: Option<Decimal>,
1706 pub open_interest: Option<i64>,
1708 pub delta: Option<Decimal>,
1710 pub gamma: Option<Decimal>,
1712 pub theta: Option<Decimal>,
1714 pub vega: Option<Decimal>,
1716 pub rho: Option<Decimal>,
1718}
1719
1720impl SecurityCalcIndex {
1721 pub(crate) fn from_proto(
1722 resp: longport_proto::quote::SecurityCalcIndex,
1723 indexes: &[CalcIndex],
1724 ) -> Self {
1725 let mut output = SecurityCalcIndex {
1726 symbol: resp.symbol,
1727 last_done: None,
1728 change_value: None,
1729 change_rate: None,
1730 volume: None,
1731 turnover: None,
1732 ytd_change_rate: None,
1733 turnover_rate: None,
1734 total_market_value: None,
1735 capital_flow: None,
1736 amplitude: None,
1737 volume_ratio: None,
1738 pe_ttm_ratio: None,
1739 pb_ratio: None,
1740 dividend_ratio_ttm: None,
1741 five_day_change_rate: None,
1742 ten_day_change_rate: None,
1743 half_year_change_rate: None,
1744 five_minutes_change_rate: None,
1745 expiry_date: None,
1746 strike_price: None,
1747 upper_strike_price: None,
1748 lower_strike_price: None,
1749 outstanding_qty: None,
1750 outstanding_ratio: None,
1751 premium: None,
1752 itm_otm: None,
1753 implied_volatility: None,
1754 warrant_delta: None,
1755 call_price: None,
1756 to_call_price: None,
1757 effective_leverage: None,
1758 leverage_ratio: None,
1759 conversion_ratio: None,
1760 balance_point: None,
1761 open_interest: None,
1762 delta: None,
1763 gamma: None,
1764 theta: None,
1765 vega: None,
1766 rho: None,
1767 };
1768
1769 for index in indexes {
1770 match index {
1771 CalcIndex::LastDone => output.last_done = resp.last_done.parse().ok(),
1772 CalcIndex::ChangeValue => output.change_value = resp.change_val.parse().ok(),
1773 CalcIndex::ChangeRate => output.change_rate = resp.change_rate.parse().ok(),
1774 CalcIndex::Volume => output.volume = Some(resp.volume),
1775 CalcIndex::Turnover => output.turnover = resp.turnover.parse().ok(),
1776 CalcIndex::YtdChangeRate => {
1777 output.ytd_change_rate = resp.ytd_change_rate.parse().ok()
1778 }
1779 CalcIndex::TurnoverRate => output.turnover_rate = resp.turnover_rate.parse().ok(),
1780 CalcIndex::TotalMarketValue => {
1781 output.total_market_value = resp.total_market_value.parse().ok()
1782 }
1783 CalcIndex::CapitalFlow => output.capital_flow = resp.capital_flow.parse().ok(),
1784 CalcIndex::Amplitude => output.amplitude = resp.amplitude.parse().ok(),
1785 CalcIndex::VolumeRatio => output.volume_ratio = resp.volume_ratio.parse().ok(),
1786 CalcIndex::PeTtmRatio => output.pe_ttm_ratio = resp.pe_ttm_ratio.parse().ok(),
1787 CalcIndex::PbRatio => output.pb_ratio = resp.pb_ratio.parse().ok(),
1788 CalcIndex::DividendRatioTtm => {
1789 output.dividend_ratio_ttm = resp.dividend_ratio_ttm.parse().ok()
1790 }
1791 CalcIndex::FiveDayChangeRate => {
1792 output.five_day_change_rate = resp.five_day_change_rate.parse().ok()
1793 }
1794 CalcIndex::TenDayChangeRate => {
1795 output.ten_day_change_rate = resp.ten_day_change_rate.parse().ok()
1796 }
1797 CalcIndex::HalfYearChangeRate => {
1798 output.half_year_change_rate = resp.half_year_change_rate.parse().ok()
1799 }
1800 CalcIndex::FiveMinutesChangeRate => {
1801 output.five_minutes_change_rate = resp.five_minutes_change_rate.parse().ok()
1802 }
1803 CalcIndex::ExpiryDate => output.expiry_date = parse_date(&resp.expiry_date).ok(),
1804 CalcIndex::StrikePrice => output.strike_price = resp.strike_price.parse().ok(),
1805 CalcIndex::UpperStrikePrice => {
1806 output.upper_strike_price = resp.upper_strike_price.parse().ok()
1807 }
1808 CalcIndex::LowerStrikePrice => {
1809 output.lower_strike_price = resp.lower_strike_price.parse().ok()
1810 }
1811 CalcIndex::OutstandingQty => output.outstanding_qty = Some(resp.outstanding_qty),
1812 CalcIndex::OutstandingRatio => {
1813 output.outstanding_ratio = resp.outstanding_ratio.parse().ok()
1814 }
1815 CalcIndex::Premium => output.premium = resp.premium.parse().ok(),
1816 CalcIndex::ItmOtm => output.itm_otm = resp.itm_otm.parse().ok(),
1817 CalcIndex::ImpliedVolatility => {
1818 output.implied_volatility = resp.implied_volatility.parse().ok()
1819 }
1820 CalcIndex::WarrantDelta => output.warrant_delta = resp.warrant_delta.parse().ok(),
1821 CalcIndex::CallPrice => output.call_price = resp.call_price.parse().ok(),
1822 CalcIndex::ToCallPrice => output.to_call_price = resp.to_call_price.parse().ok(),
1823 CalcIndex::EffectiveLeverage => {
1824 output.effective_leverage = resp.effective_leverage.parse().ok()
1825 }
1826 CalcIndex::LeverageRatio => {
1827 output.leverage_ratio = resp.leverage_ratio.parse().ok()
1828 }
1829 CalcIndex::ConversionRatio => {
1830 output.conversion_ratio = resp.conversion_ratio.parse().ok()
1831 }
1832 CalcIndex::BalancePoint => output.balance_point = resp.balance_point.parse().ok(),
1833 CalcIndex::OpenInterest => output.open_interest = Some(resp.open_interest),
1834 CalcIndex::Delta => output.delta = resp.delta.parse().ok(),
1835 CalcIndex::Gamma => output.gamma = resp.gamma.parse().ok(),
1836 CalcIndex::Theta => output.theta = resp.theta.parse().ok(),
1837 CalcIndex::Vega => output.vega = resp.vega.parse().ok(),
1838 CalcIndex::Rho => output.rho = resp.rho.parse().ok(),
1839 }
1840 }
1841
1842 output
1843 }
1844}
1845
1846#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1848pub enum SecurityListCategory {
1849 Overnight,
1851}
1852
1853impl_serialize_for_enum_string!(SecurityListCategory);
1854
1855#[derive(Debug, Serialize, Deserialize)]
1857pub struct Security {
1858 pub symbol: String,
1860 pub name_cn: String,
1862 pub name_en: String,
1864 pub name_hk: String,
1866}
1867
1868#[derive(Debug, Clone)]
1870pub struct QuotePackageDetail {
1871 pub key: String,
1873 pub name: String,
1875 pub description: String,
1877 pub start_at: OffsetDateTime,
1879 pub end_at: OffsetDateTime,
1881}
1882
1883impl TryFrom<quote::user_quote_level_detail::PackageDetail> for QuotePackageDetail {
1884 type Error = Error;
1885
1886 fn try_from(quote: quote::user_quote_level_detail::PackageDetail) -> Result<Self> {
1887 Ok(Self {
1888 key: quote.key,
1889 name: quote.name,
1890 description: quote.description,
1891 start_at: OffsetDateTime::from_unix_timestamp(quote.start)
1892 .map_err(|err| Error::parse_field_error("start_at", err))?,
1893 end_at: OffsetDateTime::from_unix_timestamp(quote.end)
1894 .map_err(|err| Error::parse_field_error("end_at", err))?,
1895 })
1896 }
1897}
1898
1899#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1901#[repr(i32)]
1902pub enum TradeSessions {
1903 Intraday = 0,
1905 All = 100,
1907}
1908
1909impl TradeSessions {
1910 #[inline]
1911 pub(crate) fn contains(&self, session: TradeSession) -> bool {
1912 match self {
1913 TradeSessions::Intraday => session == TradeSession::Intraday,
1914 TradeSessions::All => true,
1915 }
1916 }
1917}
1918
1919#[derive(Debug, Clone, Serialize, Deserialize)]
1921pub struct MarketTemperature {
1922 pub temperature: i32,
1924 #[serde(default)]
1926 pub description: String,
1927 pub valuation: i32,
1929 pub sentiment: i32,
1931 #[serde(
1933 serialize_with = "time::serde::rfc3339::serialize",
1934 deserialize_with = "serde_utils::timestamp::deserialize",
1935 alias = "updated_at"
1936 )]
1937 pub timestamp: OffsetDateTime,
1938}
1939
1940#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1942pub enum Granularity {
1943 Unknown,
1945 #[strum(serialize = "daily")]
1947 Daily,
1948 #[strum(serialize = "weekly")]
1950 Weekly,
1951 #[strum(serialize = "monthly")]
1953 Monthly,
1954}
1955
1956#[derive(Debug, Clone, Serialize, Deserialize)]
1958pub struct HistoryMarketTemperatureResponse {
1959 #[serde(rename = "type")]
1961 pub granularity: Granularity,
1962 #[serde(rename = "list")]
1964 pub records: Vec<MarketTemperature>,
1965}
1966
1967impl_serde_for_enum_string!(Granularity);
1968impl_default_for_enum_string!(
1969 OptionType,
1970 OptionDirection,
1971 WarrantType,
1972 SecurityBoard,
1973 Granularity
1974);
1975
1976#[cfg(test)]
1977mod tests {
1978 use serde::Deserialize;
1979
1980 use crate::{Market, quote::WatchlistGroup};
1981
1982 #[test]
1983 fn watch_list() {
1984 #[derive(Debug, Deserialize)]
1985 struct Response {
1986 groups: Vec<WatchlistGroup>,
1987 }
1988
1989 let json = r#"
1990 {
1991 "groups": [
1992 {
1993 "id": "1",
1994 "name": "Test",
1995 "securities": [
1996 {
1997 "symbol": "AAPL",
1998 "market": "US",
1999 "name": "Apple Inc.",
2000 "watched_price": "150.0",
2001 "watched_at": "1633036800"
2002 }
2003 ]
2004 }
2005 ]
2006 }
2007 "#;
2008
2009 let response: Response = serde_json::from_str(json).unwrap();
2010 assert_eq!(response.groups.len(), 1);
2011 assert_eq!(response.groups[0].id, 1);
2012 assert_eq!(response.groups[0].name, "Test");
2013 assert_eq!(response.groups[0].securities.len(), 1);
2014 assert_eq!(response.groups[0].securities[0].symbol, "AAPL");
2015 assert_eq!(response.groups[0].securities[0].market, Market::US);
2016 assert_eq!(response.groups[0].securities[0].name, "Apple Inc.");
2017 assert_eq!(
2018 response.groups[0].securities[0].watched_price,
2019 Some(decimal!(150.0))
2020 );
2021 assert_eq!(
2022 response.groups[0].securities[0].watched_at.unix_timestamp(),
2023 1633036800
2024 );
2025 }
2026}