Skip to main content

futu_backend/
trade_notify_push.rs

1//! C++ release-compatible decoder for CMD4716/CMD14716 trade notifications.
2
3use futu_core::error::{FutuError, Result};
4use prost::Message;
5
6use crate::proto_internal::odr_sys_cmn::{AssetChangeData, Order, OrderFill, RequsetResult};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TradeNotifyPushSource {
10    Real,
11    Sim,
12}
13
14impl TradeNotifyPushSource {
15    const fn cmd_id(self) -> u16 {
16        match self {
17            Self::Real => futu_command_spec::CMD_TRADE_NOTIFY_PUSH,
18            Self::Sim => futu_command_spec::CMD_TRADE_NOTIFY_PUSH_SIM,
19        }
20    }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct TradeNotifyHeader {
25    pub req_id: Option<Vec<u8>>,
26    pub account_id: Option<u64>,
27    pub cipher: Option<Vec<u8>>,
28    pub security_type: Option<u32>,
29    pub exchange_code: Option<u32>,
30    pub input_source: Option<u32>,
31    /// Real uses uint64 `sub_account_id`; SIM uses uint32 `market` at the same
32    /// varint tag. A u64 decoder preserves both wire shapes without inference.
33    pub account_scope: Option<u64>,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct TradeNotifyPush {
38    pub source: TradeNotifyPushSource,
39    pub header: Option<TradeNotifyHeader>,
40    pub notice_type: Option<u32>,
41    pub pstn_ids: Vec<String>,
42    pub order_ids: Vec<String>,
43    pub order_fill_ids: Vec<String>,
44    pub order_op_req_ids: Vec<String>,
45    pub exchange: Option<String>,
46    pub version: Option<u64>,
47    pub deprecated_asset_category: Option<u32>,
48    pub err_code: Option<i32>,
49    pub orders: Vec<Order>,
50    pub order_fills: Vec<OrderFill>,
51    pub origin_request_result: Vec<RequsetResult>,
52    pub asset_change_data: Option<AssetChangeData>,
53}
54
55pub fn decode_trade_notify_push(
56    source: TradeNotifyPushSource,
57    body: &[u8],
58) -> Result<TradeNotifyPush> {
59    let wire = TradeNotifyWire::decode(body)
60        .map_err(|error| FutuError::Codec(format!("CMD{} decode: {error}", source.cmd_id())))?;
61    Ok(TradeNotifyPush {
62        source,
63        header: wire.msg_header.map(TradeNotifyHeader::from),
64        notice_type: wire.notice_type,
65        pstn_ids: wire.pstn_ids,
66        order_ids: wire.order_ids,
67        order_fill_ids: wire.order_fill_ids,
68        order_op_req_ids: wire.order_op_req_ids,
69        exchange: wire.exchange,
70        version: wire.version,
71        deprecated_asset_category: wire.asset_category,
72        err_code: wire.err_code,
73        orders: wire.orders,
74        order_fills: wire.order_fills,
75        origin_request_result: wire.origin_request_result,
76        asset_change_data: wire.asset_change_data,
77    })
78}
79
80impl From<TradeNotifyHeaderWire> for TradeNotifyHeader {
81    fn from(wire: TradeNotifyHeaderWire) -> Self {
82        Self {
83            req_id: wire.req_id,
84            account_id: wire.account_id,
85            cipher: wire.cipher,
86            security_type: wire.security_type,
87            exchange_code: wire.exchange_code,
88            input_source: wire.input_source,
89            account_scope: wire.account_scope,
90        }
91    }
92}
93
94#[derive(Clone, PartialEq, Message)]
95struct TradeNotifyWire {
96    #[prost(message, optional, tag = "1")]
97    msg_header: Option<TradeNotifyHeaderWire>,
98    #[prost(uint32, optional, tag = "2")]
99    notice_type: Option<u32>,
100    #[prost(string, repeated, tag = "3")]
101    pstn_ids: Vec<String>,
102    #[prost(string, repeated, tag = "4")]
103    order_ids: Vec<String>,
104    #[prost(string, repeated, tag = "5")]
105    order_fill_ids: Vec<String>,
106    #[prost(string, repeated, tag = "6")]
107    order_op_req_ids: Vec<String>,
108    #[prost(string, optional, tag = "7")]
109    exchange: Option<String>,
110    #[prost(uint64, optional, tag = "8")]
111    version: Option<u64>,
112    #[prost(uint32, optional, tag = "9")]
113    asset_category: Option<u32>,
114    #[prost(int32, optional, tag = "10")]
115    err_code: Option<i32>,
116    #[prost(message, repeated, tag = "100")]
117    orders: Vec<Order>,
118    #[prost(message, repeated, tag = "101")]
119    order_fills: Vec<OrderFill>,
120    #[prost(message, repeated, tag = "102")]
121    origin_request_result: Vec<RequsetResult>,
122    #[prost(message, optional, tag = "103")]
123    asset_change_data: Option<AssetChangeData>,
124}
125
126#[derive(Clone, PartialEq, Eq, Message)]
127struct TradeNotifyHeaderWire {
128    /// C++ release protobuf parsing does not reject non-UTF8 proto2 strings.
129    /// This field is opaque and never participates in business dispatch.
130    #[prost(bytes = "vec", optional, tag = "1")]
131    req_id: Option<Vec<u8>>,
132    #[prost(uint64, optional, tag = "2")]
133    account_id: Option<u64>,
134    #[prost(bytes = "vec", optional, tag = "3")]
135    cipher: Option<Vec<u8>>,
136    #[prost(uint32, optional, tag = "4")]
137    security_type: Option<u32>,
138    #[prost(uint32, optional, tag = "5")]
139    exchange_code: Option<u32>,
140    #[prost(uint32, optional, tag = "6")]
141    input_source: Option<u32>,
142    #[prost(uint64, optional, tag = "7")]
143    account_scope: Option<u64>,
144}
145
146#[cfg(test)]
147#[path = "trade_notify_push_tests.rs"]
148mod trade_notify_push_tests;