Skip to main content

longbridge/trade/
push_types.rs

1use std::str::FromStr;
2
3use longbridge_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::{MultiLegInfo, 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    /// Multi-leg strategy information (only present for multi-leg option
97    /// combination orders)
98    #[serde(default)]
99    pub multi_leg: Option<MultiLegInfo>,
100}
101
102/// Grid trading master-order changed message.
103///
104/// Delivered on the same `private` topic as regular order changes. Field set is
105/// a skeleton to be confirmed against the test environment during integration;
106/// unknown fields are ignored (`#[serde(default)]`).
107#[derive(Debug, Default, Deserialize)]
108#[serde(default)]
109pub struct PushGridOrderChanged {
110    /// Grid master order ID
111    pub order_id: String,
112    /// Order status
113    pub status: String,
114    /// Security symbol (e.g. `700.HK`)
115    pub symbol: String,
116    /// Suspend reason, if any
117    pub suspend_reason: String,
118    /// Submitted base price
119    pub submitted_base_price: String,
120    /// Current base price
121    pub current_base_price: String,
122    /// Upper price bound
123    pub upper_limit_price: String,
124    /// Lower price bound
125    pub lower_limit_price: String,
126    /// Trigger price type
127    pub trigger_price_type: i32,
128    /// Quantity per trigger
129    pub trigger_quantity: String,
130    /// Settlement currency
131    pub settlement_currency: String,
132    /// Time in force (`0` = Day, `1` = GTC, `6` = GTD)
133    pub time_in_force: i32,
134    /// Regular trading hours flag
135    pub rth: i32,
136    /// Sell-side order type when depth is 0
137    pub grid_order_type_up: String,
138    /// Buy-side order type when depth is 0
139    pub grid_order_type_down: String,
140}
141
142/// Push event
143#[derive(Debug, Deserialize)]
144#[serde(tag = "event", content = "data")]
145pub enum PushEvent {
146    /// Order changed
147    #[serde(rename = "order_changed_lb")]
148    OrderChanged(PushOrderChanged),
149    /// Grid trading master order changed
150    #[serde(rename = "gridtrading_order")]
151    GridOrderChanged(PushGridOrderChanged),
152}
153
154impl PushEvent {
155    pub(crate) fn parse(command_code: u8, data: &[u8]) -> Result<Option<PushEvent>> {
156        if command_code == cmd_code::PUSH_NOTIFICATION {
157            let notification = Notification::decode(data)?;
158            if let Ok(TopicType::Private) = TopicType::from_str(&notification.topic) {
159                Ok(Some(serde_json::from_slice::<PushEvent>(
160                    &notification.data,
161                )?))
162            } else {
163                Ok(None)
164            }
165        } else {
166            Err(Error::UnknownCommand(command_code))
167        }
168    }
169}