longport/trade/
push_types.rs

1use std::str::FromStr;
2
3use longport_proto::trade::Notification;
4use prost::Message;
5use rust_decimal::Decimal;
6use serde::Deserialize;
7use strum_macros::{Display, EnumString};
8use time::OffsetDateTime;
9
10use crate::{
11    Error, Result, serde_utils,
12    trade::{OrderSide, OrderStatus, OrderTag, OrderType, TriggerStatus, cmd_code},
13};
14
15/// Topic type
16#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
17pub enum TopicType {
18    /// Private notification for trade
19    #[strum(serialize = "private")]
20    Private,
21}
22
23/// Order changed message
24#[derive(Debug, Deserialize)]
25pub struct PushOrderChanged {
26    /// Order side
27    pub side: OrderSide,
28    /// Stock name
29    pub stock_name: String,
30    /// Submitted quantity
31    pub submitted_quantity: Decimal,
32    /// Order symbol
33    pub symbol: String,
34    /// Order type
35    pub order_type: OrderType,
36    /// Submitted price
37    pub submitted_price: Decimal,
38    /// Executed quantity
39    pub executed_quantity: Decimal,
40    /// Executed price
41    #[serde(with = "serde_utils::decimal_opt_0_is_none")]
42    pub executed_price: Option<Decimal>,
43    /// Order ID
44    pub order_id: String,
45    /// Currency
46    pub currency: String,
47    /// Order status
48    pub status: OrderStatus,
49    /// Submitted time
50    #[serde(
51        serialize_with = "time::serde::rfc3339::serialize",
52        deserialize_with = "serde_utils::timestamp::deserialize"
53    )]
54    pub submitted_at: OffsetDateTime,
55    /// Last updated time
56    #[serde(
57        serialize_with = "time::serde::rfc3339::serialize",
58        deserialize_with = "serde_utils::timestamp::deserialize"
59    )]
60    pub updated_at: OffsetDateTime,
61    /// Order trigger price
62    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
63    pub trigger_price: Option<Decimal>,
64    /// Rejected message or remark
65    pub msg: String,
66    /// Order tag
67    pub tag: OrderTag,
68    /// Conditional order trigger status
69    #[serde(with = "serde_utils::trigger_status")]
70    pub trigger_status: Option<TriggerStatus>,
71    /// Conditional order trigger time
72    #[serde(
73        deserialize_with = "serde_utils::timestamp_opt::deserialize",
74        serialize_with = "serde_utils::rfc3339_opt::serialize"
75    )]
76    pub trigger_at: Option<OffsetDateTime>,
77    /// Trailing amount
78    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
79    pub trailing_amount: Option<Decimal>,
80    /// Trailing percent
81    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
82    pub trailing_percent: Option<Decimal>,
83    /// Limit offset amount
84    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
85    pub limit_offset: Option<Decimal>,
86    /// Account no
87    pub account_no: String,
88    /// Last share
89    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
90    pub last_share: Option<Decimal>,
91    /// Last price
92    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
93    pub last_price: Option<Decimal>,
94    /// Remark message
95    pub remark: String,
96}
97
98/// Push event
99#[derive(Debug, Deserialize)]
100#[serde(tag = "event", content = "data")]
101pub enum PushEvent {
102    /// Order changed
103    #[serde(rename = "order_changed_lb")]
104    OrderChanged(PushOrderChanged),
105}
106
107impl PushEvent {
108    pub(crate) fn parse(command_code: u8, data: &[u8]) -> Result<Option<PushEvent>> {
109        if command_code == cmd_code::PUSH_NOTIFICATION {
110            let notification = Notification::decode(data)?;
111            if let Ok(TopicType::Private) = TopicType::from_str(&notification.topic) {
112                Ok(Some(serde_json::from_slice::<PushEvent>(
113                    &notification.data,
114                )?))
115            } else {
116                Ok(None)
117            }
118        } else {
119            Err(Error::UnknownCommand(command_code))
120        }
121    }
122}