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
56#[derive(Debug)]
58pub struct PushDepth {
59 pub asks: Vec<Depth>,
61 pub bids: Vec<Depth>,
63}
64
65#[derive(Debug)]
67pub struct PushBrokers {
68 pub ask_brokers: Vec<Brokers>,
70 pub bid_brokers: Vec<Brokers>,
72}
73
74#[derive(Debug)]
76pub struct PushTrades {
77 pub trades: Vec<Trade>,
79}
80
81#[derive(Debug, Copy, Clone)]
83pub struct PushCandlestick {
84 pub period: Period,
86 pub candlestick: Candlestick,
88 pub is_confirmed: bool,
90}
91
92#[derive(Debug)]
94pub enum PushEventDetail {
95 Quote(PushQuote),
97 Depth(PushDepth),
99 Brokers(PushBrokers),
101 Trade(PushTrades),
103 Candlestick(PushCandlestick),
105}
106
107#[derive(Debug)]
109pub struct PushEvent {
110 #[allow(dead_code)]
111 pub(crate) sequence: i64,
112 pub symbol: String,
114 pub detail: PushEventDetail,
116}
117
118impl PushEvent {
119 pub(crate) fn parse(
120 command_code: u8,
121 data: &[u8],
122 ) -> Result<(PushEvent, Option<PushQuoteTag>)> {
123 match command_code {
124 cmd_code::PUSH_REALTIME_QUOTE => {
125 parse_push_quote(data).map(|(event, tag)| (event, Some(tag)))
126 }
127 cmd_code::PUSH_REALTIME_DEPTH => parse_push_depth(data).map(|event| (event, None)),
128 cmd_code::PUSH_REALTIME_BROKERS => parse_push_brokers(data).map(|event| (event, None)),
129 cmd_code::PUSH_REALTIME_TRADES => parse_push_trade(data).map(|event| (event, None)),
130 _ => Err(Error::UnknownCommand(command_code)),
131 }
132 }
133}
134
135fn parse_push_quote(data: &[u8]) -> Result<(PushEvent, PushQuoteTag)> {
136 let push_quote = quote::PushQuote::decode(data)?;
137 Ok((
138 PushEvent {
139 symbol: push_quote.symbol,
140 sequence: push_quote.sequence,
141 detail: PushEventDetail::Quote(PushQuote {
142 last_done: push_quote.last_done.parse().unwrap_or_default(),
143 open: push_quote.open.parse().unwrap_or_default(),
144 high: push_quote.high.parse().unwrap_or_default(),
145 low: push_quote.low.parse().unwrap_or_default(),
146 timestamp: OffsetDateTime::from_unix_timestamp(push_quote.timestamp)
147 .map_err(|err| Error::parse_field_error("timestamp", err))?,
148 volume: push_quote.volume,
149 turnover: push_quote.turnover.parse().unwrap_or_default(),
150 trade_status: TradeStatus::try_from(push_quote.trade_status).unwrap_or_default(),
151 trade_session: longport_proto::quote::TradeSession::try_from(
152 push_quote.trade_session,
153 )
154 .unwrap_or_default()
155 .into(),
156 current_volume: push_quote.current_volume,
157 current_turnover: push_quote.current_turnover.parse().unwrap_or_default(),
158 }),
159 },
160 PushQuoteTag::try_from(push_quote.tag).unwrap_or_default(),
161 ))
162}
163
164fn parse_push_depth(data: &[u8]) -> Result<PushEvent> {
165 let push_depth = quote::PushDepth::decode(data)?;
166 Ok(PushEvent {
167 symbol: push_depth.symbol,
168 sequence: push_depth.sequence,
169 detail: PushEventDetail::Depth(PushDepth {
170 asks: push_depth
171 .ask
172 .into_iter()
173 .map(TryInto::try_into)
174 .collect::<Result<Vec<_>>>()?,
175 bids: push_depth
176 .bid
177 .into_iter()
178 .map(TryInto::try_into)
179 .collect::<Result<Vec<_>>>()?,
180 }),
181 })
182}
183
184fn parse_push_brokers(data: &[u8]) -> Result<PushEvent> {
185 let push_brokers = quote::PushBrokers::decode(data)?;
186
187 Ok(PushEvent {
188 symbol: push_brokers.symbol,
189 sequence: push_brokers.sequence,
190 detail: PushEventDetail::Brokers(PushBrokers {
191 ask_brokers: push_brokers
192 .ask_brokers
193 .into_iter()
194 .map(Into::into)
195 .collect(),
196 bid_brokers: push_brokers
197 .bid_brokers
198 .into_iter()
199 .map(Into::into)
200 .collect(),
201 }),
202 })
203}
204
205fn parse_push_trade(data: &[u8]) -> Result<PushEvent> {
206 let push_trades = quote::PushTrade::decode(data)?;
207 Ok(PushEvent {
208 symbol: push_trades.symbol,
209 sequence: push_trades.sequence,
210 detail: PushEventDetail::Trade(PushTrades {
211 trades: push_trades
212 .trade
213 .into_iter()
214 .map(TryInto::try_into)
215 .collect::<Result<Vec<_>>>()?,
216 }),
217 })
218}