Skip to main content

bybit/ws/
outgoing_message.rs

1use serde::Serialize;
2
3use crate::{
4    SensitiveString, Timestamp, Topic, create_stream_signature,
5    http::{
6        AmendOrderBatchRequest, AmendOrderRequest, CancelOrderBatchRequest, CancelOrderRequest,
7        PlaceOrderBatchRequest, PlaceOrderRequest,
8    },
9    timestamp,
10};
11
12/// Messages sent to a Bybit stream.
13#[derive(Serialize, Debug)]
14#[serde(tag = "op")]
15pub enum OutgoingMessage {
16    /// Subscribe to topics.
17    #[serde(rename = "subscribe")]
18    Subscribe {
19        /// Client id echoed in the reply.
20        #[serde(skip_serializing_if = "Option::is_none")]
21        req_id: Option<String>,
22        /// Topics.
23        args: Vec<Topic>,
24    },
25    /// Unsubscribe from topics.
26    #[serde(rename = "unsubscribe")]
27    Unsubscribe {
28        /// Client id echoed in the reply.
29        #[serde(skip_serializing_if = "Option::is_none")]
30        req_id: Option<String>,
31        /// Topics.
32        args: Vec<Topic>,
33    },
34    /// Authenticate a private stream (see [`create_outgoing_message_auth_at`]).
35    #[serde(rename = "auth")]
36    Auth {
37        /// Client id echoed in the reply.
38        #[serde(skip_serializing_if = "Option::is_none")]
39        req_id: Option<String>,
40        /// `(api_key, expires, signature)`.
41        args: (String, Timestamp, String),
42    },
43    /// Application-level heartbeat.
44    #[serde(rename = "ping")]
45    Ping {
46        /// Client id echoed in the reply.
47        #[serde(skip_serializing_if = "Option::is_none")]
48        req_id: Option<String>,
49    },
50    /// Reply to a server ping.
51    #[serde(rename = "pong")]
52    Pong {
53        /// Client id echoed in the reply.
54        #[serde(skip_serializing_if = "Option::is_none")]
55        req_id: Option<String>,
56    },
57    /// Create an order on the order entry stream (`/v5/trade`).
58    #[serde(rename = "order.create")]
59    OrderCreate {
60        /// Unique id (at most 36 characters) echoed in the reply.
61        #[serde(rename = "reqId")]
62        req_id: String,
63        /// Timestamp and validity of the request.
64        header: TradeHeader,
65        /// Exactly one request.
66        args: Vec<PlaceOrderRequest>,
67    },
68    /// Amend an order on the order entry stream.
69    #[serde(rename = "order.amend")]
70    OrderAmend {
71        /// Unique id (at most 36 characters) echoed in the reply.
72        #[serde(rename = "reqId")]
73        req_id: String,
74        /// Timestamp and validity of the request.
75        header: TradeHeader,
76        /// Exactly one request.
77        args: Vec<AmendOrderRequest>,
78    },
79    /// Cancel an order on the order entry stream.
80    #[serde(rename = "order.cancel")]
81    OrderCancel {
82        /// Unique id (at most 36 characters) echoed in the reply.
83        #[serde(rename = "reqId")]
84        req_id: String,
85        /// Timestamp and validity of the request.
86        header: TradeHeader,
87        /// Exactly one request.
88        args: Vec<CancelOrderRequest>,
89    },
90    /// Create orders in a batch on the order entry stream.
91    #[serde(rename = "order.create-batch")]
92    OrderCreateBatch {
93        /// Unique id (at most 36 characters) echoed in the reply.
94        #[serde(rename = "reqId")]
95        req_id: String,
96        /// Timestamp and validity of the request.
97        header: TradeHeader,
98        /// Exactly one batch.
99        args: Vec<PlaceOrderBatchRequest>,
100    },
101    /// Amend orders in a batch on the order entry stream.
102    #[serde(rename = "order.amend-batch")]
103    OrderAmendBatch {
104        /// Unique id (at most 36 characters) echoed in the reply.
105        #[serde(rename = "reqId")]
106        req_id: String,
107        /// Timestamp and validity of the request.
108        header: TradeHeader,
109        /// Exactly one batch.
110        args: Vec<AmendOrderBatchRequest>,
111    },
112    /// Cancel orders in a batch on the order entry stream.
113    #[serde(rename = "order.cancel-batch")]
114    OrderCancelBatch {
115        /// Unique id (at most 36 characters) echoed in the reply.
116        #[serde(rename = "reqId")]
117        req_id: String,
118        /// Timestamp and validity of the request.
119        header: TradeHeader,
120        /// Exactly one batch.
121        args: Vec<CancelOrderBatchRequest>,
122    },
123}
124
125/// `header` of an order entry request.
126#[derive(Serialize, Debug, Clone, PartialEq)]
127pub struct TradeHeader {
128    /// Request time, milliseconds (use the server clock).
129    #[serde(rename = "X-BAPI-TIMESTAMP")]
130    pub timestamp: String,
131    /// How long the request stays valid, milliseconds.
132    #[serde(rename = "X-BAPI-RECV-WINDOW")]
133    pub recv_window: String,
134    /// Broker referer.
135    #[serde(rename = "Referer", skip_serializing_if = "Option::is_none")]
136    pub referer: Option<String>,
137}
138
139/// `auth` message for a private stream, valid for `recv_window` ms from now
140/// by the local clock. With a skewed clock use
141/// [`create_outgoing_message_auth_at`] and
142/// [`Client::server_timestamp`](crate::http::Client::server_timestamp).
143pub fn create_outgoing_message_auth(
144    api_key: SensitiveString,
145    api_secret: SensitiveString,
146    req_id: Option<String>,
147    recv_window: Timestamp,
148) -> OutgoingMessage {
149    create_outgoing_message_auth_at(api_key, api_secret, req_id, recv_window, timestamp())
150}
151
152/// `auth` message for a private stream, valid for `recv_window` ms from
153/// `now` (milliseconds, ideally the server time).
154pub fn create_outgoing_message_auth_at(
155    api_key: SensitiveString,
156    api_secret: SensitiveString,
157    req_id: Option<String>,
158    recv_window: Timestamp,
159    now: Timestamp,
160) -> OutgoingMessage {
161    let api_key = api_key.expose().to_string();
162    let expires = now + recv_window;
163
164    let signature = create_stream_signature(expires, api_secret);
165
166    OutgoingMessage::Auth {
167        req_id,
168        args: (api_key, expires, signature),
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use crate::*;
175
176    use super::*;
177
178    #[test]
179    fn test_serialize_outgoing_message_subscribe() {
180        let msg = OutgoingMessage::Subscribe {
181            req_id: Some(String::from("request_id")),
182            args: vec![
183                Topic::Ticker(String::from("BTCUSDT")),
184                Topic::Order(Category::Linear),
185            ],
186        };
187        let expected =
188            r#"{"op":"subscribe","req_id":"request_id","args":["tickers.BTCUSDT","order.linear"]}"#;
189        let serialized = serde_json::to_string(&msg).unwrap();
190        assert_eq!(serialized, expected);
191    }
192
193    #[test]
194    fn test_serialize_outgoing_message_unsubscribe() {
195        let msg = OutgoingMessage::Unsubscribe {
196            req_id: Some(String::from("request_id")),
197            args: vec![Topic::Ticker(String::from("BTCUSDT"))],
198        };
199        let expected = r#"{"op":"unsubscribe","req_id":"request_id","args":["tickers.BTCUSDT"]}"#;
200        let serialized = serde_json::to_string(&msg).unwrap();
201        assert_eq!(serialized, expected);
202    }
203
204    #[test]
205    fn test_serialize_outgoing_message_auth() {
206        let msg = OutgoingMessage::Auth {
207            req_id: Some(String::from("request_id")),
208            args: (
209                String::from("api_key"),
210                1662350400000,
211                String::from("signature"),
212            ),
213        };
214        let expected =
215            r#"{"op":"auth","req_id":"request_id","args":["api_key",1662350400000,"signature"]}"#;
216        let serialized = serde_json::to_string(&msg).unwrap();
217        assert_eq!(serialized, expected);
218    }
219}