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