1use longport_proto::quote::{self, Period, TradeStatus};
2use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use strum_macros::{Display, EnumString};
6use time::{Date, OffsetDateTime, Time};
7
8use crate::{
9 Error, Market, Result,
10 quote::{SubFlags, utils::parse_date},
11 serde_utils,
12};
13
14#[derive(Debug, Default, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
16pub enum TradeSession {
17 #[default]
19 Intraday,
20 Pre,
22 Post,
24 Overnight,
26}
27
28impl From<longport_proto::quote::TradeSession> for TradeSession {
29 fn from(value: longport_proto::quote::TradeSession) -> Self {
30 match value {
31 longport_proto::quote::TradeSession::NormalTrade => Self::Intraday,
32 longport_proto::quote::TradeSession::PreTrade => Self::Pre,
33 longport_proto::quote::TradeSession::PostTrade => Self::Post,
34 longport_proto::quote::TradeSession::OvernightTrade => Self::Overnight,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct Subscription {
42 pub symbol: String,
44 pub sub_types: SubFlags,
46 pub candlesticks: Vec<Period>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Depth {
53 pub position: i32,
55 pub price: Option<Decimal>,
57 pub volume: i64,
59 pub order_num: i64,
61}
62
63impl TryFrom<quote::Depth> for Depth {
64 type Error = Error;
65
66 fn try_from(depth: quote::Depth) -> Result<Self> {
67 Ok(Self {
68 position: depth.position,
69 price: depth.price.parse().ok(),
70 volume: depth.volume,
71 order_num: depth.order_num,
72 })
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct Brokers {
79 pub position: i32,
81 pub broker_ids: Vec<i32>,
83}
84
85impl From<quote::Brokers> for Brokers {
86 fn from(brokers: quote::Brokers) -> Self {
87 Self {
88 position: brokers.position,
89 broker_ids: brokers.broker_ids,
90 }
91 }
92}
93
94#[derive(Debug, FromPrimitive, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
96#[repr(i32)]
97pub enum TradeDirection {
98 #[num_enum(default)]
100 Neutral = 0,
101 Down = 1,
103 Up = 2,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct Trade {
110 pub price: Decimal,
112 pub volume: i64,
114 #[serde(with = "time::serde::rfc3339")]
116 pub timestamp: OffsetDateTime,
117 pub trade_type: String,
150 pub direction: TradeDirection,
152 pub trade_session: TradeSession,
154}
155
156impl TryFrom<quote::Trade> for Trade {
157 type Error = Error;
158
159 fn try_from(trade: quote::Trade) -> Result<Self> {
160 Ok(Self {
161 price: trade.price.parse().unwrap_or_default(),
162 volume: trade.volume,
163 timestamp: OffsetDateTime::from_unix_timestamp(trade.timestamp)
164 .map_err(|err| Error::parse_field_error("timestamp", err))?,
165 trade_type: trade.trade_type,
166 direction: trade.direction.into(),
167 trade_session: longport_proto::quote::TradeSession::try_from(trade.trade_session)
168 .unwrap_or_default()
169 .into(),
170 })
171 }
172}
173
174bitflags::bitflags! {
175 #[derive(Debug, Copy, Clone, Serialize,Deserialize)]
177 pub struct DerivativeType: u8 {
178 const OPTION = 0x1;
180
181 const WARRANT = 0x2;
183 }
184}
185
186#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display, Serialize, Deserialize)]
188#[allow(clippy::upper_case_acronyms)]
189pub enum SecurityBoard {
190 Unknown,
192 USMain,
194 USPink,
196 USDJI,
198 USNSDQ,
200 USSector,
202 USOption,
204 USOptionS,
206 HKEquity,
208 HKPreIPO,
210 HKWarrant,
212 HKHS,
214 HKSector,
216 SHMainConnect,
218 SHMainNonConnect,
220 SHSTAR,
222 CNIX,
224 CNSector,
226 SZMainConnect,
228 SZMainNonConnect,
230 SZGEMConnect,
232 SZGEMNonConnect,
234 SGMain,
236 STI,
238 SGSector,
240}
241
242#[derive(Debug, Serialize, Deserialize)]
244pub struct SecurityStaticInfo {
245 pub symbol: String,
247 pub name_cn: String,
249 pub name_en: String,
251 pub name_hk: String,
253 pub exchange: String,
255 pub currency: String,
257 pub lot_size: i32,
259 pub total_shares: i64,
261 pub circulating_shares: i64,
263 pub hk_shares: i64,
265 pub eps: Decimal,
267 pub eps_ttm: Decimal,
269 pub bps: Decimal,
271 pub dividend_yield: Decimal,
273 pub stock_derivatives: DerivativeType,
275 pub board: SecurityBoard,
277}
278
279impl TryFrom<quote::StaticInfo> for SecurityStaticInfo {
280 type Error = Error;
281
282 fn try_from(resp: quote::StaticInfo) -> Result<Self> {
283 Ok(SecurityStaticInfo {
284 symbol: resp.symbol,
285 name_cn: resp.name_cn,
286 name_en: resp.name_en,
287 name_hk: resp.name_hk,
288 exchange: resp.exchange,
289 currency: resp.currency,
290 lot_size: resp.lot_size,
291 total_shares: resp.total_shares,
292 circulating_shares: resp.circulating_shares,
293 hk_shares: resp.hk_shares,
294 eps: resp.eps.parse().unwrap_or_default(),
295 eps_ttm: resp.eps_ttm.parse().unwrap_or_default(),
296 bps: resp.bps.parse().unwrap_or_default(),
297 dividend_yield: resp.dividend_yield.parse().unwrap_or_default(),
298 stock_derivatives: resp.stock_derivatives.into_iter().fold(
299 DerivativeType::empty(),
300 |acc, value| match value {
301 1 => acc | DerivativeType::OPTION,
302 2 => acc | DerivativeType::WARRANT,
303 _ => acc,
304 },
305 ),
306 board: resp.board.parse().unwrap_or(SecurityBoard::Unknown),
307 })
308 }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct RealtimeQuote {
314 pub symbol: String,
316 pub last_done: Decimal,
318 pub open: Decimal,
320 pub high: Decimal,
322 pub low: Decimal,
324 pub timestamp: OffsetDateTime,
326 pub volume: i64,
328 pub turnover: Decimal,
330 pub trade_status: TradeStatus,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct PrePostQuote {
337 pub last_done: Decimal,
339 #[serde(with = "time::serde::rfc3339")]
341 pub timestamp: OffsetDateTime,
342 pub volume: i64,
344 pub turnover: Decimal,
346 pub high: Decimal,
348 pub low: Decimal,
350 pub prev_close: Decimal,
352}
353
354impl TryFrom<quote::PrePostQuote> for PrePostQuote {
355 type Error = Error;
356
357 fn try_from(quote: quote::PrePostQuote) -> Result<Self> {
358 Ok(Self {
359 last_done: quote.last_done.parse().unwrap_or_default(),
360 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
361 .map_err(|err| Error::parse_field_error("timestamp", err))?,
362 volume: quote.volume,
363 turnover: quote.turnover.parse().unwrap_or_default(),
364 high: quote.high.parse().unwrap_or_default(),
365 low: quote.low.parse().unwrap_or_default(),
366 prev_close: quote.prev_close.parse().unwrap_or_default(),
367 })
368 }
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct SecurityQuote {
374 pub symbol: String,
376 pub last_done: Decimal,
378 pub prev_close: Decimal,
380 pub open: Decimal,
382 pub high: Decimal,
384 pub low: Decimal,
386 #[serde(with = "time::serde::rfc3339")]
388 pub timestamp: OffsetDateTime,
389 pub volume: i64,
391 pub turnover: Decimal,
393 pub trade_status: TradeStatus,
395 pub pre_market_quote: Option<PrePostQuote>,
397 pub post_market_quote: Option<PrePostQuote>,
399 pub overnight_quote: Option<PrePostQuote>,
401}
402
403impl TryFrom<quote::SecurityQuote> for SecurityQuote {
404 type Error = Error;
405
406 fn try_from(quote: quote::SecurityQuote) -> Result<Self> {
407 Ok(Self {
408 symbol: quote.symbol,
409 last_done: quote.last_done.parse().unwrap_or_default(),
410 prev_close: quote.prev_close.parse().unwrap_or_default(),
411 open: quote.open.parse().unwrap_or_default(),
412 high: quote.high.parse().unwrap_or_default(),
413 low: quote.low.parse().unwrap_or_default(),
414 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
415 .map_err(|err| Error::parse_field_error("timestamp", err))?,
416 volume: quote.volume,
417 turnover: quote.turnover.parse().unwrap_or_default(),
418 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
419 pre_market_quote: quote.pre_market_quote.map(TryInto::try_into).transpose()?,
420 post_market_quote: quote.post_market_quote.map(TryInto::try_into).transpose()?,
421 overnight_quote: quote.over_night_quote.map(TryInto::try_into).transpose()?,
422 })
423 }
424}
425
426#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
428pub enum OptionType {
429 Unknown,
431 #[strum(serialize = "A")]
433 American,
434 #[strum(serialize = "U")]
436 Europe,
437}
438
439#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
441pub enum OptionDirection {
442 Unknown,
444 #[strum(serialize = "P")]
446 Put,
447 #[strum(serialize = "C")]
449 Call,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct OptionQuote {
455 pub symbol: String,
457 pub last_done: Decimal,
459 pub prev_close: Decimal,
461 pub open: Decimal,
463 pub high: Decimal,
465 pub low: Decimal,
467 #[serde(with = "time::serde::rfc3339")]
469 pub timestamp: OffsetDateTime,
470 pub volume: i64,
472 pub turnover: Decimal,
474 pub trade_status: TradeStatus,
476 pub implied_volatility: Decimal,
478 pub open_interest: i64,
480 pub expiry_date: Date,
482 pub strike_price: Decimal,
484 pub contract_multiplier: Decimal,
486 pub contract_type: OptionType,
488 pub contract_size: Decimal,
490 pub direction: OptionDirection,
492 pub historical_volatility: Decimal,
494 pub underlying_symbol: String,
496}
497
498impl TryFrom<quote::OptionQuote> for OptionQuote {
499 type Error = Error;
500
501 fn try_from(quote: quote::OptionQuote) -> Result<Self> {
502 let option_extend = quote.option_extend.unwrap_or_default();
503
504 Ok(Self {
505 symbol: quote.symbol,
506 last_done: quote.last_done.parse().unwrap_or_default(),
507 prev_close: quote.prev_close.parse().unwrap_or_default(),
508 open: quote.open.parse().unwrap_or_default(),
509 high: quote.high.parse().unwrap_or_default(),
510 low: quote.low.parse().unwrap_or_default(),
511 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
512 .map_err(|err| Error::parse_field_error("timestamp", err))?,
513 volume: quote.volume,
514 turnover: quote.turnover.parse().unwrap_or_default(),
515 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
516 implied_volatility: option_extend.implied_volatility.parse().unwrap_or_default(),
517 open_interest: option_extend.open_interest,
518 expiry_date: parse_date(&option_extend.expiry_date)
519 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
520 strike_price: option_extend.strike_price.parse().unwrap_or_default(),
521 contract_multiplier: option_extend
522 .contract_multiplier
523 .parse()
524 .unwrap_or_default(),
525 contract_type: option_extend.contract_type.parse().unwrap_or_default(),
526 contract_size: option_extend.contract_size.parse().unwrap_or_default(),
527 direction: option_extend.direction.parse().unwrap_or_default(),
528 historical_volatility: option_extend
529 .historical_volatility
530 .parse()
531 .unwrap_or_default(),
532 underlying_symbol: option_extend.underlying_symbol,
533 })
534 }
535}
536
537#[derive(
539 Debug,
540 Copy,
541 Clone,
542 Hash,
543 Eq,
544 PartialEq,
545 EnumString,
546 IntoPrimitive,
547 TryFromPrimitive,
548 Serialize,
549 Deserialize,
550)]
551#[repr(i32)]
552pub enum WarrantType {
553 Unknown = -1,
555 Call = 0,
557 Put = 1,
559 Bull = 2,
561 Bear = 3,
563 Inline = 4,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct WarrantQuote {
570 pub symbol: String,
572 pub last_done: Decimal,
574 pub prev_close: Decimal,
576 pub open: Decimal,
578 pub high: Decimal,
580 pub low: Decimal,
582 #[serde(with = "time::serde::rfc3339")]
584 pub timestamp: OffsetDateTime,
585 pub volume: i64,
587 pub turnover: Decimal,
589 pub trade_status: TradeStatus,
591 pub implied_volatility: Decimal,
593 pub expiry_date: Date,
595 pub last_trade_date: Date,
597 pub outstanding_ratio: Decimal,
599 pub outstanding_quantity: i64,
601 pub conversion_ratio: Decimal,
603 pub category: WarrantType,
605 pub strike_price: Decimal,
607 pub upper_strike_price: Decimal,
609 pub lower_strike_price: Decimal,
611 pub call_price: Decimal,
613 pub underlying_symbol: String,
615}
616
617impl TryFrom<quote::WarrantQuote> for WarrantQuote {
618 type Error = Error;
619
620 fn try_from(quote: quote::WarrantQuote) -> Result<Self> {
621 let warrant_extend = quote.warrant_extend.unwrap_or_default();
622
623 Ok(Self {
624 symbol: quote.symbol,
625 last_done: quote.last_done.parse().unwrap_or_default(),
626 prev_close: quote.prev_close.parse().unwrap_or_default(),
627 open: quote.open.parse().unwrap_or_default(),
628 high: quote.high.parse().unwrap_or_default(),
629 low: quote.low.parse().unwrap_or_default(),
630 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
631 .map_err(|err| Error::parse_field_error("timestamp", err))?,
632 volume: quote.volume,
633 turnover: quote.turnover.parse().unwrap_or_default(),
634 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
635 implied_volatility: warrant_extend
636 .implied_volatility
637 .parse()
638 .unwrap_or_default(),
639 expiry_date: parse_date(&warrant_extend.expiry_date)
640 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
641 last_trade_date: parse_date(&warrant_extend.last_trade_date)
642 .map_err(|err| Error::parse_field_error("last_trade_date", err))?,
643 outstanding_ratio: warrant_extend.outstanding_ratio.parse().unwrap_or_default(),
644 outstanding_quantity: warrant_extend.outstanding_qty,
645 conversion_ratio: warrant_extend.conversion_ratio.parse().unwrap_or_default(),
646 category: warrant_extend.category.parse().unwrap_or_default(),
647 strike_price: warrant_extend.strike_price.parse().unwrap_or_default(),
648 upper_strike_price: warrant_extend
649 .upper_strike_price
650 .parse()
651 .unwrap_or_default(),
652 lower_strike_price: warrant_extend
653 .lower_strike_price
654 .parse()
655 .unwrap_or_default(),
656 call_price: warrant_extend.call_price.parse().unwrap_or_default(),
657 underlying_symbol: warrant_extend.underlying_symbol,
658 })
659 }
660}
661
662#[derive(Debug, Clone, Default, Serialize, Deserialize)]
664pub struct SecurityDepth {
665 pub asks: Vec<Depth>,
667 pub bids: Vec<Depth>,
669}
670
671#[derive(Debug, Clone, Default, Serialize, Deserialize)]
673pub struct SecurityBrokers {
674 pub ask_brokers: Vec<Brokers>,
676 pub bid_brokers: Vec<Brokers>,
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct ParticipantInfo {
683 pub broker_ids: Vec<i32>,
685 pub name_cn: String,
687 pub name_en: String,
689 pub name_hk: String,
691}
692
693impl From<quote::ParticipantInfo> for ParticipantInfo {
694 fn from(info: quote::ParticipantInfo) -> Self {
695 Self {
696 broker_ids: info.broker_ids,
697 name_cn: info.participant_name_cn,
698 name_en: info.participant_name_en,
699 name_hk: info.participant_name_hk,
700 }
701 }
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize)]
706pub struct IntradayLine {
707 pub price: Decimal,
709 #[serde(with = "time::serde::rfc3339")]
711 pub timestamp: OffsetDateTime,
712 pub volume: i64,
714 pub turnover: Decimal,
716 pub avg_price: Decimal,
718}
719
720impl TryFrom<quote::Line> for IntradayLine {
721 type Error = Error;
722
723 fn try_from(value: quote::Line) -> Result<Self> {
724 Ok(Self {
725 price: value.price.parse().unwrap_or_default(),
726 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
727 .map_err(|err| Error::parse_field_error("timestamp", err))?,
728 volume: value.volume,
729 turnover: value.turnover.parse().unwrap_or_default(),
730 avg_price: value.avg_price.parse().unwrap_or_default(),
731 })
732 }
733}
734
735#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
737pub struct Candlestick {
738 pub close: Decimal,
740 pub open: Decimal,
742 pub low: Decimal,
744 pub high: Decimal,
746 pub volume: i64,
748 pub turnover: Decimal,
750 #[serde(with = "time::serde::rfc3339")]
752 pub timestamp: OffsetDateTime,
753 pub trade_session: TradeSession,
755}
756
757impl TryFrom<quote::Candlestick> for Candlestick {
758 type Error = Error;
759
760 fn try_from(value: quote::Candlestick) -> Result<Self> {
761 Ok(Self {
762 close: value.close.parse().unwrap_or_default(),
763 open: value.open.parse().unwrap_or_default(),
764 low: value.low.parse().unwrap_or_default(),
765 high: value.high.parse().unwrap_or_default(),
766 volume: value.volume,
767 turnover: value.turnover.parse().unwrap_or_default(),
768 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
769 .map_err(|err| Error::parse_field_error("timestamp", err))?,
770 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
771 .map_err(|err| Error::parse_field_error("trade_session", err))?
772 .into(),
773 })
774 }
775}
776
777impl From<(longport_candlesticks::Candlestick, TradeSession)> for Candlestick {
778 #[inline]
779 fn from(
780 (candlestick, trade_session): (longport_candlesticks::Candlestick, TradeSession),
781 ) -> Self {
782 Self {
783 close: candlestick.close,
784 open: candlestick.open,
785 low: candlestick.low,
786 high: candlestick.high,
787 volume: candlestick.volume,
788 turnover: candlestick.turnover,
789 timestamp: candlestick.time,
790 trade_session,
791 }
792 }
793}
794
795impl From<Candlestick> for longport_candlesticks::Candlestick {
796 #[inline]
797 fn from(candlestick: Candlestick) -> Self {
798 Self {
799 time: candlestick.timestamp,
800 open: candlestick.open,
801 high: candlestick.high,
802 low: candlestick.low,
803 close: candlestick.close,
804 volume: candlestick.volume,
805 turnover: candlestick.turnover,
806 }
807 }
808}
809
810#[derive(Debug, Clone, Serialize, Deserialize)]
812pub struct StrikePriceInfo {
813 pub price: Decimal,
815 pub call_symbol: String,
817 pub put_symbol: String,
819 pub standard: bool,
821}
822
823impl TryFrom<quote::StrikePriceInfo> for StrikePriceInfo {
824 type Error = Error;
825
826 fn try_from(value: quote::StrikePriceInfo) -> Result<Self> {
827 Ok(Self {
828 price: value.price.parse().unwrap_or_default(),
829 call_symbol: value.call_symbol,
830 put_symbol: value.put_symbol,
831 standard: value.standard,
832 })
833 }
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize)]
838pub struct IssuerInfo {
839 pub issuer_id: i32,
841 pub name_cn: String,
843 pub name_en: String,
845 pub name_hk: String,
847}
848
849impl From<quote::IssuerInfo> for IssuerInfo {
850 fn from(info: quote::IssuerInfo) -> Self {
851 Self {
852 issuer_id: info.id,
853 name_cn: info.name_cn,
854 name_en: info.name_en,
855 name_hk: info.name_hk,
856 }
857 }
858}
859
860#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
862#[repr(i32)]
863pub enum SortOrderType {
864 Ascending = 0,
866 Descending = 1,
868}
869
870#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
872#[repr(i32)]
873pub enum WarrantSortBy {
874 LastDone = 0,
876 ChangeRate = 1,
878 ChangeValue = 2,
880 Volume = 3,
882 Turnover = 4,
884 ExpiryDate = 5,
886 StrikePrice = 6,
888 UpperStrikePrice = 7,
890 LowerStrikePrice = 8,
892 OutstandingQuantity = 9,
894 OutstandingRatio = 10,
896 Premium = 11,
898 ItmOtm = 12,
900 ImpliedVolatility = 13,
902 Delta = 14,
904 CallPrice = 15,
906 ToCallPrice = 16,
908 EffectiveLeverage = 17,
910 LeverageRatio = 18,
912 ConversionRatio = 19,
914 BalancePoint = 20,
916 Status = 21,
918}
919
920#[allow(non_camel_case_types)]
922#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
923#[repr(i32)]
924pub enum FilterWarrantExpiryDate {
925 LT_3 = 1,
927 Between_3_6 = 2,
929 Between_6_12 = 3,
931 GT_12 = 4,
933}
934
935#[allow(non_camel_case_types)]
937#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
938#[repr(i32)]
939pub enum FilterWarrantInOutBoundsType {
940 In = 1,
942 Out = 2,
944}
945
946#[derive(
948 Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive, TryFromPrimitive, Serialize, Deserialize,
949)]
950#[repr(i32)]
951pub enum WarrantStatus {
952 Suspend = 2,
954 PrepareList = 3,
956 Normal = 4,
958}
959
960#[derive(Debug, Clone, Serialize, Deserialize)]
962pub struct WarrantInfo {
963 pub symbol: String,
965 pub warrant_type: WarrantType,
967 pub name: String,
969 pub last_done: Decimal,
971 pub change_rate: Decimal,
973 pub change_value: Decimal,
975 pub volume: i64,
977 pub turnover: Decimal,
979 pub expiry_date: Date,
981 pub strike_price: Option<Decimal>,
983 pub upper_strike_price: Option<Decimal>,
985 pub lower_strike_price: Option<Decimal>,
987 pub outstanding_qty: i64,
989 pub outstanding_ratio: Decimal,
991 pub premium: Decimal,
993 pub itm_otm: Option<Decimal>,
995 pub implied_volatility: Option<Decimal>,
997 pub delta: Option<Decimal>,
999 pub call_price: Option<Decimal>,
1001 pub to_call_price: Option<Decimal>,
1003 pub effective_leverage: Option<Decimal>,
1005 pub leverage_ratio: Decimal,
1007 pub conversion_ratio: Option<Decimal>,
1009 pub balance_point: Option<Decimal>,
1011 pub status: WarrantStatus,
1013}
1014
1015impl TryFrom<quote::FilterWarrant> for WarrantInfo {
1016 type Error = Error;
1017
1018 fn try_from(info: quote::FilterWarrant) -> Result<Self> {
1019 let r#type = WarrantType::try_from(info.r#type)
1020 .map_err(|err| Error::parse_field_error("type", err))?;
1021
1022 match r#type {
1023 WarrantType::Unknown => unreachable!(),
1024 WarrantType::Call | WarrantType::Put => Ok(Self {
1025 symbol: info.symbol,
1026 warrant_type: r#type,
1027 name: info.name,
1028 last_done: info.last_done.parse().unwrap_or_default(),
1029 change_rate: info.change_rate.parse().unwrap_or_default(),
1030 change_value: info.change_val.parse().unwrap_or_default(),
1031 volume: info.volume,
1032 turnover: info.turnover.parse().unwrap_or_default(),
1033 expiry_date: parse_date(&info.expiry_date)
1034 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1035 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1036 upper_strike_price: None,
1037 lower_strike_price: None,
1038 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1039 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1040 premium: info.premium.parse().unwrap_or_default(),
1041 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1042 implied_volatility: Some(info.last_done.parse().unwrap_or_default()),
1043 delta: Some(info.last_done.parse().unwrap_or_default()),
1044 call_price: None,
1045 to_call_price: None,
1046 effective_leverage: Some(info.last_done.parse().unwrap_or_default()),
1047 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1048 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1049 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1050 status: WarrantStatus::try_from(info.status)
1051 .map_err(|err| Error::parse_field_error("state", err))?,
1052 }),
1053 WarrantType::Bull | WarrantType::Bear => Ok(Self {
1054 symbol: info.symbol,
1055 warrant_type: r#type,
1056 name: info.name,
1057 last_done: info.last_done.parse().unwrap_or_default(),
1058 change_rate: info.change_rate.parse().unwrap_or_default(),
1059 change_value: info.change_val.parse().unwrap_or_default(),
1060 volume: info.volume,
1061 turnover: info.turnover.parse().unwrap_or_default(),
1062 expiry_date: parse_date(&info.expiry_date)
1063 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1064 strike_price: Some(info.last_done.parse().unwrap_or_default()),
1065 upper_strike_price: None,
1066 lower_strike_price: None,
1067 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1068 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1069 premium: info.premium.parse().unwrap_or_default(),
1070 itm_otm: Some(info.last_done.parse().unwrap_or_default()),
1071 implied_volatility: None,
1072 delta: None,
1073 call_price: Some(info.call_price.parse().unwrap_or_default()),
1074 to_call_price: Some(info.to_call_price.parse().unwrap_or_default()),
1075 effective_leverage: None,
1076 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1077 conversion_ratio: Some(info.last_done.parse().unwrap_or_default()),
1078 balance_point: Some(info.last_done.parse().unwrap_or_default()),
1079 status: WarrantStatus::try_from(info.status)
1080 .map_err(|err| Error::parse_field_error("state", err))?,
1081 }),
1082 WarrantType::Inline => Ok(Self {
1083 symbol: info.symbol,
1084 warrant_type: r#type,
1085 name: info.name,
1086 last_done: info.last_done.parse().unwrap_or_default(),
1087 change_rate: info.change_rate.parse().unwrap_or_default(),
1088 change_value: info.change_val.parse().unwrap_or_default(),
1089 volume: info.volume,
1090 turnover: info.turnover.parse().unwrap_or_default(),
1091 expiry_date: parse_date(&info.expiry_date)
1092 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1093 strike_price: None,
1094 upper_strike_price: Some(info.upper_strike_price.parse().unwrap_or_default()),
1095 lower_strike_price: Some(info.lower_strike_price.parse().unwrap_or_default()),
1096 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1097 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1098 premium: info.premium.parse().unwrap_or_default(),
1099 itm_otm: None,
1100 implied_volatility: None,
1101 delta: None,
1102 call_price: None,
1103 to_call_price: None,
1104 effective_leverage: None,
1105 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1106 conversion_ratio: None,
1107 balance_point: None,
1108 status: WarrantStatus::try_from(info.status)
1109 .map_err(|err| Error::parse_field_error("state", err))?,
1110 }),
1111 }
1112 }
1113}
1114
1115#[derive(Debug, Clone, Serialize, Deserialize)]
1117pub struct TradingSessionInfo {
1118 pub begin_time: Time,
1120 pub end_time: Time,
1122 pub trade_session: TradeSession,
1124}
1125
1126impl TryFrom<quote::TradePeriod> for TradingSessionInfo {
1127 type Error = Error;
1128
1129 fn try_from(value: quote::TradePeriod) -> Result<Self> {
1130 #[inline]
1131 fn parse_time(value: i32) -> ::std::result::Result<Time, time::error::ComponentRange> {
1132 Time::from_hms(((value / 100) % 100) as u8, (value % 100) as u8, 0)
1133 }
1134
1135 Ok(Self {
1136 begin_time: parse_time(value.beg_time)
1137 .map_err(|err| Error::parse_field_error("beg_time", err))?,
1138 end_time: parse_time(value.end_time)
1139 .map_err(|err| Error::parse_field_error("end_time", err))?,
1140 trade_session: longport_proto::quote::TradeSession::try_from(value.trade_session)
1141 .unwrap_or_default()
1142 .into(),
1143 })
1144 }
1145}
1146
1147#[derive(Debug, Clone, Serialize, Deserialize)]
1149pub struct MarketTradingSession {
1150 pub market: Market,
1152 pub trade_sessions: Vec<TradingSessionInfo>,
1154}
1155
1156impl TryFrom<quote::MarketTradePeriod> for MarketTradingSession {
1157 type Error = Error;
1158
1159 fn try_from(value: quote::MarketTradePeriod) -> Result<Self> {
1160 Ok(Self {
1161 market: value.market.parse().unwrap_or_default(),
1162 trade_sessions: value
1163 .trade_session
1164 .into_iter()
1165 .map(TryInto::try_into)
1166 .collect::<Result<Vec<_>>>()?,
1167 })
1168 }
1169}
1170
1171#[derive(Debug, Clone, Serialize, Deserialize)]
1173pub struct MarketTradingDays {
1174 pub trading_days: Vec<Date>,
1176 pub half_trading_days: Vec<Date>,
1178}
1179
1180#[derive(Debug, Clone, Serialize, Deserialize)]
1182pub struct CapitalFlowLine {
1183 pub inflow: Decimal,
1185 pub timestamp: OffsetDateTime,
1187}
1188
1189impl TryFrom<quote::capital_flow_intraday_response::CapitalFlowLine> for CapitalFlowLine {
1190 type Error = Error;
1191
1192 fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result<Self> {
1193 Ok(Self {
1194 inflow: value.inflow.parse().unwrap_or_default(),
1195 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1196 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1197 })
1198 }
1199}
1200
1201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1203pub struct CapitalDistribution {
1204 pub large: Decimal,
1206 pub medium: Decimal,
1208 pub small: Decimal,
1210}
1211
1212impl TryFrom<quote::capital_distribution_response::CapitalDistribution> for CapitalDistribution {
1213 type Error = Error;
1214
1215 fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result<Self> {
1216 Ok(Self {
1217 large: value.large.parse().unwrap_or_default(),
1218 medium: value.medium.parse().unwrap_or_default(),
1219 small: value.small.parse().unwrap_or_default(),
1220 })
1221 }
1222}
1223
1224#[derive(Debug, Clone, Serialize, Deserialize)]
1226pub struct CapitalDistributionResponse {
1227 pub timestamp: OffsetDateTime,
1229 pub capital_in: CapitalDistribution,
1231 pub capital_out: CapitalDistribution,
1233}
1234
1235impl TryFrom<quote::CapitalDistributionResponse> for CapitalDistributionResponse {
1236 type Error = Error;
1237
1238 fn try_from(value: quote::CapitalDistributionResponse) -> Result<Self> {
1239 Ok(Self {
1240 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1241 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1242 capital_in: value
1243 .capital_in
1244 .map(TryInto::try_into)
1245 .transpose()?
1246 .unwrap_or_default(),
1247 capital_out: value
1248 .capital_out
1249 .map(TryInto::try_into)
1250 .transpose()?
1251 .unwrap_or_default(),
1252 })
1253 }
1254}
1255
1256#[derive(Debug, Clone, Serialize, Deserialize)]
1258pub struct WatchlistSecurity {
1259 pub symbol: String,
1261 pub market: Market,
1263 pub name: String,
1265 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
1267 pub watched_price: Option<Decimal>,
1268 #[serde(
1270 serialize_with = "time::serde::rfc3339::serialize",
1271 deserialize_with = "serde_utils::timestamp::deserialize"
1272 )]
1273 pub watched_at: OffsetDateTime,
1274}
1275
1276#[derive(Debug, Clone, Serialize, Deserialize)]
1278pub struct WatchlistGroup {
1279 #[serde(with = "serde_utils::int64_str")]
1281 pub id: i64,
1282 pub name: String,
1284 pub securities: Vec<WatchlistSecurity>,
1286}
1287
1288#[derive(Debug, Clone)]
1290pub struct RequestCreateWatchlistGroup {
1291 pub name: String,
1293 pub securities: Option<Vec<String>>,
1295}
1296
1297impl RequestCreateWatchlistGroup {
1298 pub fn new(name: impl Into<String>) -> Self {
1300 Self {
1301 name: name.into(),
1302 securities: None,
1303 }
1304 }
1305
1306 pub fn securities<I, T>(self, securities: I) -> Self
1308 where
1309 I: IntoIterator<Item = T>,
1310 T: Into<String>,
1311 {
1312 Self {
1313 securities: Some(securities.into_iter().map(Into::into).collect()),
1314 ..self
1315 }
1316 }
1317}
1318
1319#[derive(Debug, Copy, Clone, Default, Serialize)]
1321#[serde(rename_all = "lowercase")]
1322pub enum SecuritiesUpdateMode {
1323 Add,
1325 Remove,
1327 #[default]
1329 Replace,
1330}
1331
1332#[derive(Debug, Clone)]
1334pub struct RequestUpdateWatchlistGroup {
1335 pub id: i64,
1337 pub name: Option<String>,
1339 pub securities: Option<Vec<String>>,
1341 pub mode: SecuritiesUpdateMode,
1343}
1344
1345impl RequestUpdateWatchlistGroup {
1346 #[inline]
1348 pub fn new(id: i64) -> Self {
1349 Self {
1350 id,
1351 name: None,
1352 securities: None,
1353 mode: SecuritiesUpdateMode::default(),
1354 }
1355 }
1356
1357 pub fn name(self, name: impl Into<String>) -> Self {
1359 Self {
1360 name: Some(name.into()),
1361 ..self
1362 }
1363 }
1364
1365 pub fn securities<I, T>(self, securities: I) -> Self
1367 where
1368 I: IntoIterator<Item = T>,
1369 T: Into<String>,
1370 {
1371 Self {
1372 securities: Some(securities.into_iter().map(Into::into).collect()),
1373 ..self
1374 }
1375 }
1376
1377 pub fn mode(self, mode: SecuritiesUpdateMode) -> Self {
1379 Self { mode, ..self }
1380 }
1381}
1382
1383#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1385pub enum CalcIndex {
1386 LastDone,
1388 ChangeValue,
1390 ChangeRate,
1392 Volume,
1394 Turnover,
1396 YtdChangeRate,
1398 TurnoverRate,
1400 TotalMarketValue,
1402 CapitalFlow,
1404 Amplitude,
1406 VolumeRatio,
1408 PeTtmRatio,
1410 PbRatio,
1412 DividendRatioTtm,
1414 FiveDayChangeRate,
1416 TenDayChangeRate,
1418 HalfYearChangeRate,
1420 FiveMinutesChangeRate,
1422 ExpiryDate,
1424 StrikePrice,
1426 UpperStrikePrice,
1428 LowerStrikePrice,
1430 OutstandingQty,
1432 OutstandingRatio,
1434 Premium,
1436 ItmOtm,
1438 ImpliedVolatility,
1440 WarrantDelta,
1442 CallPrice,
1444 ToCallPrice,
1446 EffectiveLeverage,
1448 LeverageRatio,
1450 ConversionRatio,
1452 BalancePoint,
1454 OpenInterest,
1456 Delta,
1458 Gamma,
1460 Theta,
1462 Vega,
1464 Rho,
1466}
1467
1468impl From<CalcIndex> for longport_proto::quote::CalcIndex {
1469 fn from(value: CalcIndex) -> Self {
1470 use longport_proto::quote::CalcIndex::*;
1471
1472 match value {
1473 CalcIndex::LastDone => CalcindexLastDone,
1474 CalcIndex::ChangeValue => CalcindexChangeVal,
1475 CalcIndex::ChangeRate => CalcindexChangeRate,
1476 CalcIndex::Volume => CalcindexVolume,
1477 CalcIndex::Turnover => CalcindexTurnover,
1478 CalcIndex::YtdChangeRate => CalcindexYtdChangeRate,
1479 CalcIndex::TurnoverRate => CalcindexTurnoverRate,
1480 CalcIndex::TotalMarketValue => CalcindexTotalMarketValue,
1481 CalcIndex::CapitalFlow => CalcindexCapitalFlow,
1482 CalcIndex::Amplitude => CalcindexAmplitude,
1483 CalcIndex::VolumeRatio => CalcindexVolumeRatio,
1484 CalcIndex::PeTtmRatio => CalcindexPeTtmRatio,
1485 CalcIndex::PbRatio => CalcindexPbRatio,
1486 CalcIndex::DividendRatioTtm => CalcindexDividendRatioTtm,
1487 CalcIndex::FiveDayChangeRate => CalcindexFiveDayChangeRate,
1488 CalcIndex::TenDayChangeRate => CalcindexTenDayChangeRate,
1489 CalcIndex::HalfYearChangeRate => CalcindexHalfYearChangeRate,
1490 CalcIndex::FiveMinutesChangeRate => CalcindexFiveMinutesChangeRate,
1491 CalcIndex::ExpiryDate => CalcindexExpiryDate,
1492 CalcIndex::StrikePrice => CalcindexStrikePrice,
1493 CalcIndex::UpperStrikePrice => CalcindexUpperStrikePrice,
1494 CalcIndex::LowerStrikePrice => CalcindexLowerStrikePrice,
1495 CalcIndex::OutstandingQty => CalcindexOutstandingQty,
1496 CalcIndex::OutstandingRatio => CalcindexOutstandingRatio,
1497 CalcIndex::Premium => CalcindexPremium,
1498 CalcIndex::ItmOtm => CalcindexItmOtm,
1499 CalcIndex::ImpliedVolatility => CalcindexImpliedVolatility,
1500 CalcIndex::WarrantDelta => CalcindexWarrantDelta,
1501 CalcIndex::CallPrice => CalcindexCallPrice,
1502 CalcIndex::ToCallPrice => CalcindexToCallPrice,
1503 CalcIndex::EffectiveLeverage => CalcindexEffectiveLeverage,
1504 CalcIndex::LeverageRatio => CalcindexLeverageRatio,
1505 CalcIndex::ConversionRatio => CalcindexConversionRatio,
1506 CalcIndex::BalancePoint => CalcindexBalancePoint,
1507 CalcIndex::OpenInterest => CalcindexOpenInterest,
1508 CalcIndex::Delta => CalcindexDelta,
1509 CalcIndex::Gamma => CalcindexGamma,
1510 CalcIndex::Theta => CalcindexTheta,
1511 CalcIndex::Vega => CalcindexVega,
1512 CalcIndex::Rho => CalcindexRho,
1513 }
1514 }
1515}
1516
1517#[derive(Debug, Clone, Serialize, Deserialize)]
1519pub struct SecurityCalcIndex {
1520 pub symbol: String,
1522 pub last_done: Option<Decimal>,
1524 pub change_value: Option<Decimal>,
1526 pub change_rate: Option<Decimal>,
1528 pub volume: Option<i64>,
1530 pub turnover: Option<Decimal>,
1532 pub ytd_change_rate: Option<Decimal>,
1534 pub turnover_rate: Option<Decimal>,
1536 pub total_market_value: Option<Decimal>,
1538 pub capital_flow: Option<Decimal>,
1540 pub amplitude: Option<Decimal>,
1542 pub volume_ratio: Option<Decimal>,
1544 pub pe_ttm_ratio: Option<Decimal>,
1546 pub pb_ratio: Option<Decimal>,
1548 pub dividend_ratio_ttm: Option<Decimal>,
1550 pub five_day_change_rate: Option<Decimal>,
1552 pub ten_day_change_rate: Option<Decimal>,
1554 pub half_year_change_rate: Option<Decimal>,
1556 pub five_minutes_change_rate: Option<Decimal>,
1558 pub expiry_date: Option<Date>,
1560 pub strike_price: Option<Decimal>,
1562 pub upper_strike_price: Option<Decimal>,
1564 pub lower_strike_price: Option<Decimal>,
1566 pub outstanding_qty: Option<i64>,
1568 pub outstanding_ratio: Option<Decimal>,
1570 pub premium: Option<Decimal>,
1572 pub itm_otm: Option<Decimal>,
1574 pub implied_volatility: Option<Decimal>,
1576 pub warrant_delta: Option<Decimal>,
1578 pub call_price: Option<Decimal>,
1580 pub to_call_price: Option<Decimal>,
1582 pub effective_leverage: Option<Decimal>,
1584 pub leverage_ratio: Option<Decimal>,
1586 pub conversion_ratio: Option<Decimal>,
1588 pub balance_point: Option<Decimal>,
1590 pub open_interest: Option<i64>,
1592 pub delta: Option<Decimal>,
1594 pub gamma: Option<Decimal>,
1596 pub theta: Option<Decimal>,
1598 pub vega: Option<Decimal>,
1600 pub rho: Option<Decimal>,
1602}
1603
1604impl SecurityCalcIndex {
1605 pub(crate) fn from_proto(
1606 resp: longport_proto::quote::SecurityCalcIndex,
1607 indexes: &[CalcIndex],
1608 ) -> Self {
1609 let mut output = SecurityCalcIndex {
1610 symbol: resp.symbol,
1611 last_done: None,
1612 change_value: None,
1613 change_rate: None,
1614 volume: None,
1615 turnover: None,
1616 ytd_change_rate: None,
1617 turnover_rate: None,
1618 total_market_value: None,
1619 capital_flow: None,
1620 amplitude: None,
1621 volume_ratio: None,
1622 pe_ttm_ratio: None,
1623 pb_ratio: None,
1624 dividend_ratio_ttm: None,
1625 five_day_change_rate: None,
1626 ten_day_change_rate: None,
1627 half_year_change_rate: None,
1628 five_minutes_change_rate: None,
1629 expiry_date: None,
1630 strike_price: None,
1631 upper_strike_price: None,
1632 lower_strike_price: None,
1633 outstanding_qty: None,
1634 outstanding_ratio: None,
1635 premium: None,
1636 itm_otm: None,
1637 implied_volatility: None,
1638 warrant_delta: None,
1639 call_price: None,
1640 to_call_price: None,
1641 effective_leverage: None,
1642 leverage_ratio: None,
1643 conversion_ratio: None,
1644 balance_point: None,
1645 open_interest: None,
1646 delta: None,
1647 gamma: None,
1648 theta: None,
1649 vega: None,
1650 rho: None,
1651 };
1652
1653 for index in indexes {
1654 match index {
1655 CalcIndex::LastDone => output.last_done = resp.last_done.parse().ok(),
1656 CalcIndex::ChangeValue => output.change_value = resp.change_val.parse().ok(),
1657 CalcIndex::ChangeRate => output.change_rate = resp.change_rate.parse().ok(),
1658 CalcIndex::Volume => output.volume = Some(resp.volume),
1659 CalcIndex::Turnover => output.turnover = resp.turnover.parse().ok(),
1660 CalcIndex::YtdChangeRate => {
1661 output.ytd_change_rate = resp.ytd_change_rate.parse().ok()
1662 }
1663 CalcIndex::TurnoverRate => output.turnover_rate = resp.turnover_rate.parse().ok(),
1664 CalcIndex::TotalMarketValue => {
1665 output.total_market_value = resp.total_market_value.parse().ok()
1666 }
1667 CalcIndex::CapitalFlow => output.capital_flow = resp.capital_flow.parse().ok(),
1668 CalcIndex::Amplitude => output.amplitude = resp.amplitude.parse().ok(),
1669 CalcIndex::VolumeRatio => output.volume_ratio = resp.volume_ratio.parse().ok(),
1670 CalcIndex::PeTtmRatio => output.pe_ttm_ratio = resp.pe_ttm_ratio.parse().ok(),
1671 CalcIndex::PbRatio => output.pb_ratio = resp.pb_ratio.parse().ok(),
1672 CalcIndex::DividendRatioTtm => {
1673 output.dividend_ratio_ttm = resp.dividend_ratio_ttm.parse().ok()
1674 }
1675 CalcIndex::FiveDayChangeRate => {
1676 output.five_day_change_rate = resp.five_day_change_rate.parse().ok()
1677 }
1678 CalcIndex::TenDayChangeRate => {
1679 output.ten_day_change_rate = resp.ten_day_change_rate.parse().ok()
1680 }
1681 CalcIndex::HalfYearChangeRate => {
1682 output.half_year_change_rate = resp.half_year_change_rate.parse().ok()
1683 }
1684 CalcIndex::FiveMinutesChangeRate => {
1685 output.five_minutes_change_rate = resp.five_minutes_change_rate.parse().ok()
1686 }
1687 CalcIndex::ExpiryDate => output.expiry_date = parse_date(&resp.expiry_date).ok(),
1688 CalcIndex::StrikePrice => output.strike_price = resp.strike_price.parse().ok(),
1689 CalcIndex::UpperStrikePrice => {
1690 output.upper_strike_price = resp.upper_strike_price.parse().ok()
1691 }
1692 CalcIndex::LowerStrikePrice => {
1693 output.lower_strike_price = resp.lower_strike_price.parse().ok()
1694 }
1695 CalcIndex::OutstandingQty => output.outstanding_qty = Some(resp.outstanding_qty),
1696 CalcIndex::OutstandingRatio => {
1697 output.outstanding_ratio = resp.outstanding_ratio.parse().ok()
1698 }
1699 CalcIndex::Premium => output.premium = resp.premium.parse().ok(),
1700 CalcIndex::ItmOtm => output.itm_otm = resp.itm_otm.parse().ok(),
1701 CalcIndex::ImpliedVolatility => {
1702 output.implied_volatility = resp.implied_volatility.parse().ok()
1703 }
1704 CalcIndex::WarrantDelta => output.warrant_delta = resp.warrant_delta.parse().ok(),
1705 CalcIndex::CallPrice => output.call_price = resp.call_price.parse().ok(),
1706 CalcIndex::ToCallPrice => output.to_call_price = resp.to_call_price.parse().ok(),
1707 CalcIndex::EffectiveLeverage => {
1708 output.effective_leverage = resp.effective_leverage.parse().ok()
1709 }
1710 CalcIndex::LeverageRatio => {
1711 output.leverage_ratio = resp.leverage_ratio.parse().ok()
1712 }
1713 CalcIndex::ConversionRatio => {
1714 output.conversion_ratio = resp.conversion_ratio.parse().ok()
1715 }
1716 CalcIndex::BalancePoint => output.balance_point = resp.balance_point.parse().ok(),
1717 CalcIndex::OpenInterest => output.open_interest = Some(resp.open_interest),
1718 CalcIndex::Delta => output.delta = resp.delta.parse().ok(),
1719 CalcIndex::Gamma => output.gamma = resp.gamma.parse().ok(),
1720 CalcIndex::Theta => output.theta = resp.theta.parse().ok(),
1721 CalcIndex::Vega => output.vega = resp.vega.parse().ok(),
1722 CalcIndex::Rho => output.rho = resp.rho.parse().ok(),
1723 }
1724 }
1725
1726 output
1727 }
1728}
1729
1730#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1732pub enum SecurityListCategory {
1733 Overnight,
1735}
1736
1737impl_serialize_for_enum_string!(SecurityListCategory);
1738
1739#[derive(Debug, Serialize, Deserialize)]
1741pub struct Security {
1742 pub symbol: String,
1744 pub name_cn: String,
1746 pub name_en: String,
1748 pub name_hk: String,
1750}
1751
1752#[derive(Debug, Clone)]
1754pub struct QuotePackageDetail {
1755 pub key: String,
1757 pub name: String,
1759 pub description: String,
1761 pub start_at: OffsetDateTime,
1763 pub end_at: OffsetDateTime,
1765}
1766
1767impl TryFrom<quote::user_quote_level_detail::PackageDetail> for QuotePackageDetail {
1768 type Error = Error;
1769
1770 fn try_from(quote: quote::user_quote_level_detail::PackageDetail) -> Result<Self> {
1771 Ok(Self {
1772 key: quote.key,
1773 name: quote.name,
1774 description: quote.description,
1775 start_at: OffsetDateTime::from_unix_timestamp(quote.start)
1776 .map_err(|err| Error::parse_field_error("start_at", err))?,
1777 end_at: OffsetDateTime::from_unix_timestamp(quote.end)
1778 .map_err(|err| Error::parse_field_error("end_at", err))?,
1779 })
1780 }
1781}
1782
1783#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1785#[repr(i32)]
1786pub enum TradeSessions {
1787 Intraday = 0,
1789 All = 100,
1791}
1792
1793impl TradeSessions {
1794 #[inline]
1795 pub(crate) fn contains(&self, session: TradeSession) -> bool {
1796 match self {
1797 TradeSessions::Intraday => session == TradeSession::Intraday,
1798 TradeSessions::All => true,
1799 }
1800 }
1801}
1802
1803#[derive(Debug, Clone, Serialize, Deserialize)]
1805pub struct MarketTemperature {
1806 pub temperature: i32,
1808 #[serde(default)]
1810 pub description: String,
1811 pub valuation: i32,
1813 pub sentiment: i32,
1815 #[serde(
1817 serialize_with = "time::serde::rfc3339::serialize",
1818 deserialize_with = "serde_utils::timestamp::deserialize",
1819 alias = "updated_at"
1820 )]
1821 pub timestamp: OffsetDateTime,
1822}
1823
1824#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1826pub enum Granularity {
1827 Unknown,
1829 #[strum(serialize = "daily")]
1831 Daily,
1832 #[strum(serialize = "weekly")]
1834 Weekly,
1835 #[strum(serialize = "monthly")]
1837 Monthly,
1838}
1839
1840#[derive(Debug, Clone, Serialize, Deserialize)]
1842pub struct HistoryMarketTemperatureResponse {
1843 #[serde(rename = "type")]
1845 pub granularity: Granularity,
1846 #[serde(rename = "list")]
1848 pub records: Vec<MarketTemperature>,
1849}
1850
1851impl_serde_for_enum_string!(Granularity);
1852impl_default_for_enum_string!(
1853 OptionType,
1854 OptionDirection,
1855 WarrantType,
1856 SecurityBoard,
1857 Granularity
1858);
1859
1860#[cfg(test)]
1861mod tests {
1862 use serde::Deserialize;
1863
1864 use crate::{Market, quote::WatchlistGroup};
1865
1866 #[test]
1867 fn watch_list() {
1868 #[derive(Debug, Deserialize)]
1869 struct Response {
1870 groups: Vec<WatchlistGroup>,
1871 }
1872
1873 let json = r#"
1874 {
1875 "groups": [
1876 {
1877 "id": "1",
1878 "name": "Test",
1879 "securities": [
1880 {
1881 "symbol": "AAPL",
1882 "market": "US",
1883 "name": "Apple Inc.",
1884 "watched_price": "150.0",
1885 "watched_at": "1633036800"
1886 }
1887 ]
1888 }
1889 ]
1890 }
1891 "#;
1892
1893 let response: Response = serde_json::from_str(json).unwrap();
1894 assert_eq!(response.groups.len(), 1);
1895 assert_eq!(response.groups[0].id, 1);
1896 assert_eq!(response.groups[0].name, "Test");
1897 assert_eq!(response.groups[0].securities.len(), 1);
1898 assert_eq!(response.groups[0].securities[0].symbol, "AAPL");
1899 assert_eq!(response.groups[0].securities[0].market, Market::US);
1900 assert_eq!(response.groups[0].securities[0].name, "Apple Inc.");
1901 assert_eq!(
1902 response.groups[0].securities[0].watched_price,
1903 Some(decimal!(150.0))
1904 );
1905 assert_eq!(
1906 response.groups[0].securities[0].watched_at.unix_timestamp(),
1907 1633036800
1908 );
1909 }
1910}