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 open_updated: bool,
802}
803
804impl TryFrom<quote::Candlestick> for Candlestick {
805 type Error = Error;
806
807 fn try_from(value: quote::Candlestick) -> Result<Self> {
808 Ok(Self {
809 close: value.close.parse().unwrap_or_default(),
810 open: value.open.parse().unwrap_or_default(),
811 low: value.low.parse().unwrap_or_default(),
812 high: value.high.parse().unwrap_or_default(),
813 volume: value.volume,
814 turnover: value.turnover.parse().unwrap_or_default(),
815 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
816 .map_err(|err| Error::parse_field_error("timestamp", err))?,
817 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
818 .map_err(|err| Error::parse_field_error("trade_session", err))?
819 .into(),
820 open_updated: true,
821 })
822 }
823}
824
825impl longport_candlesticks::CandlestickType for Candlestick {
826 type PriceType = Decimal;
827 type VolumeType = i64;
828 type TurnoverType = Decimal;
829 type TradeSessionType = TradeSession;
830
831 #[inline]
832 fn new(
833 components: CandlestickComponents<
834 Self::PriceType,
835 Self::VolumeType,
836 Self::TurnoverType,
837 Self::TradeSessionType,
838 >,
839 ) -> Self {
840 Self {
841 timestamp: components.time,
842 open: components.open,
843 high: components.high,
844 low: components.low,
845 close: components.close,
846 volume: components.volume,
847 turnover: components.turnover,
848 trade_session: components.trade_session,
849 open_updated: components.open_updated,
850 }
851 }
852
853 #[inline]
854 fn time(&self) -> OffsetDateTime {
855 self.timestamp
856 }
857
858 #[inline]
859 fn set_time(&mut self, time: OffsetDateTime) {
860 self.timestamp = time;
861 }
862
863 #[inline]
864 fn open(&self) -> Self::PriceType {
865 self.open
866 }
867
868 #[inline]
869 fn set_open(&mut self, open: Self::PriceType) {
870 self.open = open;
871 }
872
873 #[inline]
874 fn high(&self) -> Self::PriceType {
875 self.high
876 }
877
878 #[inline]
879 fn set_high(&mut self, high: Self::PriceType) {
880 self.high = high;
881 }
882
883 #[inline]
884 fn low(&self) -> Self::PriceType {
885 self.low
886 }
887
888 #[inline]
889 fn set_low(&mut self, low: Self::PriceType) {
890 self.low = low;
891 }
892
893 #[inline]
894 fn close(&self) -> Self::PriceType {
895 self.close
896 }
897
898 #[inline]
899 fn set_close(&mut self, close: Self::PriceType) {
900 self.close = close;
901 }
902
903 #[inline]
904 fn volume(&self) -> Self::VolumeType {
905 self.volume
906 }
907
908 #[inline]
909 fn set_volume(&mut self, volume: Self::VolumeType) {
910 self.volume = volume;
911 }
912
913 #[inline]
914 fn turnover(&self) -> Self::TurnoverType {
915 self.turnover
916 }
917
918 #[inline]
919 fn set_turnover(&mut self, turnover: Self::TurnoverType) {
920 self.turnover = turnover;
921 }
922
923 #[inline]
924 fn trade_session(&self) -> Self::TradeSessionType {
925 self.trade_session
926 }
927
928 #[inline]
929 fn set_open_updated(&mut self, updated: bool) {
930 self.open_updated = updated;
931 }
932
933 #[inline]
934 fn open_updated(&self) -> bool {
935 self.open_updated
936 }
937}
938
939#[derive(Debug, Clone, Serialize, Deserialize)]
941pub struct StrikePriceInfo {
942 pub price: Decimal,
944 pub call_symbol: String,
946 pub put_symbol: String,
948 pub standard: bool,
950}
951
952impl TryFrom<quote::StrikePriceInfo> for StrikePriceInfo {
953 type Error = Error;
954
955 fn try_from(value: quote::StrikePriceInfo) -> Result<Self> {
956 Ok(Self {
957 price: value.price.parse().unwrap_or_default(),
958 call_symbol: value.call_symbol,
959 put_symbol: value.put_symbol,
960 standard: value.standard,
961 })
962 }
963}
964
965#[derive(Debug, Clone, Serialize, Deserialize)]
967pub struct IssuerInfo {
968 pub issuer_id: i32,
970 pub name_cn: String,
972 pub name_en: String,
974 pub name_hk: String,
976}
977
978impl From<quote::IssuerInfo> for IssuerInfo {
979 fn from(info: quote::IssuerInfo) -> Self {
980 Self {
981 issuer_id: info.id,
982 name_cn: info.name_cn,
983 name_en: info.name_en,
984 name_hk: info.name_hk,
985 }
986 }
987}
988
989#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
991#[repr(i32)]
992pub enum SortOrderType {
993 Ascending = 0,
995 Descending = 1,
997}
998
999#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1001#[repr(i32)]
1002pub enum WarrantSortBy {
1003 LastDone = 0,
1005 ChangeRate = 1,
1007 ChangeValue = 2,
1009 Volume = 3,
1011 Turnover = 4,
1013 ExpiryDate = 5,
1015 StrikePrice = 6,
1017 UpperStrikePrice = 7,
1019 LowerStrikePrice = 8,
1021 OutstandingQuantity = 9,
1023 OutstandingRatio = 10,
1025 Premium = 11,
1027 ItmOtm = 12,
1029 ImpliedVolatility = 13,
1031 Delta = 14,
1033 CallPrice = 15,
1035 ToCallPrice = 16,
1037 EffectiveLeverage = 17,
1039 LeverageRatio = 18,
1041 ConversionRatio = 19,
1043 BalancePoint = 20,
1045 Status = 21,
1047}
1048
1049#[allow(non_camel_case_types)]
1051#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1052#[repr(i32)]
1053pub enum FilterWarrantExpiryDate {
1054 LT_3 = 1,
1056 Between_3_6 = 2,
1058 Between_6_12 = 3,
1060 GT_12 = 4,
1062}
1063
1064#[allow(non_camel_case_types)]
1066#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1067#[repr(i32)]
1068pub enum FilterWarrantInOutBoundsType {
1069 In = 1,
1071 Out = 2,
1073}
1074
1075#[derive(
1077 Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive, TryFromPrimitive, Serialize, Deserialize,
1078)]
1079#[repr(i32)]
1080pub enum WarrantStatus {
1081 Suspend = 2,
1083 PrepareList = 3,
1085 Normal = 4,
1087}
1088
1089#[derive(Debug, Clone, Serialize, Deserialize)]
1091pub struct WarrantInfo {
1092 pub symbol: String,
1094 pub warrant_type: WarrantType,
1096 pub name: String,
1098 pub last_done: Decimal,
1100 pub change_rate: Decimal,
1102 pub change_value: Decimal,
1104 pub volume: i64,
1106 pub turnover: Decimal,
1108 pub expiry_date: Date,
1110 pub strike_price: Option<Decimal>,
1112 pub upper_strike_price: Option<Decimal>,
1114 pub lower_strike_price: Option<Decimal>,
1116 pub outstanding_qty: i64,
1118 pub outstanding_ratio: Decimal,
1120 pub premium: Decimal,
1122 pub itm_otm: Option<Decimal>,
1124 pub implied_volatility: Option<Decimal>,
1126 pub delta: Option<Decimal>,
1128 pub call_price: Option<Decimal>,
1130 pub to_call_price: Option<Decimal>,
1132 pub effective_leverage: Option<Decimal>,
1134 pub leverage_ratio: Decimal,
1136 pub conversion_ratio: Option<Decimal>,
1138 pub balance_point: Option<Decimal>,
1140 pub status: WarrantStatus,
1142}
1143
1144impl TryFrom<quote::FilterWarrant> for WarrantInfo {
1145 type Error = Error;
1146
1147 fn try_from(info: quote::FilterWarrant) -> Result<Self> {
1148 let r#type = WarrantType::try_from(info.r#type)
1149 .map_err(|err| Error::parse_field_error("type", err))?;
1150
1151 match r#type {
1152 WarrantType::Unknown => unreachable!(),
1153 WarrantType::Call | WarrantType::Put => Ok(Self {
1154 symbol: info.symbol,
1155 warrant_type: r#type,
1156 name: info.name,
1157 last_done: info.last_done.parse().unwrap_or_default(),
1158 change_rate: info.change_rate.parse().unwrap_or_default(),
1159 change_value: info.change_val.parse().unwrap_or_default(),
1160 volume: info.volume,
1161 turnover: info.turnover.parse().unwrap_or_default(),
1162 expiry_date: parse_date(&info.expiry_date)
1163 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1164 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1165 upper_strike_price: None,
1166 lower_strike_price: None,
1167 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1168 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1169 premium: info.premium.parse().unwrap_or_default(),
1170 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1171 implied_volatility: Some(info.last_done.parse().unwrap_or_default()),
1172 delta: Some(info.last_done.parse().unwrap_or_default()),
1173 call_price: None,
1174 to_call_price: None,
1175 effective_leverage: Some(info.last_done.parse().unwrap_or_default()),
1176 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1177 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1178 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1179 status: WarrantStatus::try_from(info.status)
1180 .map_err(|err| Error::parse_field_error("state", err))?,
1181 }),
1182 WarrantType::Bull | WarrantType::Bear => Ok(Self {
1183 symbol: info.symbol,
1184 warrant_type: r#type,
1185 name: info.name,
1186 last_done: info.last_done.parse().unwrap_or_default(),
1187 change_rate: info.change_rate.parse().unwrap_or_default(),
1188 change_value: info.change_val.parse().unwrap_or_default(),
1189 volume: info.volume,
1190 turnover: info.turnover.parse().unwrap_or_default(),
1191 expiry_date: parse_date(&info.expiry_date)
1192 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1193 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1194 upper_strike_price: None,
1195 lower_strike_price: None,
1196 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1197 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1198 premium: info.premium.parse().unwrap_or_default(),
1199 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1200 implied_volatility: None,
1201 delta: None,
1202 call_price: Some(info.call_price.parse().unwrap_or_default()),
1203 to_call_price: Some(info.to_call_price.parse().unwrap_or_default()),
1204 effective_leverage: None,
1205 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1206 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1207 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1208 status: WarrantStatus::try_from(info.status)
1209 .map_err(|err| Error::parse_field_error("state", err))?,
1210 }),
1211 WarrantType::Inline => Ok(Self {
1212 symbol: info.symbol,
1213 warrant_type: r#type,
1214 name: info.name,
1215 last_done: info.last_done.parse().unwrap_or_default(),
1216 change_rate: info.change_rate.parse().unwrap_or_default(),
1217 change_value: info.change_val.parse().unwrap_or_default(),
1218 volume: info.volume,
1219 turnover: info.turnover.parse().unwrap_or_default(),
1220 expiry_date: parse_date(&info.expiry_date)
1221 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1222 strike_price: None,
1223 upper_strike_price: Some(info.upper_strike_price.parse().unwrap_or_default()),
1224 lower_strike_price: Some(info.lower_strike_price.parse().unwrap_or_default()),
1225 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1226 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1227 premium: info.premium.parse().unwrap_or_default(),
1228 itm_otm: None,
1229 implied_volatility: None,
1230 delta: None,
1231 call_price: None,
1232 to_call_price: None,
1233 effective_leverage: None,
1234 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1235 conversion_ratio: None,
1236 balance_point: None,
1237 status: WarrantStatus::try_from(info.status)
1238 .map_err(|err| Error::parse_field_error("state", err))?,
1239 }),
1240 }
1241 }
1242}
1243
1244#[derive(Debug, Clone, Serialize, Deserialize)]
1246pub struct TradingSessionInfo {
1247 pub begin_time: Time,
1249 pub end_time: Time,
1251 pub trade_session: TradeSession,
1253}
1254
1255impl TryFrom<quote::TradePeriod> for TradingSessionInfo {
1256 type Error = Error;
1257
1258 fn try_from(value: quote::TradePeriod) -> Result<Self> {
1259 #[inline]
1260 fn parse_time(value: i32) -> ::std::result::Result<Time, time::error::ComponentRange> {
1261 Time::from_hms(((value / 100) % 100) as u8, (value % 100) as u8, 0)
1262 }
1263
1264 Ok(Self {
1265 begin_time: parse_time(value.beg_time)
1266 .map_err(|err| Error::parse_field_error("beg_time", err))?,
1267 end_time: parse_time(value.end_time)
1268 .map_err(|err| Error::parse_field_error("end_time", err))?,
1269 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
1270 .unwrap_or_default()
1271 .into(),
1272 })
1273 }
1274}
1275
1276#[derive(Debug, Clone, Serialize, Deserialize)]
1278pub struct MarketTradingSession {
1279 pub market: Market,
1281 pub trade_sessions: Vec<TradingSessionInfo>,
1283}
1284
1285impl TryFrom<quote::MarketTradePeriod> for MarketTradingSession {
1286 type Error = Error;
1287
1288 fn try_from(value: quote::MarketTradePeriod) -> Result<Self> {
1289 Ok(Self {
1290 market: value.market.parse().unwrap_or_default(),
1291 trade_sessions: value
1292 .trade_session
1293 .into_iter()
1294 .map(TryInto::try_into)
1295 .collect::<Result<Vec<_>>>()?,
1296 })
1297 }
1298}
1299
1300#[derive(Debug, Clone, Serialize, Deserialize)]
1302pub struct MarketTradingDays {
1303 pub trading_days: Vec<Date>,
1305 pub half_trading_days: Vec<Date>,
1307}
1308
1309#[derive(Debug, Clone, Serialize, Deserialize)]
1311pub struct CapitalFlowLine {
1312 pub inflow: Decimal,
1314 pub timestamp: OffsetDateTime,
1316}
1317
1318impl TryFrom<quote::capital_flow_intraday_response::CapitalFlowLine> for CapitalFlowLine {
1319 type Error = Error;
1320
1321 fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result<Self> {
1322 Ok(Self {
1323 inflow: value.inflow.parse().unwrap_or_default(),
1324 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1325 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1326 })
1327 }
1328}
1329
1330#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1332pub struct CapitalDistribution {
1333 pub large: Decimal,
1335 pub medium: Decimal,
1337 pub small: Decimal,
1339}
1340
1341impl TryFrom<quote::capital_distribution_response::CapitalDistribution> for CapitalDistribution {
1342 type Error = Error;
1343
1344 fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result<Self> {
1345 Ok(Self {
1346 large: value.large.parse().unwrap_or_default(),
1347 medium: value.medium.parse().unwrap_or_default(),
1348 small: value.small.parse().unwrap_or_default(),
1349 })
1350 }
1351}
1352
1353#[derive(Debug, Clone, Serialize, Deserialize)]
1355pub struct CapitalDistributionResponse {
1356 pub timestamp: OffsetDateTime,
1358 pub capital_in: CapitalDistribution,
1360 pub capital_out: CapitalDistribution,
1362}
1363
1364impl TryFrom<quote::CapitalDistributionResponse> for CapitalDistributionResponse {
1365 type Error = Error;
1366
1367 fn try_from(value: quote::CapitalDistributionResponse) -> Result<Self> {
1368 Ok(Self {
1369 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1370 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1371 capital_in: value
1372 .capital_in
1373 .map(TryInto::try_into)
1374 .transpose()?
1375 .unwrap_or_default(),
1376 capital_out: value
1377 .capital_out
1378 .map(TryInto::try_into)
1379 .transpose()?
1380 .unwrap_or_default(),
1381 })
1382 }
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize)]
1387pub struct WatchlistSecurity {
1388 pub symbol: String,
1390 pub market: Market,
1392 pub name: String,
1394 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
1396 pub watched_price: Option<Decimal>,
1397 #[serde(
1399 serialize_with = "time::serde::rfc3339::serialize",
1400 deserialize_with = "serde_utils::timestamp::deserialize"
1401 )]
1402 pub watched_at: OffsetDateTime,
1403}
1404
1405#[derive(Debug, Clone, Serialize, Deserialize)]
1407pub struct WatchlistGroup {
1408 #[serde(with = "serde_utils::int64_str")]
1410 pub id: i64,
1411 pub name: String,
1413 pub securities: Vec<WatchlistSecurity>,
1415}
1416
1417#[derive(Debug, Clone)]
1419pub struct RequestCreateWatchlistGroup {
1420 pub name: String,
1422 pub securities: Option<Vec<String>>,
1424}
1425
1426impl RequestCreateWatchlistGroup {
1427 pub fn new(name: impl Into<String>) -> Self {
1429 Self {
1430 name: name.into(),
1431 securities: None,
1432 }
1433 }
1434
1435 pub fn securities<I, T>(self, securities: I) -> Self
1437 where
1438 I: IntoIterator<Item = T>,
1439 T: Into<String>,
1440 {
1441 Self {
1442 securities: Some(securities.into_iter().map(Into::into).collect()),
1443 ..self
1444 }
1445 }
1446}
1447
1448#[derive(Debug, Copy, Clone, Default, Serialize)]
1450#[serde(rename_all = "lowercase")]
1451pub enum SecuritiesUpdateMode {
1452 Add,
1454 Remove,
1456 #[default]
1458 Replace,
1459}
1460
1461#[derive(Debug, Clone)]
1463pub struct RequestUpdateWatchlistGroup {
1464 pub id: i64,
1466 pub name: Option<String>,
1468 pub securities: Option<Vec<String>>,
1470 pub mode: SecuritiesUpdateMode,
1472}
1473
1474impl RequestUpdateWatchlistGroup {
1475 #[inline]
1477 pub fn new(id: i64) -> Self {
1478 Self {
1479 id,
1480 name: None,
1481 securities: None,
1482 mode: SecuritiesUpdateMode::default(),
1483 }
1484 }
1485
1486 pub fn name(self, name: impl Into<String>) -> Self {
1488 Self {
1489 name: Some(name.into()),
1490 ..self
1491 }
1492 }
1493
1494 pub fn securities<I, T>(self, securities: I) -> Self
1496 where
1497 I: IntoIterator<Item = T>,
1498 T: Into<String>,
1499 {
1500 Self {
1501 securities: Some(securities.into_iter().map(Into::into).collect()),
1502 ..self
1503 }
1504 }
1505
1506 pub fn mode(self, mode: SecuritiesUpdateMode) -> Self {
1508 Self { mode, ..self }
1509 }
1510}
1511
1512#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1514pub enum CalcIndex {
1515 LastDone,
1517 ChangeValue,
1519 ChangeRate,
1521 Volume,
1523 Turnover,
1525 YtdChangeRate,
1527 TurnoverRate,
1529 TotalMarketValue,
1531 CapitalFlow,
1533 Amplitude,
1535 VolumeRatio,
1537 PeTtmRatio,
1539 PbRatio,
1541 DividendRatioTtm,
1543 FiveDayChangeRate,
1545 TenDayChangeRate,
1547 HalfYearChangeRate,
1549 FiveMinutesChangeRate,
1551 ExpiryDate,
1553 StrikePrice,
1555 UpperStrikePrice,
1557 LowerStrikePrice,
1559 OutstandingQty,
1561 OutstandingRatio,
1563 Premium,
1565 ItmOtm,
1567 ImpliedVolatility,
1569 WarrantDelta,
1571 CallPrice,
1573 ToCallPrice,
1575 EffectiveLeverage,
1577 LeverageRatio,
1579 ConversionRatio,
1581 BalancePoint,
1583 OpenInterest,
1585 Delta,
1587 Gamma,
1589 Theta,
1591 Vega,
1593 Rho,
1595}
1596
1597impl From<CalcIndex> for longport_proto::quote::CalcIndex {
1598 fn from(value: CalcIndex) -> Self {
1599 use longport_proto::quote::CalcIndex::*;
1600
1601 match value {
1602 CalcIndex::LastDone => CalcindexLastDone,
1603 CalcIndex::ChangeValue => CalcindexChangeVal,
1604 CalcIndex::ChangeRate => CalcindexChangeRate,
1605 CalcIndex::Volume => CalcindexVolume,
1606 CalcIndex::Turnover => CalcindexTurnover,
1607 CalcIndex::YtdChangeRate => CalcindexYtdChangeRate,
1608 CalcIndex::TurnoverRate => CalcindexTurnoverRate,
1609 CalcIndex::TotalMarketValue => CalcindexTotalMarketValue,
1610 CalcIndex::CapitalFlow => CalcindexCapitalFlow,
1611 CalcIndex::Amplitude => CalcindexAmplitude,
1612 CalcIndex::VolumeRatio => CalcindexVolumeRatio,
1613 CalcIndex::PeTtmRatio => CalcindexPeTtmRatio,
1614 CalcIndex::PbRatio => CalcindexPbRatio,
1615 CalcIndex::DividendRatioTtm => CalcindexDividendRatioTtm,
1616 CalcIndex::FiveDayChangeRate => CalcindexFiveDayChangeRate,
1617 CalcIndex::TenDayChangeRate => CalcindexTenDayChangeRate,
1618 CalcIndex::HalfYearChangeRate => CalcindexHalfYearChangeRate,
1619 CalcIndex::FiveMinutesChangeRate => CalcindexFiveMinutesChangeRate,
1620 CalcIndex::ExpiryDate => CalcindexExpiryDate,
1621 CalcIndex::StrikePrice => CalcindexStrikePrice,
1622 CalcIndex::UpperStrikePrice => CalcindexUpperStrikePrice,
1623 CalcIndex::LowerStrikePrice => CalcindexLowerStrikePrice,
1624 CalcIndex::OutstandingQty => CalcindexOutstandingQty,
1625 CalcIndex::OutstandingRatio => CalcindexOutstandingRatio,
1626 CalcIndex::Premium => CalcindexPremium,
1627 CalcIndex::ItmOtm => CalcindexItmOtm,
1628 CalcIndex::ImpliedVolatility => CalcindexImpliedVolatility,
1629 CalcIndex::WarrantDelta => CalcindexWarrantDelta,
1630 CalcIndex::CallPrice => CalcindexCallPrice,
1631 CalcIndex::ToCallPrice => CalcindexToCallPrice,
1632 CalcIndex::EffectiveLeverage => CalcindexEffectiveLeverage,
1633 CalcIndex::LeverageRatio => CalcindexLeverageRatio,
1634 CalcIndex::ConversionRatio => CalcindexConversionRatio,
1635 CalcIndex::BalancePoint => CalcindexBalancePoint,
1636 CalcIndex::OpenInterest => CalcindexOpenInterest,
1637 CalcIndex::Delta => CalcindexDelta,
1638 CalcIndex::Gamma => CalcindexGamma,
1639 CalcIndex::Theta => CalcindexTheta,
1640 CalcIndex::Vega => CalcindexVega,
1641 CalcIndex::Rho => CalcindexRho,
1642 }
1643 }
1644}
1645
1646#[derive(Debug, Clone, Serialize, Deserialize)]
1648pub struct SecurityCalcIndex {
1649 pub symbol: String,
1651 pub last_done: Option<Decimal>,
1653 pub change_value: Option<Decimal>,
1655 pub change_rate: Option<Decimal>,
1657 pub volume: Option<i64>,
1659 pub turnover: Option<Decimal>,
1661 pub ytd_change_rate: Option<Decimal>,
1663 pub turnover_rate: Option<Decimal>,
1665 pub total_market_value: Option<Decimal>,
1667 pub capital_flow: Option<Decimal>,
1669 pub amplitude: Option<Decimal>,
1671 pub volume_ratio: Option<Decimal>,
1673 pub pe_ttm_ratio: Option<Decimal>,
1675 pub pb_ratio: Option<Decimal>,
1677 pub dividend_ratio_ttm: Option<Decimal>,
1679 pub five_day_change_rate: Option<Decimal>,
1681 pub ten_day_change_rate: Option<Decimal>,
1683 pub half_year_change_rate: Option<Decimal>,
1685 pub five_minutes_change_rate: Option<Decimal>,
1687 pub expiry_date: Option<Date>,
1689 pub strike_price: Option<Decimal>,
1691 pub upper_strike_price: Option<Decimal>,
1693 pub lower_strike_price: Option<Decimal>,
1695 pub outstanding_qty: Option<i64>,
1697 pub outstanding_ratio: Option<Decimal>,
1699 pub premium: Option<Decimal>,
1701 pub itm_otm: Option<Decimal>,
1703 pub implied_volatility: Option<Decimal>,
1705 pub warrant_delta: Option<Decimal>,
1707 pub call_price: Option<Decimal>,
1709 pub to_call_price: Option<Decimal>,
1711 pub effective_leverage: Option<Decimal>,
1713 pub leverage_ratio: Option<Decimal>,
1715 pub conversion_ratio: Option<Decimal>,
1717 pub balance_point: Option<Decimal>,
1719 pub open_interest: Option<i64>,
1721 pub delta: Option<Decimal>,
1723 pub gamma: Option<Decimal>,
1725 pub theta: Option<Decimal>,
1727 pub vega: Option<Decimal>,
1729 pub rho: Option<Decimal>,
1731}
1732
1733impl SecurityCalcIndex {
1734 pub(crate) fn from_proto(
1735 resp: longport_proto::quote::SecurityCalcIndex,
1736 indexes: &[CalcIndex],
1737 ) -> Self {
1738 let mut output = SecurityCalcIndex {
1739 symbol: resp.symbol,
1740 last_done: None,
1741 change_value: None,
1742 change_rate: None,
1743 volume: None,
1744 turnover: None,
1745 ytd_change_rate: None,
1746 turnover_rate: None,
1747 total_market_value: None,
1748 capital_flow: None,
1749 amplitude: None,
1750 volume_ratio: None,
1751 pe_ttm_ratio: None,
1752 pb_ratio: None,
1753 dividend_ratio_ttm: None,
1754 five_day_change_rate: None,
1755 ten_day_change_rate: None,
1756 half_year_change_rate: None,
1757 five_minutes_change_rate: None,
1758 expiry_date: None,
1759 strike_price: None,
1760 upper_strike_price: None,
1761 lower_strike_price: None,
1762 outstanding_qty: None,
1763 outstanding_ratio: None,
1764 premium: None,
1765 itm_otm: None,
1766 implied_volatility: None,
1767 warrant_delta: None,
1768 call_price: None,
1769 to_call_price: None,
1770 effective_leverage: None,
1771 leverage_ratio: None,
1772 conversion_ratio: None,
1773 balance_point: None,
1774 open_interest: None,
1775 delta: None,
1776 gamma: None,
1777 theta: None,
1778 vega: None,
1779 rho: None,
1780 };
1781
1782 for index in indexes {
1783 match index {
1784 CalcIndex::LastDone => output.last_done = resp.last_done.parse().ok(),
1785 CalcIndex::ChangeValue => output.change_value = resp.change_val.parse().ok(),
1786 CalcIndex::ChangeRate => output.change_rate = resp.change_rate.parse().ok(),
1787 CalcIndex::Volume => output.volume = Some(resp.volume),
1788 CalcIndex::Turnover => output.turnover = resp.turnover.parse().ok(),
1789 CalcIndex::YtdChangeRate => {
1790 output.ytd_change_rate = resp.ytd_change_rate.parse().ok()
1791 }
1792 CalcIndex::TurnoverRate => output.turnover_rate = resp.turnover_rate.parse().ok(),
1793 CalcIndex::TotalMarketValue => {
1794 output.total_market_value = resp.total_market_value.parse().ok()
1795 }
1796 CalcIndex::CapitalFlow => output.capital_flow = resp.capital_flow.parse().ok(),
1797 CalcIndex::Amplitude => output.amplitude = resp.amplitude.parse().ok(),
1798 CalcIndex::VolumeRatio => output.volume_ratio = resp.volume_ratio.parse().ok(),
1799 CalcIndex::PeTtmRatio => output.pe_ttm_ratio = resp.pe_ttm_ratio.parse().ok(),
1800 CalcIndex::PbRatio => output.pb_ratio = resp.pb_ratio.parse().ok(),
1801 CalcIndex::DividendRatioTtm => {
1802 output.dividend_ratio_ttm = resp.dividend_ratio_ttm.parse().ok()
1803 }
1804 CalcIndex::FiveDayChangeRate => {
1805 output.five_day_change_rate = resp.five_day_change_rate.parse().ok()
1806 }
1807 CalcIndex::TenDayChangeRate => {
1808 output.ten_day_change_rate = resp.ten_day_change_rate.parse().ok()
1809 }
1810 CalcIndex::HalfYearChangeRate => {
1811 output.half_year_change_rate = resp.half_year_change_rate.parse().ok()
1812 }
1813 CalcIndex::FiveMinutesChangeRate => {
1814 output.five_minutes_change_rate = resp.five_minutes_change_rate.parse().ok()
1815 }
1816 CalcIndex::ExpiryDate => output.expiry_date = parse_date(&resp.expiry_date).ok(),
1817 CalcIndex::StrikePrice => output.strike_price = resp.strike_price.parse().ok(),
1818 CalcIndex::UpperStrikePrice => {
1819 output.upper_strike_price = resp.upper_strike_price.parse().ok()
1820 }
1821 CalcIndex::LowerStrikePrice => {
1822 output.lower_strike_price = resp.lower_strike_price.parse().ok()
1823 }
1824 CalcIndex::OutstandingQty => output.outstanding_qty = Some(resp.outstanding_qty),
1825 CalcIndex::OutstandingRatio => {
1826 output.outstanding_ratio = resp.outstanding_ratio.parse().ok()
1827 }
1828 CalcIndex::Premium => output.premium = resp.premium.parse().ok(),
1829 CalcIndex::ItmOtm => output.itm_otm = resp.itm_otm.parse().ok(),
1830 CalcIndex::ImpliedVolatility => {
1831 output.implied_volatility = resp.implied_volatility.parse().ok()
1832 }
1833 CalcIndex::WarrantDelta => output.warrant_delta = resp.warrant_delta.parse().ok(),
1834 CalcIndex::CallPrice => output.call_price = resp.call_price.parse().ok(),
1835 CalcIndex::ToCallPrice => output.to_call_price = resp.to_call_price.parse().ok(),
1836 CalcIndex::EffectiveLeverage => {
1837 output.effective_leverage = resp.effective_leverage.parse().ok()
1838 }
1839 CalcIndex::LeverageRatio => {
1840 output.leverage_ratio = resp.leverage_ratio.parse().ok()
1841 }
1842 CalcIndex::ConversionRatio => {
1843 output.conversion_ratio = resp.conversion_ratio.parse().ok()
1844 }
1845 CalcIndex::BalancePoint => output.balance_point = resp.balance_point.parse().ok(),
1846 CalcIndex::OpenInterest => output.open_interest = Some(resp.open_interest),
1847 CalcIndex::Delta => output.delta = resp.delta.parse().ok(),
1848 CalcIndex::Gamma => output.gamma = resp.gamma.parse().ok(),
1849 CalcIndex::Theta => output.theta = resp.theta.parse().ok(),
1850 CalcIndex::Vega => output.vega = resp.vega.parse().ok(),
1851 CalcIndex::Rho => output.rho = resp.rho.parse().ok(),
1852 }
1853 }
1854
1855 output
1856 }
1857}
1858
1859#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1861pub enum SecurityListCategory {
1862 Overnight,
1864}
1865
1866impl_serialize_for_enum_string!(SecurityListCategory);
1867
1868#[derive(Debug, Serialize, Deserialize)]
1870pub struct Security {
1871 pub symbol: String,
1873 pub name_cn: String,
1875 pub name_en: String,
1877 pub name_hk: String,
1879}
1880
1881#[derive(Debug, Clone)]
1883pub struct QuotePackageDetail {
1884 pub key: String,
1886 pub name: String,
1888 pub description: String,
1890 pub start_at: OffsetDateTime,
1892 pub end_at: OffsetDateTime,
1894}
1895
1896impl TryFrom<quote::user_quote_level_detail::PackageDetail> for QuotePackageDetail {
1897 type Error = Error;
1898
1899 fn try_from(quote: quote::user_quote_level_detail::PackageDetail) -> Result<Self> {
1900 Ok(Self {
1901 key: quote.key,
1902 name: quote.name,
1903 description: quote.description,
1904 start_at: OffsetDateTime::from_unix_timestamp(quote.start)
1905 .map_err(|err| Error::parse_field_error("start_at", err))?,
1906 end_at: OffsetDateTime::from_unix_timestamp(quote.end)
1907 .map_err(|err| Error::parse_field_error("end_at", err))?,
1908 })
1909 }
1910}
1911
1912#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1914#[repr(i32)]
1915pub enum TradeSessions {
1916 Intraday = 0,
1918 All = 100,
1920}
1921
1922impl TradeSessions {
1923 #[inline]
1924 pub(crate) fn contains(&self, session: TradeSession) -> bool {
1925 match self {
1926 TradeSessions::Intraday => session == TradeSession::Intraday,
1927 TradeSessions::All => true,
1928 }
1929 }
1930}
1931
1932#[derive(Debug, Clone, Serialize, Deserialize)]
1934pub struct MarketTemperature {
1935 pub temperature: i32,
1937 #[serde(default)]
1939 pub description: String,
1940 pub valuation: i32,
1942 pub sentiment: i32,
1944 #[serde(
1946 serialize_with = "time::serde::rfc3339::serialize",
1947 deserialize_with = "serde_utils::timestamp::deserialize",
1948 alias = "updated_at"
1949 )]
1950 pub timestamp: OffsetDateTime,
1951}
1952
1953#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1955pub enum Granularity {
1956 Unknown,
1958 #[strum(serialize = "daily")]
1960 Daily,
1961 #[strum(serialize = "weekly")]
1963 Weekly,
1964 #[strum(serialize = "monthly")]
1966 Monthly,
1967}
1968
1969#[derive(Debug, Clone, Serialize, Deserialize)]
1971pub struct HistoryMarketTemperatureResponse {
1972 #[serde(rename = "type")]
1974 pub granularity: Granularity,
1975 #[serde(rename = "list")]
1977 pub records: Vec<MarketTemperature>,
1978}
1979
1980impl_serde_for_enum_string!(Granularity);
1981impl_default_for_enum_string!(
1982 OptionType,
1983 OptionDirection,
1984 WarrantType,
1985 SecurityBoard,
1986 Granularity
1987);
1988
1989#[cfg(test)]
1990mod tests {
1991 use serde::Deserialize;
1992
1993 use crate::{Market, quote::WatchlistGroup};
1994
1995 #[test]
1996 fn watch_list() {
1997 #[derive(Debug, Deserialize)]
1998 struct Response {
1999 groups: Vec<WatchlistGroup>,
2000 }
2001
2002 let json = r#"
2003 {
2004 "groups": [
2005 {
2006 "id": "1",
2007 "name": "Test",
2008 "securities": [
2009 {
2010 "symbol": "AAPL",
2011 "market": "US",
2012 "name": "Apple Inc.",
2013 "watched_price": "150.0",
2014 "watched_at": "1633036800"
2015 }
2016 ]
2017 }
2018 ]
2019 }
2020 "#;
2021
2022 let response: Response = serde_json::from_str(json).unwrap();
2023 assert_eq!(response.groups.len(), 1);
2024 assert_eq!(response.groups[0].id, 1);
2025 assert_eq!(response.groups[0].name, "Test");
2026 assert_eq!(response.groups[0].securities.len(), 1);
2027 assert_eq!(response.groups[0].securities[0].symbol, "AAPL");
2028 assert_eq!(response.groups[0].securities[0].market, Market::US);
2029 assert_eq!(response.groups[0].securities[0].name, "Apple Inc.");
2030 assert_eq!(
2031 response.groups[0].securities[0].watched_price,
2032 Some(decimal!(150.0))
2033 );
2034 assert_eq!(
2035 response.groups[0].securities[0].watched_at.unix_timestamp(),
2036 1633036800
2037 );
2038 }
2039}