longport/quote/
push_types.rs

1use longport_proto::quote::{self, Period, PushQuoteTag, TradeStatus};
2use prost::Message;
3use rust_decimal::Decimal;
4use time::OffsetDateTime;
5
6use crate::{
7    Error, Result,
8    quote::{Brokers, Candlestick, Depth, Trade, TradeSession, cmd_code},
9};
10
11/// Quote message
12#[derive(Debug, Clone)]
13pub struct PushQuote {
14    /// Latest price
15    pub last_done: Decimal,
16    /// Open
17    pub open: Decimal,
18    /// High
19    pub high: Decimal,
20    /// Low
21    pub low: Decimal,
22    /// Time of latest price
23    pub timestamp: OffsetDateTime,
24    /// Volume
25    pub volume: i64,
26    /// Turnover
27    pub turnover: Decimal,
28    /// Security trading status
29    pub trade_status: TradeStatus,
30    /// Trade session
31    pub trade_session: TradeSession,
32    /// Increase volume between pushes
33    pub current_volume: i64,
34    /// Increase turnover between pushes
35    pub current_turnover: Decimal,
36}
37
38impl Default for PushQuote {
39    fn default() -> Self {
40        Self {
41            last_done: Default::default(),
42            open: Default::default(),
43            high: Default::default(),
44            low: Default::default(),
45            timestamp: OffsetDateTime::from_unix_timestamp(0).unwrap(),
46            volume: Default::default(),
47            turnover: Default::default(),
48            trade_status: Default::default(),
49            trade_session: Default::default(),
50            current_volume: Default::default(),
51            current_turnover: Default::default(),
52        }
53    }
54}
55
56impl longport_candlesticks::QuoteType for PushQuote {
57    type PriceType = Decimal;
58    type VolumeType = i64;
59    type TurnoverType = Decimal;
60    type TradeSessionType = TradeSession;
61
62    #[inline]
63    fn time(&self) -> OffsetDateTime {
64        self.timestamp
65    }
66
67    #[inline]
68    fn open(&self) -> Self::PriceType {
69        self.open
70    }
71
72    #[inline]
73    fn high(&self) -> Self::PriceType {
74        self.high
75    }
76
77    #[inline]
78    fn low(&self) -> Self::PriceType {
79        self.low
80    }
81
82    #[inline]
83    fn last_done(&self) -> Self::PriceType {
84        self.last_done
85    }
86
87    #[inline]
88    fn volume(&self) -> Self::VolumeType {
89        self.volume
90    }
91
92    #[inline]
93    fn turnover(&self) -> Self::TurnoverType {
94        self.turnover
95    }
96
97    #[inline]
98    fn trade_session(&self) -> Self::TradeSessionType {
99        self.trade_session
100    }
101}
102
103/// Depth message
104#[derive(Debug)]
105pub struct PushDepth {
106    /// Ask depth
107    pub asks: Vec<Depth>,
108    /// Bid depth
109    pub bids: Vec<Depth>,
110}
111
112/// Brokers message
113#[derive(Debug)]
114pub struct PushBrokers {
115    /// Ask brokers
116    pub ask_brokers: Vec<Brokers>,
117    /// Bid brokers
118    pub bid_brokers: Vec<Brokers>,
119}
120
121/// Trades message
122#[derive(Debug)]
123pub struct PushTrades {
124    /// Trades data
125    pub trades: Vec<Trade>,
126}
127
128/// Candlestick updated message
129#[derive(Debug, Copy, Clone)]
130pub struct PushCandlestick {
131    /// Period type
132    pub period: Period,
133    /// Candlestick
134    pub candlestick: Candlestick,
135    /// Is confirmed
136    pub is_confirmed: bool,
137}
138
139/// Push event detail
140#[derive(Debug)]
141pub enum PushEventDetail {
142    /// Quote
143    Quote(PushQuote),
144    /// Depth
145    Depth(PushDepth),
146    /// Brokers
147    Brokers(PushBrokers),
148    /// Trade
149    Trade(PushTrades),
150    /// Candlestick
151    Candlestick(PushCandlestick),
152}
153
154/// Push event
155#[derive(Debug)]
156pub struct PushEvent {
157    #[allow(dead_code)]
158    pub(crate) sequence: i64,
159    /// Security code
160    pub symbol: String,
161    /// Event detail
162    pub detail: PushEventDetail,
163}
164
165impl PushEvent {
166    pub(crate) fn parse(
167        command_code: u8,
168        data: &[u8],
169    ) -> Result<(PushEvent, Option<PushQuoteTag>)> {
170        match command_code {
171            cmd_code::PUSH_REALTIME_QUOTE => {
172                parse_push_quote(data).map(|(event, tag)| (event, Some(tag)))
173            }
174            cmd_code::PUSH_REALTIME_DEPTH => parse_push_depth(data).map(|event| (event, None)),
175            cmd_code::PUSH_REALTIME_BROKERS => parse_push_brokers(data).map(|event| (event, None)),
176            cmd_code::PUSH_REALTIME_TRADES => parse_push_trade(data).map(|event| (event, None)),
177            _ => Err(Error::UnknownCommand(command_code)),
178        }
179    }
180}
181
182fn parse_push_quote(data: &[u8]) -> Result<(PushEvent, PushQuoteTag)> {
183    let push_quote = quote::PushQuote::decode(data)?;
184    Ok((
185        PushEvent {
186            symbol: push_quote.symbol,
187            sequence: push_quote.sequence,
188            detail: PushEventDetail::Quote(PushQuote {
189                last_done: push_quote.last_done.parse().unwrap_or_default(),
190                open: push_quote.open.parse().unwrap_or_default(),
191                high: push_quote.high.parse().unwrap_or_default(),
192                low: push_quote.low.parse().unwrap_or_default(),
193                timestamp: OffsetDateTime::from_unix_timestamp(push_quote.timestamp)
194                    .map_err(|err| Error::parse_field_error("timestamp", err))?,
195                volume: push_quote.volume,
196                turnover: push_quote.turnover.parse().unwrap_or_default(),
197                trade_status: TradeStatus::try_from(push_quote.trade_status).unwrap_or_default(),
198                trade_session: longport_proto::quote::TradeSession::try_from(
199                    push_quote.trade_session,
200                )
201                .unwrap_or_default()
202                .into(),
203                current_volume: push_quote.current_volume,
204                current_turnover: push_quote.current_turnover.parse().unwrap_or_default(),
205            }),
206        },
207        PushQuoteTag::try_from(push_quote.tag).unwrap_or_default(),
208    ))
209}
210
211fn parse_push_depth(data: &[u8]) -> Result<PushEvent> {
212    let push_depth = quote::PushDepth::decode(data)?;
213    Ok(PushEvent {
214        symbol: push_depth.symbol,
215        sequence: push_depth.sequence,
216        detail: PushEventDetail::Depth(PushDepth {
217            asks: push_depth
218                .ask
219                .into_iter()
220                .map(TryInto::try_into)
221                .collect::<Result<Vec<_>>>()?,
222            bids: push_depth
223                .bid
224                .into_iter()
225                .map(TryInto::try_into)
226                .collect::<Result<Vec<_>>>()?,
227        }),
228    })
229}
230
231fn parse_push_brokers(data: &[u8]) -> Result<PushEvent> {
232    let push_brokers = quote::PushBrokers::decode(data)?;
233
234    Ok(PushEvent {
235        symbol: push_brokers.symbol,
236        sequence: push_brokers.sequence,
237        detail: PushEventDetail::Brokers(PushBrokers {
238            ask_brokers: push_brokers
239                .ask_brokers
240                .into_iter()
241                .map(Into::into)
242                .collect(),
243            bid_brokers: push_brokers
244                .bid_brokers
245                .into_iter()
246                .map(Into::into)
247                .collect(),
248        }),
249    })
250}
251
252fn parse_push_trade(data: &[u8]) -> Result<PushEvent> {
253    let push_trades = quote::PushTrade::decode(data)?;
254    Ok(PushEvent {
255        symbol: push_trades.symbol,
256        sequence: push_trades.sequence,
257        detail: PushEventDetail::Trade(PushTrades {
258            trades: push_trades
259                .trade
260                .into_iter()
261                .map(TryInto::try_into)
262                .collect::<Result<Vec<_>>>()?,
263        }),
264    })
265}