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
103#[derive(Debug)]
105pub struct PushDepth {
106 pub asks: Vec<Depth>,
108 pub bids: Vec<Depth>,
110}
111
112#[derive(Debug)]
114pub struct PushBrokers {
115 pub ask_brokers: Vec<Brokers>,
117 pub bid_brokers: Vec<Brokers>,
119}
120
121#[derive(Debug)]
123pub struct PushTrades {
124 pub trades: Vec<Trade>,
126}
127
128#[derive(Debug, Copy, Clone)]
130pub struct PushCandlestick {
131 pub period: Period,
133 pub candlestick: Candlestick,
135 pub is_confirmed: bool,
137}
138
139#[derive(Debug)]
141pub enum PushEventDetail {
142 Quote(PushQuote),
144 Depth(PushDepth),
146 Brokers(PushBrokers),
148 Trade(PushTrades),
150 Candlestick(PushCandlestick),
152}
153
154#[derive(Debug)]
156pub struct PushEvent {
157 #[allow(dead_code)]
158 pub(crate) sequence: i64,
159 pub symbol: String,
161 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}