1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use std::str::FromStr;

use longport_proto::trade::Notification;
use prost::Message;
use rust_decimal::Decimal;
use serde::Deserialize;
use strum_macros::{Display, EnumString};
use time::OffsetDateTime;

use crate::{
    serde_utils,
    trade::{cmd_code, OrderSide, OrderStatus, OrderTag, OrderType, TriggerStatus},
    Error, Result,
};

/// Topic type
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
pub enum TopicType {
    /// Private notification for trade
    #[strum(serialize = "private")]
    Private,
}

/// Order changed message
#[derive(Debug, Deserialize)]
pub struct PushOrderChanged {
    /// Order side
    pub side: OrderSide,
    /// Stock name
    pub stock_name: String,
    /// Submitted quantity
    pub submitted_quantity: Decimal,
    /// Order symbol
    pub symbol: String,
    /// Order type
    pub order_type: OrderType,
    /// Submitted price
    pub submitted_price: Decimal,
    /// Executed quantity
    pub executed_quantity: Decimal,
    /// Executed price
    #[serde(with = "serde_utils::decimal_opt_0_is_none")]
    pub executed_price: Option<Decimal>,
    /// Order ID
    pub order_id: String,
    /// Currency
    pub currency: String,
    /// Order status
    pub status: OrderStatus,
    /// Submitted time
    #[serde(with = "serde_utils::timestamp")]
    pub submitted_at: OffsetDateTime,
    /// Last updated time
    #[serde(with = "serde_utils::timestamp")]
    pub updated_at: OffsetDateTime,
    /// Order trigger price
    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
    pub trigger_price: Option<Decimal>,
    /// Rejected message or remark
    pub msg: String,
    /// Order tag
    pub tag: OrderTag,
    /// Conditional order trigger status
    #[serde(with = "serde_utils::trigger_status")]
    pub trigger_status: Option<TriggerStatus>,
    /// Conditional order trigger time
    #[serde(with = "serde_utils::timestamp_opt")]
    pub trigger_at: Option<OffsetDateTime>,
    /// Trailing amount
    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
    pub trailing_amount: Option<Decimal>,
    /// Trailing percent
    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
    pub trailing_percent: Option<Decimal>,
    /// Limit offset amount
    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
    pub limit_offset: Option<Decimal>,
    /// Account no
    pub account_no: String,
    /// Last share
    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
    pub last_share: Option<Decimal>,
    /// Last price
    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
    pub last_price: Option<Decimal>,
    /// Remark message
    pub remark: String,
}

/// Push event
#[derive(Debug, Deserialize)]
#[serde(tag = "event", content = "data")]
pub enum PushEvent {
    /// Order changed
    #[serde(rename = "order_changed_lb")]
    OrderChanged(PushOrderChanged),
}

impl PushEvent {
    pub(crate) fn parse(command_code: u8, data: &[u8]) -> Result<Option<PushEvent>> {
        if command_code == cmd_code::PUSH_NOTIFICATION {
            let notification = Notification::decode(data)?;
            if let Ok(TopicType::Private) = TopicType::from_str(&notification.topic) {
                Ok(Some(serde_json::from_slice::<PushEvent>(
                    &notification.data,
                )?))
            } else {
                Ok(None)
            }
        } else {
            Err(Error::UnknownCommand(command_code))
        }
    }
}