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#[derive(Debug, Clone)]
13pub struct PushQuote {
14 pub last_done: Decimal,
16 pub open: Decimal,
18 pub high: Decimal,
20 pub low: Decimal,
22 pub timestamp: OffsetDateTime,
24 pub volume: i64,
26 pub turnover: Decimal,
28 pub trade_status: TradeStatus,
30 pub trade_session: TradeSession,
32 pub current_volume: i64,
34 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#[derive(Debug)]
139pub struct PushDepth {
140 pub asks: Vec<Depth>,
142 pub bids: Vec<Depth>,
144}
145
146#[derive(Debug)]
148pub struct PushBrokers {
149 pub ask_brokers: Vec<Brokers>,
151 pub bid_brokers: Vec<Brokers>,
153}
154
155#[derive(Debug)]
157pub struct PushTrades {
158 pub trades: Vec<Trade>,
160}
161
162#[derive(Debug, Copy, Clone)]
164pub struct PushCandlestick {
165 pub period: Period,
167 pub candlestick: Candlestick,
169 pub is_confirmed: bool,
171}
172
173#[derive(Debug)]
175pub enum PushEventDetail {
176 Quote(PushQuote),
178 Depth(PushDepth),
180 Brokers(PushBrokers),
182 Trade(PushTrades),
184 Candlestick(PushCandlestick),
186}
187
188#[derive(Debug)]
190pub struct PushEvent {
191 #[allow(dead_code)]
192 pub(crate) sequence: i64,
193 pub symbol: String,
195 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}