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
103impl longport_candlesticks::TradeType for PushQuote {
104    type PriceType = Decimal;
105    type VolumeType = i64;
106    type TurnoverType = Decimal;
107    type TradeSessionType = TradeSession;
108
109    #[inline]
110    fn time(&self) -> OffsetDateTime {
111        self.timestamp
112    }
113
114    #[inline]
115    fn price(&self) -> Self::PriceType {
116        self.last_done
117    }
118
119    #[inline]
120    #[allow(clippy::misnamed_getters)]
121    fn volume(&self) -> Self::VolumeType {
122        self.current_volume
123    }
124
125    #[inline]
126    #[allow(clippy::misnamed_getters)]
127    fn turnover(&self, _lot_size: i32) -> Self::TurnoverType {
128        self.current_turnover
129    }
130
131    #[inline]
132    fn trade_session(&self) -> Self::TradeSessionType {
133        self.trade_session
134    }
135}
136
137/// Depth message
138#[derive(Debug)]
139pub struct PushDepth {
140    /// Ask depth
141    pub asks: Vec<Depth>,
142    /// Bid depth
143    pub bids: Vec<Depth>,
144}
145
146/// Brokers message
147#[derive(Debug)]
148pub struct PushBrokers {
149    /// Ask brokers
150    pub ask_brokers: Vec<Brokers>,
151    /// Bid brokers
152    pub bid_brokers: Vec<Brokers>,
153}
154
155/// Trades message
156#[derive(Debug)]
157pub struct PushTrades {
158    /// Trades data
159    pub trades: Vec<Trade>,
160}
161
162/// Candlestick updated message
163#[derive(Debug, Copy, Clone)]
164pub struct PushCandlestick {
165    /// Period type
166    pub period: Period,
167    /// Candlestick
168    pub candlestick: Candlestick,
169    /// Is confirmed
170    pub is_confirmed: bool,
171}
172
173/// Push event detail
174#[derive(Debug)]
175pub enum PushEventDetail {
176    /// Quote
177    Quote(PushQuote),
178    /// Depth
179    Depth(PushDepth),
180    /// Brokers
181    Brokers(PushBrokers),
182    /// Trade
183    Trade(PushTrades),
184    /// Candlestick
185    Candlestick(PushCandlestick),
186}
187
188/// Push event
189#[derive(Debug)]
190pub struct PushEvent {
191    #[allow(dead_code)]
192    pub(crate) sequence: i64,
193    /// Security code
194    pub symbol: String,
195    /// Event detail
196    pub detail: PushEventDetail,
197}
198
199impl PushEvent {
200    pub(crate) fn parse(
201        command_code: u8,
202        data: &[u8],
203    ) -> Result<(PushEvent, Option<PushQuoteTag>)> {
204        match command_code {
205            cmd_code::PUSH_REALTIME_QUOTE => {
206                parse_push_quote(data).map(|(event, tag)| (event, Some(tag)))
207            }
208            cmd_code::PUSH_REALTIME_DEPTH => parse_push_depth(data).map(|event| (event, None)),
209            cmd_code::PUSH_REALTIME_BROKERS => parse_push_brokers(data).map(|event| (event, None)),
210            cmd_code::PUSH_REALTIME_TRADES => parse_push_trade(data).map(|event| (event, None)),
211            _ => Err(Error::UnknownCommand(command_code)),
212        }
213    }
214}
215
216fn parse_push_quote(data: &[u8]) -> Result<(PushEvent, PushQuoteTag)> {
217    let push_quote = quote::PushQuote::decode(data)?;
218    Ok((
219        PushEvent {
220            symbol: push_quote.symbol,
221            sequence: push_quote.sequence,
222            detail: PushEventDetail::Quote(PushQuote {
223                last_done: push_quote.last_done.parse().unwrap_or_default(),
224                open: push_quote.open.parse().unwrap_or_default(),
225                high: push_quote.high.parse().unwrap_or_default(),
226                low: push_quote.low.parse().unwrap_or_default(),
227                timestamp: OffsetDateTime::from_unix_timestamp(push_quote.timestamp)
228                    .map_err(|err| Error::parse_field_error("timestamp", err))?,
229                volume: push_quote.volume,
230                turnover: push_quote.turnover.parse().unwrap_or_default(),
231                trade_status: TradeStatus::try_from(push_quote.trade_status).unwrap_or_default(),
232                trade_session: longport_proto::quote::TradeSession::try_from(
233                    push_quote.trade_session,
234                )
235                .unwrap_or_default()
236                .into(),
237                current_volume: push_quote.current_volume,
238                current_turnover: push_quote.current_turnover.parse().unwrap_or_default(),
239            }),
240        },
241        PushQuoteTag::try_from(push_quote.tag).unwrap_or_default(),
242    ))
243}
244
245fn parse_push_depth(data: &[u8]) -> Result<PushEvent> {
246    let push_depth = quote::PushDepth::decode(data)?;
247    Ok(PushEvent {
248        symbol: push_depth.symbol,
249        sequence: push_depth.sequence,
250        detail: PushEventDetail::Depth(PushDepth {
251            asks: push_depth
252                .ask
253                .into_iter()
254                .map(TryInto::try_into)
255                .collect::<Result<Vec<_>>>()?,
256            bids: push_depth
257                .bid
258                .into_iter()
259                .map(TryInto::try_into)
260                .collect::<Result<Vec<_>>>()?,
261        }),
262    })
263}
264
265fn parse_push_brokers(data: &[u8]) -> Result<PushEvent> {
266    let push_brokers = quote::PushBrokers::decode(data)?;
267
268    Ok(PushEvent {
269        symbol: push_brokers.symbol,
270        sequence: push_brokers.sequence,
271        detail: PushEventDetail::Brokers(PushBrokers {
272            ask_brokers: push_brokers
273                .ask_brokers
274                .into_iter()
275                .map(Into::into)
276                .collect(),
277            bid_brokers: push_brokers
278                .bid_brokers
279                .into_iter()
280                .map(Into::into)
281                .collect(),
282        }),
283    })
284}
285
286fn parse_push_trade(data: &[u8]) -> Result<PushEvent> {
287    let push_trades = quote::PushTrade::decode(data)?;
288    Ok(PushEvent {
289        symbol: push_trades.symbol,
290        sequence: push_trades.sequence,
291        detail: PushEventDetail::Trade(PushTrades {
292            trades: push_trades
293                .trade
294                .into_iter()
295                .map(TryInto::try_into)
296                .collect::<Result<Vec<_>>>()?,
297        }),
298    })
299}