1use longbridge_candlesticks::CandlestickComponents;
2use longbridge_proto::quote::{self, Period, TradeStatus};
3use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use strum_macros::{Display, EnumString};
7use time::{Date, OffsetDateTime, Time};
8
9use crate::{
10 Error, Market, Result,
11 quote::{SubFlags, utils::parse_date},
12 serde_utils,
13};
14
15#[derive(Debug, Default, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
17pub enum TradeSession {
18 #[default]
20 Intraday,
21 Pre,
23 Post,
25 Overnight,
27}
28
29impl longbridge_candlesticks::TradeSessionType for TradeSession {
30 #[inline]
31 fn kind(&self) -> longbridge_candlesticks::TradeSessionKind {
32 match self {
33 TradeSession::Intraday => longbridge_candlesticks::TRADE_SESSION_INTRADAY,
34 TradeSession::Pre => longbridge_candlesticks::TRADE_SESSION_PRE,
35 TradeSession::Post => longbridge_candlesticks::TRADE_SESSION_POST,
36 TradeSession::Overnight => longbridge_candlesticks::TRADE_SESSION_OVERNIGHT,
37 }
38 }
39}
40
41impl From<longbridge_proto::quote::TradeSession> for TradeSession {
42 #[inline]
43 fn from(value: longbridge_proto::quote::TradeSession) -> Self {
44 match value {
45 longbridge_proto::quote::TradeSession::NormalTrade => Self::Intraday,
46 longbridge_proto::quote::TradeSession::PreTrade => Self::Pre,
47 longbridge_proto::quote::TradeSession::PostTrade => Self::Post,
48 longbridge_proto::quote::TradeSession::OvernightTrade => Self::Overnight,
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct Subscription {
56 pub symbol: String,
58 pub sub_types: SubFlags,
60 pub candlesticks: Vec<Period>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct Depth {
67 pub position: i32,
69 pub price: Option<Decimal>,
71 pub volume: i64,
73 pub order_num: i64,
75}
76
77impl TryFrom<quote::Depth> for Depth {
78 type Error = Error;
79
80 fn try_from(depth: quote::Depth) -> Result<Self> {
81 Ok(Self {
82 position: depth.position,
83 price: depth.price.parse().ok(),
84 volume: depth.volume,
85 order_num: depth.order_num,
86 })
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct Brokers {
93 pub position: i32,
95 pub broker_ids: Vec<i32>,
97}
98
99impl From<quote::Brokers> for Brokers {
100 fn from(brokers: quote::Brokers) -> Self {
101 Self {
102 position: brokers.position,
103 broker_ids: brokers.broker_ids,
104 }
105 }
106}
107
108#[derive(Debug, FromPrimitive, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
110#[repr(i32)]
111pub enum TradeDirection {
112 #[num_enum(default)]
114 Neutral = 0,
115 Down = 1,
117 Up = 2,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct Trade {
124 pub price: Decimal,
126 pub volume: i64,
128 #[serde(with = "time::serde::rfc3339")]
130 pub timestamp: OffsetDateTime,
131 pub trade_type: String,
164 pub direction: TradeDirection,
166 pub trade_session: TradeSession,
168}
169
170impl TryFrom<quote::Trade> for Trade {
171 type Error = Error;
172
173 fn try_from(trade: quote::Trade) -> Result<Self> {
174 Ok(Self {
175 price: trade.price.parse().unwrap_or_default(),
176 volume: trade.volume,
177 timestamp: OffsetDateTime::from_unix_timestamp(trade.timestamp)
178 .map_err(|err| Error::parse_field_error("timestamp", err))?,
179 trade_type: trade.trade_type,
180 direction: trade.direction.into(),
181 trade_session: longbridge_proto::quote::TradeSession::try_from(trade.trade_session)
182 .unwrap_or_default()
183 .into(),
184 })
185 }
186}
187
188impl longbridge_candlesticks::TradeType for Trade {
189 type PriceType = Decimal;
190 type VolumeType = i64;
191 type TurnoverType = Decimal;
192 type TradeSessionType = TradeSession;
193
194 #[inline]
195 fn time(&self) -> OffsetDateTime {
196 self.timestamp
197 }
198
199 #[inline]
200 fn price(&self) -> Self::PriceType {
201 self.price
202 }
203
204 #[inline]
205 fn volume(&self) -> Self::VolumeType {
206 self.volume
207 }
208
209 #[inline]
210 fn turnover(&self, lot_size: i32) -> Self::TurnoverType {
211 self.price * Decimal::from(self.volume * lot_size as i64)
212 }
213
214 #[inline]
215 fn trade_session(&self) -> TradeSession {
216 self.trade_session
217 }
218}
219
220bitflags::bitflags! {
221 #[derive(Debug, Copy, Clone, Serialize,Deserialize)]
223 pub struct DerivativeType: u8 {
224 const OPTION = 0x1;
226
227 const WARRANT = 0x2;
229 }
230}
231
232#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display, Serialize, Deserialize)]
234#[allow(clippy::upper_case_acronyms)]
235pub enum SecurityBoard {
236 Unknown,
238 USMain,
240 USPink,
242 USDJI,
244 USNSDQ,
246 USSector,
248 USOption,
250 USOptionS,
252 HKEquity,
254 HKPreIPO,
256 HKWarrant,
258 HKHS,
260 HKSector,
262 SHMainConnect,
264 SHMainNonConnect,
266 SHSTAR,
268 CNIX,
270 CNSector,
272 SZMainConnect,
274 SZMainNonConnect,
276 SZGEMConnect,
278 SZGEMNonConnect,
280 SGMain,
282 STI,
284 SGSector,
286 SPXIndex,
288 VIXIndex,
290}
291
292#[derive(Debug, Serialize, Deserialize)]
294pub struct SecurityStaticInfo {
295 pub symbol: String,
297 pub name_cn: String,
299 pub name_en: String,
301 pub name_hk: String,
303 pub exchange: String,
305 pub currency: String,
307 pub lot_size: i32,
309 pub total_shares: i64,
311 pub circulating_shares: i64,
313 pub hk_shares: i64,
315 pub eps: Decimal,
317 pub eps_ttm: Decimal,
319 pub bps: Decimal,
321 pub dividend_yield: Decimal,
323 pub stock_derivatives: DerivativeType,
325 pub board: SecurityBoard,
327}
328
329impl TryFrom<quote::StaticInfo> for SecurityStaticInfo {
330 type Error = Error;
331
332 fn try_from(resp: quote::StaticInfo) -> Result<Self> {
333 Ok(SecurityStaticInfo {
334 symbol: resp.symbol,
335 name_cn: resp.name_cn,
336 name_en: resp.name_en,
337 name_hk: resp.name_hk,
338 exchange: resp.exchange,
339 currency: resp.currency,
340 lot_size: resp.lot_size,
341 total_shares: resp.total_shares,
342 circulating_shares: resp.circulating_shares,
343 hk_shares: resp.hk_shares,
344 eps: resp.eps.parse().unwrap_or_default(),
345 eps_ttm: resp.eps_ttm.parse().unwrap_or_default(),
346 bps: resp.bps.parse().unwrap_or_default(),
347 dividend_yield: resp.dividend_yield.parse().unwrap_or_default(),
348 stock_derivatives: resp.stock_derivatives.into_iter().fold(
349 DerivativeType::empty(),
350 |acc, value| match value {
351 1 => acc | DerivativeType::OPTION,
352 2 => acc | DerivativeType::WARRANT,
353 _ => acc,
354 },
355 ),
356 board: resp.board.parse().unwrap_or(SecurityBoard::Unknown),
357 })
358 }
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct RealtimeQuote {
364 pub symbol: String,
366 pub last_done: Decimal,
368 pub open: Decimal,
370 pub high: Decimal,
372 pub low: Decimal,
374 pub timestamp: OffsetDateTime,
376 pub volume: i64,
378 pub turnover: Decimal,
380 pub trade_status: TradeStatus,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct PrePostQuote {
387 pub last_done: Decimal,
389 #[serde(with = "time::serde::rfc3339")]
391 pub timestamp: OffsetDateTime,
392 pub volume: i64,
394 pub turnover: Decimal,
396 pub high: Decimal,
398 pub low: Decimal,
400 pub prev_close: Decimal,
402}
403
404impl TryFrom<quote::PrePostQuote> for PrePostQuote {
405 type Error = Error;
406
407 fn try_from(quote: quote::PrePostQuote) -> Result<Self> {
408 Ok(Self {
409 last_done: quote.last_done.parse().unwrap_or_default(),
410 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
411 .map_err(|err| Error::parse_field_error("timestamp", err))?,
412 volume: quote.volume,
413 turnover: quote.turnover.parse().unwrap_or_default(),
414 high: quote.high.parse().unwrap_or_default(),
415 low: quote.low.parse().unwrap_or_default(),
416 prev_close: quote.prev_close.parse().unwrap_or_default(),
417 })
418 }
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct SecurityQuote {
424 pub symbol: String,
426 pub last_done: Decimal,
428 pub prev_close: Decimal,
430 pub open: Decimal,
432 pub high: Decimal,
434 pub low: Decimal,
436 #[serde(with = "time::serde::rfc3339")]
438 pub timestamp: OffsetDateTime,
439 pub volume: i64,
441 pub turnover: Decimal,
443 pub trade_status: TradeStatus,
445 pub pre_market_quote: Option<PrePostQuote>,
447 pub post_market_quote: Option<PrePostQuote>,
449 pub overnight_quote: Option<PrePostQuote>,
451}
452
453impl TryFrom<quote::SecurityQuote> for SecurityQuote {
454 type Error = Error;
455
456 fn try_from(quote: quote::SecurityQuote) -> Result<Self> {
457 Ok(Self {
458 symbol: quote.symbol,
459 last_done: quote.last_done.parse().unwrap_or_default(),
460 prev_close: quote.prev_close.parse().unwrap_or_default(),
461 open: quote.open.parse().unwrap_or_default(),
462 high: quote.high.parse().unwrap_or_default(),
463 low: quote.low.parse().unwrap_or_default(),
464 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
465 .map_err(|err| Error::parse_field_error("timestamp", err))?,
466 volume: quote.volume,
467 turnover: quote.turnover.parse().unwrap_or_default(),
468 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
469 pre_market_quote: quote.pre_market_quote.map(TryInto::try_into).transpose()?,
470 post_market_quote: quote.post_market_quote.map(TryInto::try_into).transpose()?,
471 overnight_quote: quote.over_night_quote.map(TryInto::try_into).transpose()?,
472 })
473 }
474}
475
476#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
478pub enum OptionType {
479 Unknown,
481 #[strum(serialize = "A")]
483 American,
484 #[strum(serialize = "U")]
486 Europe,
487}
488
489#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Serialize, Deserialize)]
491pub enum OptionDirection {
492 Unknown,
494 #[strum(serialize = "P")]
496 Put,
497 #[strum(serialize = "C")]
499 Call,
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct OptionQuote {
505 pub symbol: String,
507 pub last_done: Decimal,
509 pub prev_close: Decimal,
511 pub open: Decimal,
513 pub high: Decimal,
515 pub low: Decimal,
517 #[serde(with = "time::serde::rfc3339")]
519 pub timestamp: OffsetDateTime,
520 pub volume: i64,
522 pub turnover: Decimal,
524 pub trade_status: TradeStatus,
526 pub implied_volatility: Decimal,
528 pub open_interest: i64,
530 pub expiry_date: Date,
532 pub strike_price: Decimal,
534 pub contract_multiplier: Decimal,
536 pub contract_type: OptionType,
538 pub contract_size: Decimal,
540 pub direction: OptionDirection,
542 pub historical_volatility: Decimal,
544 pub underlying_symbol: String,
546}
547
548impl TryFrom<quote::OptionQuote> for OptionQuote {
549 type Error = Error;
550
551 fn try_from(quote: quote::OptionQuote) -> Result<Self> {
552 let option_extend = quote.option_extend.unwrap_or_default();
553
554 Ok(Self {
555 symbol: quote.symbol,
556 last_done: quote.last_done.parse().unwrap_or_default(),
557 prev_close: quote.prev_close.parse().unwrap_or_default(),
558 open: quote.open.parse().unwrap_or_default(),
559 high: quote.high.parse().unwrap_or_default(),
560 low: quote.low.parse().unwrap_or_default(),
561 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
562 .map_err(|err| Error::parse_field_error("timestamp", err))?,
563 volume: quote.volume,
564 turnover: quote.turnover.parse().unwrap_or_default(),
565 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
566 implied_volatility: option_extend.implied_volatility.parse().unwrap_or_default(),
567 open_interest: option_extend.open_interest,
568 expiry_date: parse_date(&option_extend.expiry_date)
569 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
570 strike_price: option_extend.strike_price.parse().unwrap_or_default(),
571 contract_multiplier: option_extend
572 .contract_multiplier
573 .parse()
574 .unwrap_or_default(),
575 contract_type: option_extend.contract_type.parse().unwrap_or_default(),
576 contract_size: option_extend.contract_size.parse().unwrap_or_default(),
577 direction: option_extend.direction.parse().unwrap_or_default(),
578 historical_volatility: option_extend
579 .historical_volatility
580 .parse()
581 .unwrap_or_default(),
582 underlying_symbol: option_extend.underlying_symbol,
583 })
584 }
585}
586
587#[derive(
589 Debug,
590 Copy,
591 Clone,
592 Hash,
593 Eq,
594 PartialEq,
595 EnumString,
596 IntoPrimitive,
597 TryFromPrimitive,
598 Serialize,
599 Deserialize,
600)]
601#[repr(i32)]
602pub enum WarrantType {
603 Unknown = -1,
605 Call = 0,
607 Put = 1,
609 Bull = 2,
611 Bear = 3,
613 Inline = 4,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct WarrantQuote {
620 pub symbol: String,
622 pub last_done: Decimal,
624 pub prev_close: Decimal,
626 pub open: Decimal,
628 pub high: Decimal,
630 pub low: Decimal,
632 #[serde(with = "time::serde::rfc3339")]
634 pub timestamp: OffsetDateTime,
635 pub volume: i64,
637 pub turnover: Decimal,
639 pub trade_status: TradeStatus,
641 pub implied_volatility: Decimal,
643 pub expiry_date: Date,
645 pub last_trade_date: Date,
647 pub outstanding_ratio: Decimal,
649 pub outstanding_quantity: i64,
651 pub conversion_ratio: Decimal,
653 pub category: WarrantType,
655 pub strike_price: Decimal,
657 pub upper_strike_price: Decimal,
659 pub lower_strike_price: Decimal,
661 pub call_price: Decimal,
663 pub underlying_symbol: String,
665}
666
667impl TryFrom<quote::WarrantQuote> for WarrantQuote {
668 type Error = Error;
669
670 fn try_from(quote: quote::WarrantQuote) -> Result<Self> {
671 let warrant_extend = quote.warrant_extend.unwrap_or_default();
672
673 Ok(Self {
674 symbol: quote.symbol,
675 last_done: quote.last_done.parse().unwrap_or_default(),
676 prev_close: quote.prev_close.parse().unwrap_or_default(),
677 open: quote.open.parse().unwrap_or_default(),
678 high: quote.high.parse().unwrap_or_default(),
679 low: quote.low.parse().unwrap_or_default(),
680 timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)
681 .map_err(|err| Error::parse_field_error("timestamp", err))?,
682 volume: quote.volume,
683 turnover: quote.turnover.parse().unwrap_or_default(),
684 trade_status: TradeStatus::try_from(quote.trade_status).unwrap_or_default(),
685 implied_volatility: warrant_extend
686 .implied_volatility
687 .parse()
688 .unwrap_or_default(),
689 expiry_date: parse_date(&warrant_extend.expiry_date)
690 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
691 last_trade_date: parse_date(&warrant_extend.last_trade_date)
692 .map_err(|err| Error::parse_field_error("last_trade_date", err))?,
693 outstanding_ratio: warrant_extend.outstanding_ratio.parse().unwrap_or_default(),
694 outstanding_quantity: warrant_extend.outstanding_qty,
695 conversion_ratio: warrant_extend.conversion_ratio.parse().unwrap_or_default(),
696 category: warrant_extend.category.parse().unwrap_or_default(),
697 strike_price: warrant_extend.strike_price.parse().unwrap_or_default(),
698 upper_strike_price: warrant_extend
699 .upper_strike_price
700 .parse()
701 .unwrap_or_default(),
702 lower_strike_price: warrant_extend
703 .lower_strike_price
704 .parse()
705 .unwrap_or_default(),
706 call_price: warrant_extend.call_price.parse().unwrap_or_default(),
707 underlying_symbol: warrant_extend.underlying_symbol,
708 })
709 }
710}
711
712#[derive(Debug, Clone, Default, Serialize, Deserialize)]
714pub struct SecurityDepth {
715 pub asks: Vec<Depth>,
717 pub bids: Vec<Depth>,
719}
720
721#[derive(Debug, Clone, Default, Serialize, Deserialize)]
723pub struct SecurityBrokers {
724 pub ask_brokers: Vec<Brokers>,
726 pub bid_brokers: Vec<Brokers>,
728}
729
730#[derive(Debug, Clone, Serialize, Deserialize)]
732pub struct ParticipantInfo {
733 pub broker_ids: Vec<i32>,
735 pub name_cn: String,
737 pub name_en: String,
739 pub name_hk: String,
741}
742
743impl From<quote::ParticipantInfo> for ParticipantInfo {
744 fn from(info: quote::ParticipantInfo) -> Self {
745 Self {
746 broker_ids: info.broker_ids,
747 name_cn: info.participant_name_cn,
748 name_en: info.participant_name_en,
749 name_hk: info.participant_name_hk,
750 }
751 }
752}
753
754#[derive(Debug, Clone, Serialize, Deserialize)]
756pub struct IntradayLine {
757 pub price: Decimal,
759 #[serde(with = "time::serde::rfc3339")]
761 pub timestamp: OffsetDateTime,
762 pub volume: i64,
764 pub turnover: Decimal,
766 pub avg_price: Decimal,
768}
769
770impl TryFrom<quote::Line> for IntradayLine {
771 type Error = Error;
772
773 fn try_from(value: quote::Line) -> Result<Self> {
774 Ok(Self {
775 price: value.price.parse().unwrap_or_default(),
776 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
777 .map_err(|err| Error::parse_field_error("timestamp", err))?,
778 volume: value.volume,
779 turnover: value.turnover.parse().unwrap_or_default(),
780 avg_price: value.avg_price.parse().unwrap_or_default(),
781 })
782 }
783}
784
785#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
787pub struct Candlestick {
788 pub close: Decimal,
790 pub open: Decimal,
792 pub low: Decimal,
794 pub high: Decimal,
796 pub volume: i64,
798 pub turnover: Decimal,
800 #[serde(with = "time::serde::rfc3339")]
802 pub timestamp: OffsetDateTime,
803 pub trade_session: TradeSession,
805 open_updated: bool,
806}
807
808impl TryFrom<quote::Candlestick> for Candlestick {
809 type Error = Error;
810
811 fn try_from(value: quote::Candlestick) -> Result<Self> {
812 Ok(Self {
813 close: value.close.parse().unwrap_or_default(),
814 open: value.open.parse().unwrap_or_default(),
815 low: value.low.parse().unwrap_or_default(),
816 high: value.high.parse().unwrap_or_default(),
817 volume: value.volume,
818 turnover: value.turnover.parse().unwrap_or_default(),
819 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
820 .map_err(|err| Error::parse_field_error("timestamp", err))?,
821 trade_session: longbridge_proto::quote::TradeSession::try_from(value.trade_session)
822 .map_err(|err| Error::parse_field_error("trade_session", err))?
823 .into(),
824 open_updated: true,
825 })
826 }
827}
828
829impl longbridge_candlesticks::CandlestickType for Candlestick {
830 type PriceType = Decimal;
831 type VolumeType = i64;
832 type TurnoverType = Decimal;
833 type TradeSessionType = TradeSession;
834
835 #[inline]
836 fn new(
837 components: CandlestickComponents<
838 Self::PriceType,
839 Self::VolumeType,
840 Self::TurnoverType,
841 Self::TradeSessionType,
842 >,
843 ) -> Self {
844 Self {
845 timestamp: components.time,
846 open: components.open,
847 high: components.high,
848 low: components.low,
849 close: components.close,
850 volume: components.volume,
851 turnover: components.turnover,
852 trade_session: components.trade_session,
853 open_updated: components.open_updated,
854 }
855 }
856
857 #[inline]
858 fn time(&self) -> OffsetDateTime {
859 self.timestamp
860 }
861
862 #[inline]
863 fn set_time(&mut self, time: OffsetDateTime) {
864 self.timestamp = time;
865 }
866
867 #[inline]
868 fn open(&self) -> Self::PriceType {
869 self.open
870 }
871
872 #[inline]
873 fn set_open(&mut self, open: Self::PriceType) {
874 self.open = open;
875 }
876
877 #[inline]
878 fn high(&self) -> Self::PriceType {
879 self.high
880 }
881
882 #[inline]
883 fn set_high(&mut self, high: Self::PriceType) {
884 self.high = high;
885 }
886
887 #[inline]
888 fn low(&self) -> Self::PriceType {
889 self.low
890 }
891
892 #[inline]
893 fn set_low(&mut self, low: Self::PriceType) {
894 self.low = low;
895 }
896
897 #[inline]
898 fn close(&self) -> Self::PriceType {
899 self.close
900 }
901
902 #[inline]
903 fn set_close(&mut self, close: Self::PriceType) {
904 self.close = close;
905 }
906
907 #[inline]
908 fn volume(&self) -> Self::VolumeType {
909 self.volume
910 }
911
912 #[inline]
913 fn set_volume(&mut self, volume: Self::VolumeType) {
914 self.volume = volume;
915 }
916
917 #[inline]
918 fn turnover(&self) -> Self::TurnoverType {
919 self.turnover
920 }
921
922 #[inline]
923 fn set_turnover(&mut self, turnover: Self::TurnoverType) {
924 self.turnover = turnover;
925 }
926
927 #[inline]
928 fn trade_session(&self) -> Self::TradeSessionType {
929 self.trade_session
930 }
931
932 #[inline]
933 fn set_open_updated(&mut self, updated: bool) {
934 self.open_updated = updated;
935 }
936
937 #[inline]
938 fn open_updated(&self) -> bool {
939 self.open_updated
940 }
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
945pub struct StrikePriceInfo {
946 pub price: Decimal,
948 pub call_symbol: String,
950 pub put_symbol: String,
952 pub standard: bool,
954}
955
956impl TryFrom<quote::StrikePriceInfo> for StrikePriceInfo {
957 type Error = Error;
958
959 fn try_from(value: quote::StrikePriceInfo) -> Result<Self> {
960 Ok(Self {
961 price: value.price.parse().unwrap_or_default(),
962 call_symbol: value.call_symbol,
963 put_symbol: value.put_symbol,
964 standard: value.standard,
965 })
966 }
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize)]
971pub struct IssuerInfo {
972 pub issuer_id: i32,
974 pub name_cn: String,
976 pub name_en: String,
978 pub name_hk: String,
980}
981
982impl From<quote::IssuerInfo> for IssuerInfo {
983 fn from(info: quote::IssuerInfo) -> Self {
984 Self {
985 issuer_id: info.id,
986 name_cn: info.name_cn,
987 name_en: info.name_en,
988 name_hk: info.name_hk,
989 }
990 }
991}
992
993#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
995#[repr(i32)]
996pub enum SortOrderType {
997 Ascending = 0,
999 Descending = 1,
1001}
1002
1003#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1005#[repr(i32)]
1006pub enum WarrantSortBy {
1007 LastDone = 0,
1009 ChangeRate = 1,
1011 ChangeValue = 2,
1013 Volume = 3,
1015 Turnover = 4,
1017 ExpiryDate = 5,
1019 StrikePrice = 6,
1021 UpperStrikePrice = 7,
1023 LowerStrikePrice = 8,
1025 OutstandingQuantity = 9,
1027 OutstandingRatio = 10,
1029 Premium = 11,
1031 ItmOtm = 12,
1033 ImpliedVolatility = 13,
1035 Delta = 14,
1037 CallPrice = 15,
1039 ToCallPrice = 16,
1041 EffectiveLeverage = 17,
1043 LeverageRatio = 18,
1045 ConversionRatio = 19,
1047 BalancePoint = 20,
1049 Status = 21,
1051}
1052
1053#[allow(non_camel_case_types)]
1055#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1056#[repr(i32)]
1057pub enum FilterWarrantExpiryDate {
1058 LT_3 = 1,
1060 Between_3_6 = 2,
1062 Between_6_12 = 3,
1064 GT_12 = 4,
1066}
1067
1068#[allow(non_camel_case_types)]
1070#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive)]
1071#[repr(i32)]
1072pub enum FilterWarrantInOutBoundsType {
1073 In = 1,
1075 Out = 2,
1077}
1078
1079#[derive(
1081 Debug, Copy, Clone, Hash, Eq, PartialEq, IntoPrimitive, TryFromPrimitive, Serialize, Deserialize,
1082)]
1083#[repr(i32)]
1084pub enum WarrantStatus {
1085 Suspend = 2,
1087 PrepareList = 3,
1089 Normal = 4,
1091}
1092
1093#[derive(Debug, Clone, Serialize, Deserialize)]
1095pub struct WarrantInfo {
1096 pub symbol: String,
1098 pub warrant_type: WarrantType,
1100 pub name: String,
1102 pub last_done: Decimal,
1104 pub change_rate: Decimal,
1106 pub change_value: Decimal,
1108 pub volume: i64,
1110 pub turnover: Decimal,
1112 pub expiry_date: Date,
1114 pub strike_price: Option<Decimal>,
1116 pub upper_strike_price: Option<Decimal>,
1118 pub lower_strike_price: Option<Decimal>,
1120 pub outstanding_qty: i64,
1122 pub outstanding_ratio: Decimal,
1124 pub premium: Decimal,
1126 pub itm_otm: Option<Decimal>,
1128 pub implied_volatility: Option<Decimal>,
1130 pub delta: Option<Decimal>,
1132 pub call_price: Option<Decimal>,
1134 pub to_call_price: Option<Decimal>,
1136 pub effective_leverage: Option<Decimal>,
1138 pub leverage_ratio: Decimal,
1140 pub conversion_ratio: Option<Decimal>,
1142 pub balance_point: Option<Decimal>,
1144 pub status: WarrantStatus,
1146}
1147
1148impl TryFrom<quote::FilterWarrant> for WarrantInfo {
1149 type Error = Error;
1150
1151 fn try_from(info: quote::FilterWarrant) -> Result<Self> {
1152 let r#type = WarrantType::try_from(info.r#type)
1153 .map_err(|err| Error::parse_field_error("type", err))?;
1154
1155 match r#type {
1156 WarrantType::Unknown => unreachable!(),
1157 WarrantType::Call | WarrantType::Put => Ok(Self {
1158 symbol: info.symbol,
1159 warrant_type: r#type,
1160 name: info.name,
1161 last_done: info.last_done.parse().unwrap_or_default(),
1162 change_rate: info.change_rate.parse().unwrap_or_default(),
1163 change_value: info.change_val.parse().unwrap_or_default(),
1164 volume: info.volume,
1165 turnover: info.turnover.parse().unwrap_or_default(),
1166 expiry_date: parse_date(&info.expiry_date)
1167 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1168 strike_price: info.strike_price.parse().ok(),
1169 upper_strike_price: info.upper_strike_price.parse().ok(),
1170 lower_strike_price: info.lower_strike_price.parse().ok(),
1171 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1172 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1173 premium: info.premium.parse().unwrap_or_default(),
1174 itm_otm: info.itm_otm.parse().ok(),
1175 implied_volatility: info.implied_volatility.parse().ok(),
1176 delta: info.delta.parse().ok(),
1177 call_price: info.call_price.parse().ok(),
1178 to_call_price: info.to_call_price.parse().ok(),
1179 effective_leverage: info.effective_leverage.parse().ok(),
1180 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1181 conversion_ratio: info.conversion_ratio.parse().ok(),
1182 balance_point: info.balance_point.parse().ok(),
1183 status: WarrantStatus::try_from(info.status)
1184 .map_err(|err| Error::parse_field_error("state", err))?,
1185 }),
1186 WarrantType::Bull | WarrantType::Bear => Ok(Self {
1187 symbol: info.symbol,
1188 warrant_type: r#type,
1189 name: info.name,
1190 last_done: info.last_done.parse().unwrap_or_default(),
1191 change_rate: info.change_rate.parse().unwrap_or_default(),
1192 change_value: info.change_val.parse().unwrap_or_default(),
1193 volume: info.volume,
1194 turnover: info.turnover.parse().unwrap_or_default(),
1195 expiry_date: parse_date(&info.expiry_date)
1196 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1197 strike_price: Some(info.strike_price.parse().unwrap_or_default()),
1198 upper_strike_price: None,
1199 lower_strike_price: None,
1200 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1201 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1202 premium: info.premium.parse().unwrap_or_default(),
1203 itm_otm: Some(info.itm_otm.parse().unwrap_or_default()),
1204 implied_volatility: None,
1205 delta: None,
1206 call_price: Some(info.call_price.parse().unwrap_or_default()),
1207 to_call_price: Some(info.to_call_price.parse().unwrap_or_default()),
1208 effective_leverage: None,
1209 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1210 conversion_ratio: Some(info.conversion_ratio.parse().unwrap_or_default()),
1211 balance_point: Some(info.balance_point.parse().unwrap_or_default()),
1212 status: WarrantStatus::try_from(info.status)
1213 .map_err(|err| Error::parse_field_error("state", err))?,
1214 }),
1215 WarrantType::Inline => Ok(Self {
1216 symbol: info.symbol,
1217 warrant_type: r#type,
1218 name: info.name,
1219 last_done: info.last_done.parse().unwrap_or_default(),
1220 change_rate: info.change_rate.parse().unwrap_or_default(),
1221 change_value: info.change_val.parse().unwrap_or_default(),
1222 volume: info.volume,
1223 turnover: info.turnover.parse().unwrap_or_default(),
1224 expiry_date: parse_date(&info.expiry_date)
1225 .map_err(|err| Error::parse_field_error("expiry_date", err))?,
1226 strike_price: None,
1227 upper_strike_price: Some(info.upper_strike_price.parse().unwrap_or_default()),
1228 lower_strike_price: Some(info.lower_strike_price.parse().unwrap_or_default()),
1229 outstanding_qty: info.outstanding_qty.parse().unwrap_or_default(),
1230 outstanding_ratio: info.outstanding_ratio.parse().unwrap_or_default(),
1231 premium: info.premium.parse().unwrap_or_default(),
1232 itm_otm: None,
1233 implied_volatility: None,
1234 delta: None,
1235 call_price: None,
1236 to_call_price: None,
1237 effective_leverage: None,
1238 leverage_ratio: info.leverage_ratio.parse().unwrap_or_default(),
1239 conversion_ratio: None,
1240 balance_point: None,
1241 status: WarrantStatus::try_from(info.status)
1242 .map_err(|err| Error::parse_field_error("state", err))?,
1243 }),
1244 }
1245 }
1246}
1247
1248#[derive(Debug, Clone, Serialize, Deserialize)]
1250pub struct TradingSessionInfo {
1251 pub begin_time: Time,
1253 pub end_time: Time,
1255 pub trade_session: TradeSession,
1257}
1258
1259impl TryFrom<quote::TradePeriod> for TradingSessionInfo {
1260 type Error = Error;
1261
1262 fn try_from(value: quote::TradePeriod) -> Result<Self> {
1263 #[inline]
1264 fn parse_time(value: i32) -> ::std::result::Result<Time, time::error::ComponentRange> {
1265 Time::from_hms(((value / 100) % 100) as u8, (value % 100) as u8, 0)
1266 }
1267
1268 Ok(Self {
1269 begin_time: parse_time(value.beg_time)
1270 .map_err(|err| Error::parse_field_error("beg_time", err))?,
1271 end_time: parse_time(value.end_time)
1272 .map_err(|err| Error::parse_field_error("end_time", err))?,
1273 trade_session: longbridge_proto::quote::TradeSession::try_from(value.trade_session)
1274 .unwrap_or_default()
1275 .into(),
1276 })
1277 }
1278}
1279
1280#[derive(Debug, Clone, Serialize, Deserialize)]
1282pub struct MarketTradingSession {
1283 pub market: Market,
1285 pub trade_sessions: Vec<TradingSessionInfo>,
1287}
1288
1289impl TryFrom<quote::MarketTradePeriod> for MarketTradingSession {
1290 type Error = Error;
1291
1292 fn try_from(value: quote::MarketTradePeriod) -> Result<Self> {
1293 Ok(Self {
1294 market: value.market.parse().unwrap_or_default(),
1295 trade_sessions: value
1296 .trade_session
1297 .into_iter()
1298 .map(TryInto::try_into)
1299 .collect::<Result<Vec<_>>>()?,
1300 })
1301 }
1302}
1303
1304#[derive(Debug, Clone, Serialize, Deserialize)]
1306pub struct MarketTradingDays {
1307 pub trading_days: Vec<Date>,
1309 pub half_trading_days: Vec<Date>,
1311}
1312
1313#[derive(Debug, Clone, Serialize, Deserialize)]
1315pub struct CapitalFlowLine {
1316 pub inflow: Decimal,
1318 pub timestamp: OffsetDateTime,
1320}
1321
1322impl TryFrom<quote::capital_flow_intraday_response::CapitalFlowLine> for CapitalFlowLine {
1323 type Error = Error;
1324
1325 fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result<Self> {
1326 Ok(Self {
1327 inflow: value.inflow.parse().unwrap_or_default(),
1328 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1329 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1330 })
1331 }
1332}
1333
1334#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1336pub struct CapitalDistribution {
1337 pub large: Decimal,
1339 pub medium: Decimal,
1341 pub small: Decimal,
1343}
1344
1345impl TryFrom<quote::capital_distribution_response::CapitalDistribution> for CapitalDistribution {
1346 type Error = Error;
1347
1348 fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result<Self> {
1349 Ok(Self {
1350 large: value.large.parse().unwrap_or_default(),
1351 medium: value.medium.parse().unwrap_or_default(),
1352 small: value.small.parse().unwrap_or_default(),
1353 })
1354 }
1355}
1356
1357#[derive(Debug, Clone, Serialize, Deserialize)]
1359pub struct CapitalDistributionResponse {
1360 pub timestamp: OffsetDateTime,
1362 pub capital_in: CapitalDistribution,
1364 pub capital_out: CapitalDistribution,
1366}
1367
1368impl TryFrom<quote::CapitalDistributionResponse> for CapitalDistributionResponse {
1369 type Error = Error;
1370
1371 fn try_from(value: quote::CapitalDistributionResponse) -> Result<Self> {
1372 Ok(Self {
1373 timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)
1374 .map_err(|err| Error::parse_field_error("timestamp", err))?,
1375 capital_in: value
1376 .capital_in
1377 .map(TryInto::try_into)
1378 .transpose()?
1379 .unwrap_or_default(),
1380 capital_out: value
1381 .capital_out
1382 .map(TryInto::try_into)
1383 .transpose()?
1384 .unwrap_or_default(),
1385 })
1386 }
1387}
1388
1389#[derive(Debug, Clone, Serialize, Deserialize)]
1391pub struct WatchlistSecurity {
1392 pub symbol: String,
1394 pub market: Market,
1396 pub name: String,
1398 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
1400 pub watched_price: Option<Decimal>,
1401 #[serde(
1403 serialize_with = "time::serde::rfc3339::serialize",
1404 deserialize_with = "serde_utils::timestamp::deserialize"
1405 )]
1406 pub watched_at: OffsetDateTime,
1407 #[serde(default)]
1409 pub is_pinned: bool,
1410}
1411
1412#[derive(Debug, Clone, Serialize, Deserialize)]
1414pub struct WatchlistGroup {
1415 #[serde(with = "serde_utils::int64_str")]
1417 pub id: i64,
1418 pub name: String,
1420 pub securities: Vec<WatchlistSecurity>,
1422}
1423
1424#[derive(Debug, Clone)]
1426pub struct RequestCreateWatchlistGroup {
1427 pub name: String,
1429 pub securities: Option<Vec<String>>,
1431}
1432
1433impl RequestCreateWatchlistGroup {
1434 pub fn new(name: impl Into<String>) -> Self {
1436 Self {
1437 name: name.into(),
1438 securities: None,
1439 }
1440 }
1441
1442 pub fn securities<I, T>(self, securities: I) -> Self
1444 where
1445 I: IntoIterator<Item = T>,
1446 T: Into<String>,
1447 {
1448 Self {
1449 securities: Some(securities.into_iter().map(Into::into).collect()),
1450 ..self
1451 }
1452 }
1453}
1454
1455#[derive(Debug, Copy, Clone, Default, Serialize)]
1457#[serde(rename_all = "lowercase")]
1458pub enum SecuritiesUpdateMode {
1459 Add,
1461 Remove,
1463 #[default]
1465 Replace,
1466}
1467
1468#[derive(Debug, Clone)]
1470pub struct RequestUpdateWatchlistGroup {
1471 pub id: i64,
1473 pub name: Option<String>,
1475 pub securities: Option<Vec<String>>,
1477 pub mode: SecuritiesUpdateMode,
1479}
1480
1481impl RequestUpdateWatchlistGroup {
1482 #[inline]
1484 pub fn new(id: i64) -> Self {
1485 Self {
1486 id,
1487 name: None,
1488 securities: None,
1489 mode: SecuritiesUpdateMode::default(),
1490 }
1491 }
1492
1493 pub fn name(self, name: impl Into<String>) -> Self {
1495 Self {
1496 name: Some(name.into()),
1497 ..self
1498 }
1499 }
1500
1501 pub fn securities<I, T>(self, securities: I) -> Self
1503 where
1504 I: IntoIterator<Item = T>,
1505 T: Into<String>,
1506 {
1507 Self {
1508 securities: Some(securities.into_iter().map(Into::into).collect()),
1509 ..self
1510 }
1511 }
1512
1513 pub fn mode(self, mode: SecuritiesUpdateMode) -> Self {
1515 Self { mode, ..self }
1516 }
1517}
1518
1519#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1521pub enum CalcIndex {
1522 LastDone,
1524 ChangeValue,
1526 ChangeRate,
1528 Volume,
1530 Turnover,
1532 YtdChangeRate,
1534 TurnoverRate,
1536 TotalMarketValue,
1538 CapitalFlow,
1540 Amplitude,
1542 VolumeRatio,
1544 PeTtmRatio,
1546 PbRatio,
1548 DividendRatioTtm,
1550 FiveDayChangeRate,
1552 TenDayChangeRate,
1554 HalfYearChangeRate,
1556 FiveMinutesChangeRate,
1558 ExpiryDate,
1560 StrikePrice,
1562 UpperStrikePrice,
1564 LowerStrikePrice,
1566 OutstandingQty,
1568 OutstandingRatio,
1570 Premium,
1572 ItmOtm,
1574 ImpliedVolatility,
1576 WarrantDelta,
1578 CallPrice,
1580 ToCallPrice,
1582 EffectiveLeverage,
1584 LeverageRatio,
1586 ConversionRatio,
1588 BalancePoint,
1590 OpenInterest,
1592 Delta,
1594 Gamma,
1596 Theta,
1598 Vega,
1600 Rho,
1602}
1603
1604impl From<CalcIndex> for longbridge_proto::quote::CalcIndex {
1605 fn from(value: CalcIndex) -> Self {
1606 use longbridge_proto::quote::CalcIndex::*;
1607
1608 match value {
1609 CalcIndex::LastDone => CalcindexLastDone,
1610 CalcIndex::ChangeValue => CalcindexChangeVal,
1611 CalcIndex::ChangeRate => CalcindexChangeRate,
1612 CalcIndex::Volume => CalcindexVolume,
1613 CalcIndex::Turnover => CalcindexTurnover,
1614 CalcIndex::YtdChangeRate => CalcindexYtdChangeRate,
1615 CalcIndex::TurnoverRate => CalcindexTurnoverRate,
1616 CalcIndex::TotalMarketValue => CalcindexTotalMarketValue,
1617 CalcIndex::CapitalFlow => CalcindexCapitalFlow,
1618 CalcIndex::Amplitude => CalcindexAmplitude,
1619 CalcIndex::VolumeRatio => CalcindexVolumeRatio,
1620 CalcIndex::PeTtmRatio => CalcindexPeTtmRatio,
1621 CalcIndex::PbRatio => CalcindexPbRatio,
1622 CalcIndex::DividendRatioTtm => CalcindexDividendRatioTtm,
1623 CalcIndex::FiveDayChangeRate => CalcindexFiveDayChangeRate,
1624 CalcIndex::TenDayChangeRate => CalcindexTenDayChangeRate,
1625 CalcIndex::HalfYearChangeRate => CalcindexHalfYearChangeRate,
1626 CalcIndex::FiveMinutesChangeRate => CalcindexFiveMinutesChangeRate,
1627 CalcIndex::ExpiryDate => CalcindexExpiryDate,
1628 CalcIndex::StrikePrice => CalcindexStrikePrice,
1629 CalcIndex::UpperStrikePrice => CalcindexUpperStrikePrice,
1630 CalcIndex::LowerStrikePrice => CalcindexLowerStrikePrice,
1631 CalcIndex::OutstandingQty => CalcindexOutstandingQty,
1632 CalcIndex::OutstandingRatio => CalcindexOutstandingRatio,
1633 CalcIndex::Premium => CalcindexPremium,
1634 CalcIndex::ItmOtm => CalcindexItmOtm,
1635 CalcIndex::ImpliedVolatility => CalcindexImpliedVolatility,
1636 CalcIndex::WarrantDelta => CalcindexWarrantDelta,
1637 CalcIndex::CallPrice => CalcindexCallPrice,
1638 CalcIndex::ToCallPrice => CalcindexToCallPrice,
1639 CalcIndex::EffectiveLeverage => CalcindexEffectiveLeverage,
1640 CalcIndex::LeverageRatio => CalcindexLeverageRatio,
1641 CalcIndex::ConversionRatio => CalcindexConversionRatio,
1642 CalcIndex::BalancePoint => CalcindexBalancePoint,
1643 CalcIndex::OpenInterest => CalcindexOpenInterest,
1644 CalcIndex::Delta => CalcindexDelta,
1645 CalcIndex::Gamma => CalcindexGamma,
1646 CalcIndex::Theta => CalcindexTheta,
1647 CalcIndex::Vega => CalcindexVega,
1648 CalcIndex::Rho => CalcindexRho,
1649 }
1650 }
1651}
1652
1653#[derive(Debug, Clone, Serialize, Deserialize)]
1655pub struct SecurityCalcIndex {
1656 pub symbol: String,
1658 pub last_done: Option<Decimal>,
1660 pub change_value: Option<Decimal>,
1662 pub change_rate: Option<Decimal>,
1664 pub volume: Option<i64>,
1666 pub turnover: Option<Decimal>,
1668 pub ytd_change_rate: Option<Decimal>,
1670 pub turnover_rate: Option<Decimal>,
1672 pub total_market_value: Option<Decimal>,
1674 pub capital_flow: Option<Decimal>,
1676 pub amplitude: Option<Decimal>,
1678 pub volume_ratio: Option<Decimal>,
1680 pub pe_ttm_ratio: Option<Decimal>,
1682 pub pb_ratio: Option<Decimal>,
1684 pub dividend_ratio_ttm: Option<Decimal>,
1686 pub five_day_change_rate: Option<Decimal>,
1688 pub ten_day_change_rate: Option<Decimal>,
1690 pub half_year_change_rate: Option<Decimal>,
1692 pub five_minutes_change_rate: Option<Decimal>,
1694 pub expiry_date: Option<Date>,
1696 pub strike_price: Option<Decimal>,
1698 pub upper_strike_price: Option<Decimal>,
1700 pub lower_strike_price: Option<Decimal>,
1702 pub outstanding_qty: Option<i64>,
1704 pub outstanding_ratio: Option<Decimal>,
1706 pub premium: Option<Decimal>,
1708 pub itm_otm: Option<Decimal>,
1710 pub implied_volatility: Option<Decimal>,
1712 pub warrant_delta: Option<Decimal>,
1714 pub call_price: Option<Decimal>,
1716 pub to_call_price: Option<Decimal>,
1718 pub effective_leverage: Option<Decimal>,
1720 pub leverage_ratio: Option<Decimal>,
1722 pub conversion_ratio: Option<Decimal>,
1724 pub balance_point: Option<Decimal>,
1726 pub open_interest: Option<i64>,
1728 pub delta: Option<Decimal>,
1730 pub gamma: Option<Decimal>,
1732 pub theta: Option<Decimal>,
1738 pub vega: Option<Decimal>,
1745 pub rho: Option<Decimal>,
1752}
1753
1754impl SecurityCalcIndex {
1755 pub(crate) fn from_proto(
1756 resp: longbridge_proto::quote::SecurityCalcIndex,
1757 indexes: &[CalcIndex],
1758 ) -> Self {
1759 let mut output = SecurityCalcIndex {
1760 symbol: resp.symbol,
1761 last_done: None,
1762 change_value: None,
1763 change_rate: None,
1764 volume: None,
1765 turnover: None,
1766 ytd_change_rate: None,
1767 turnover_rate: None,
1768 total_market_value: None,
1769 capital_flow: None,
1770 amplitude: None,
1771 volume_ratio: None,
1772 pe_ttm_ratio: None,
1773 pb_ratio: None,
1774 dividend_ratio_ttm: None,
1775 five_day_change_rate: None,
1776 ten_day_change_rate: None,
1777 half_year_change_rate: None,
1778 five_minutes_change_rate: None,
1779 expiry_date: None,
1780 strike_price: None,
1781 upper_strike_price: None,
1782 lower_strike_price: None,
1783 outstanding_qty: None,
1784 outstanding_ratio: None,
1785 premium: None,
1786 itm_otm: None,
1787 implied_volatility: None,
1788 warrant_delta: None,
1789 call_price: None,
1790 to_call_price: None,
1791 effective_leverage: None,
1792 leverage_ratio: None,
1793 conversion_ratio: None,
1794 balance_point: None,
1795 open_interest: None,
1796 delta: None,
1797 gamma: None,
1798 theta: None,
1799 vega: None,
1800 rho: None,
1801 };
1802
1803 for index in indexes {
1804 match index {
1805 CalcIndex::LastDone => output.last_done = resp.last_done.parse().ok(),
1806 CalcIndex::ChangeValue => output.change_value = resp.change_val.parse().ok(),
1807 CalcIndex::ChangeRate => output.change_rate = resp.change_rate.parse().ok(),
1808 CalcIndex::Volume => output.volume = Some(resp.volume),
1809 CalcIndex::Turnover => output.turnover = resp.turnover.parse().ok(),
1810 CalcIndex::YtdChangeRate => {
1811 output.ytd_change_rate = resp.ytd_change_rate.parse().ok()
1812 }
1813 CalcIndex::TurnoverRate => output.turnover_rate = resp.turnover_rate.parse().ok(),
1814 CalcIndex::TotalMarketValue => {
1815 output.total_market_value = resp.total_market_value.parse().ok()
1816 }
1817 CalcIndex::CapitalFlow => output.capital_flow = resp.capital_flow.parse().ok(),
1818 CalcIndex::Amplitude => output.amplitude = resp.amplitude.parse().ok(),
1819 CalcIndex::VolumeRatio => output.volume_ratio = resp.volume_ratio.parse().ok(),
1820 CalcIndex::PeTtmRatio => output.pe_ttm_ratio = resp.pe_ttm_ratio.parse().ok(),
1821 CalcIndex::PbRatio => output.pb_ratio = resp.pb_ratio.parse().ok(),
1822 CalcIndex::DividendRatioTtm => {
1823 output.dividend_ratio_ttm = resp.dividend_ratio_ttm.parse().ok()
1824 }
1825 CalcIndex::FiveDayChangeRate => {
1826 output.five_day_change_rate = resp.five_day_change_rate.parse().ok()
1827 }
1828 CalcIndex::TenDayChangeRate => {
1829 output.ten_day_change_rate = resp.ten_day_change_rate.parse().ok()
1830 }
1831 CalcIndex::HalfYearChangeRate => {
1832 output.half_year_change_rate = resp.half_year_change_rate.parse().ok()
1833 }
1834 CalcIndex::FiveMinutesChangeRate => {
1835 output.five_minutes_change_rate = resp.five_minutes_change_rate.parse().ok()
1836 }
1837 CalcIndex::ExpiryDate => output.expiry_date = parse_date(&resp.expiry_date).ok(),
1838 CalcIndex::StrikePrice => output.strike_price = resp.strike_price.parse().ok(),
1839 CalcIndex::UpperStrikePrice => {
1840 output.upper_strike_price = resp.upper_strike_price.parse().ok()
1841 }
1842 CalcIndex::LowerStrikePrice => {
1843 output.lower_strike_price = resp.lower_strike_price.parse().ok()
1844 }
1845 CalcIndex::OutstandingQty => output.outstanding_qty = Some(resp.outstanding_qty),
1846 CalcIndex::OutstandingRatio => {
1847 output.outstanding_ratio = resp.outstanding_ratio.parse().ok()
1848 }
1849 CalcIndex::Premium => output.premium = resp.premium.parse().ok(),
1850 CalcIndex::ItmOtm => output.itm_otm = resp.itm_otm.parse().ok(),
1851 CalcIndex::ImpliedVolatility => {
1852 output.implied_volatility = resp.implied_volatility.parse().ok()
1853 }
1854 CalcIndex::WarrantDelta => output.warrant_delta = resp.warrant_delta.parse().ok(),
1855 CalcIndex::CallPrice => output.call_price = resp.call_price.parse().ok(),
1856 CalcIndex::ToCallPrice => output.to_call_price = resp.to_call_price.parse().ok(),
1857 CalcIndex::EffectiveLeverage => {
1858 output.effective_leverage = resp.effective_leverage.parse().ok()
1859 }
1860 CalcIndex::LeverageRatio => {
1861 output.leverage_ratio = resp.leverage_ratio.parse().ok()
1862 }
1863 CalcIndex::ConversionRatio => {
1864 output.conversion_ratio = resp.conversion_ratio.parse().ok()
1865 }
1866 CalcIndex::BalancePoint => output.balance_point = resp.balance_point.parse().ok(),
1867 CalcIndex::OpenInterest => output.open_interest = Some(resp.open_interest),
1868 CalcIndex::Delta => output.delta = resp.delta.parse().ok(),
1869 CalcIndex::Gamma => output.gamma = resp.gamma.parse().ok(),
1870 CalcIndex::Theta => output.theta = resp.theta.parse().ok(),
1871 CalcIndex::Vega => output.vega = resp.vega.parse().ok(),
1872 CalcIndex::Rho => output.rho = resp.rho.parse().ok(),
1873 }
1874 }
1875
1876 output
1877 }
1878}
1879
1880#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1882pub enum SecurityListCategory {
1883 Overnight,
1885}
1886
1887impl_serialize_for_enum_string!(SecurityListCategory);
1888
1889#[derive(Debug, Serialize, Deserialize)]
1891pub struct Security {
1892 pub symbol: String,
1894 pub name_cn: String,
1896 pub name_en: String,
1898 pub name_hk: String,
1900}
1901
1902#[derive(Debug, Clone)]
1904pub struct QuotePackageDetail {
1905 pub key: String,
1907 pub name: String,
1909 pub description: String,
1911 pub start_at: OffsetDateTime,
1913 pub end_at: OffsetDateTime,
1915}
1916
1917impl TryFrom<quote::user_quote_level_detail::PackageDetail> for QuotePackageDetail {
1918 type Error = Error;
1919
1920 fn try_from(quote: quote::user_quote_level_detail::PackageDetail) -> Result<Self> {
1921 Ok(Self {
1922 key: quote.key,
1923 name: quote.name,
1924 description: quote.description,
1925 start_at: OffsetDateTime::from_unix_timestamp(quote.start)
1926 .map_err(|err| Error::parse_field_error("start_at", err))?,
1927 end_at: OffsetDateTime::from_unix_timestamp(quote.end)
1928 .map_err(|err| Error::parse_field_error("end_at", err))?,
1929 })
1930 }
1931}
1932
1933#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1935#[repr(i32)]
1936pub enum TradeSessions {
1937 Intraday = 0,
1939 All = 100,
1941}
1942
1943impl TradeSessions {
1944 #[inline]
1945 pub(crate) fn contains(&self, session: TradeSession) -> bool {
1946 match self {
1947 TradeSessions::Intraday => session == TradeSession::Intraday,
1948 TradeSessions::All => true,
1949 }
1950 }
1951}
1952
1953#[derive(Debug, Clone, Serialize, Deserialize)]
1955pub struct MarketTemperature {
1956 pub temperature: i32,
1958 #[serde(default)]
1960 pub description: String,
1961 pub valuation: i32,
1963 pub sentiment: i32,
1965 #[serde(
1967 serialize_with = "time::serde::rfc3339::serialize",
1968 deserialize_with = "serde_utils::timestamp::deserialize",
1969 alias = "updated_at"
1970 )]
1971 pub timestamp: OffsetDateTime,
1972}
1973
1974#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
1976pub enum Granularity {
1977 Unknown,
1979 #[strum(serialize = "daily")]
1981 Daily,
1982 #[strum(serialize = "weekly")]
1984 Weekly,
1985 #[strum(serialize = "monthly")]
1987 Monthly,
1988}
1989
1990#[derive(Debug, Clone, Serialize, Deserialize)]
1992pub struct HistoryMarketTemperatureResponse {
1993 #[serde(rename = "type")]
1995 pub granularity: Granularity,
1996 #[serde(rename = "list")]
1998 pub records: Vec<MarketTemperature>,
1999}
2000
2001#[derive(Debug, Clone, Serialize, Deserialize)]
2003pub struct FilingItem {
2004 pub id: String,
2006 pub title: String,
2008 #[serde(default)]
2010 pub description: String,
2011 pub file_name: String,
2013 pub file_urls: Vec<String>,
2015 #[serde(
2017 rename = "publish_at",
2018 serialize_with = "time::serde::rfc3339::serialize",
2019 deserialize_with = "crate::serde_utils::timestamp::deserialize"
2020 )]
2021 pub published_at: OffsetDateTime,
2022}
2023
2024impl_serde_for_enum_string!(Granularity);
2025impl_default_for_enum_string!(
2026 OptionType,
2027 OptionDirection,
2028 WarrantType,
2029 SecurityBoard,
2030 Granularity
2031);
2032
2033#[derive(Debug, Clone, Serialize, Deserialize)]
2037pub struct ShortPositionsItem {
2038 pub timestamp: String,
2040 pub rate: String,
2042 pub close: String,
2044 #[serde(default)]
2046 pub current_shares_short: String,
2047 #[serde(default)]
2049 pub avg_daily_share_volume: String,
2050 #[serde(default)]
2052 pub days_to_cover: String,
2053 #[serde(default)]
2055 pub amount: String,
2056 #[serde(default)]
2058 pub balance: String,
2059 #[serde(default)]
2061 pub cost: String,
2062}
2063
2064#[derive(Debug, Clone, Serialize, Deserialize)]
2066pub struct ShortPositionsResponse {
2067 pub data: Vec<ShortPositionsItem>,
2069}
2070
2071#[derive(Debug, Clone, Serialize, Deserialize)]
2075pub struct OptionVolumeStats {
2076 pub c: String,
2078 pub p: String,
2080}
2081
2082#[derive(Debug, Clone, Serialize, Deserialize)]
2086pub struct OptionVolumeDaily {
2087 pub stats: Vec<OptionVolumeDailyStat>,
2089}
2090
2091#[derive(Debug, Clone, Serialize, Deserialize)]
2093pub struct OptionVolumeDailyStat {
2094 #[serde(
2096 rename = "underlying_counter_id",
2097 deserialize_with = "crate::utils::counter::deserialize_counter_id_as_symbol"
2098 )]
2099 pub symbol: String,
2100 pub timestamp: String,
2102 pub total_volume: String,
2104 pub total_put_volume: String,
2106 pub total_call_volume: String,
2108 pub put_call_volume_ratio: String,
2110 pub total_open_interest: String,
2112 pub total_put_open_interest: String,
2114 pub total_call_open_interest: String,
2116 pub put_call_open_interest_ratio: String,
2118}
2119
2120#[derive(Debug, Clone, Serialize, Deserialize)]
2124pub struct ShortTradesItem {
2125 pub timestamp: String,
2127 pub rate: String,
2129 pub close: String,
2131 #[serde(default)]
2133 pub nus_amount: String,
2134 #[serde(default)]
2136 pub ny_amount: String,
2137 #[serde(default)]
2139 pub total_amount: String,
2140 #[serde(default)]
2142 pub amount: String,
2143 #[serde(default)]
2145 pub balance: String,
2146}
2147
2148#[derive(Debug, Clone, Serialize, Deserialize)]
2150pub struct ShortTradesResponse {
2151 pub data: Vec<ShortTradesItem>,
2153}
2154
2155#[derive(Debug, Clone, Serialize, Deserialize)]
2159#[serde(rename_all = "lowercase")]
2160pub enum PinnedMode {
2161 Add,
2163 Remove,
2165}
2166
2167#[cfg(test)]
2168mod tests {
2169 use serde::Deserialize;
2170
2171 use crate::{Market, quote::WatchlistGroup};
2172
2173 #[test]
2174 fn watch_list() {
2175 #[derive(Debug, Deserialize)]
2176 struct Response {
2177 groups: Vec<WatchlistGroup>,
2178 }
2179
2180 let json = r#"
2181 {
2182 "groups": [
2183 {
2184 "id": "1",
2185 "name": "Test",
2186 "securities": [
2187 {
2188 "symbol": "AAPL",
2189 "market": "US",
2190 "name": "Apple Inc.",
2191 "watched_price": "150.0",
2192 "watched_at": "1633036800"
2193 }
2194 ]
2195 }
2196 ]
2197 }
2198 "#;
2199
2200 let response: Response = serde_json::from_str(json).unwrap();
2201 assert_eq!(response.groups.len(), 1);
2202 assert_eq!(response.groups[0].id, 1);
2203 assert_eq!(response.groups[0].name, "Test");
2204 assert_eq!(response.groups[0].securities.len(), 1);
2205 assert_eq!(response.groups[0].securities[0].symbol, "AAPL");
2206 assert_eq!(response.groups[0].securities[0].market, Market::US);
2207 assert_eq!(response.groups[0].securities[0].name, "Apple Inc.");
2208 assert_eq!(
2209 response.groups[0].securities[0].watched_price,
2210 Some(decimal!(150.0))
2211 );
2212 assert_eq!(
2213 response.groups[0].securities[0].watched_at.unix_timestamp(),
2214 1633036800
2215 );
2216 }
2217}