Skip to main content

deribit_websocket/message/
request.rs

1//! WebSocket request message handling
2
3use crate::model::{quote::*, trading::*, ws_types::JsonRpcRequest};
4
5/// Request builder for WebSocket messages
6#[derive(Debug, Clone)]
7pub struct RequestBuilder {
8    id_counter: u64,
9}
10
11impl Default for RequestBuilder {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl RequestBuilder {
18    /// Create a new request builder
19    pub fn new() -> Self {
20        Self { id_counter: 1 }
21    }
22
23    /// Build a JSON-RPC request
24    pub fn build_request(
25        &mut self,
26        method: &str,
27        params: Option<serde_json::Value>,
28    ) -> JsonRpcRequest {
29        let id = self.id_counter;
30        self.id_counter += 1;
31
32        JsonRpcRequest {
33            jsonrpc: "2.0".to_string(),
34            id: serde_json::Value::Number(serde_json::Number::from(id)),
35            method: method.to_string(),
36            params,
37        }
38    }
39
40    /// Build authentication request
41    pub fn build_auth_request(&mut self, client_id: &str, client_secret: &str) -> JsonRpcRequest {
42        let params = serde_json::json!({
43            "grant_type": "client_credentials",
44            "client_id": client_id,
45            "client_secret": client_secret
46        });
47
48        self.build_request("public/auth", Some(params))
49    }
50
51    /// Build subscription request
52    pub fn build_subscribe_request(&mut self, channels: Vec<String>) -> JsonRpcRequest {
53        let params = serde_json::json!({
54            "channels": channels
55        });
56
57        self.build_request("public/subscribe", Some(params))
58    }
59
60    /// Build unsubscription request
61    pub fn build_unsubscribe_request(&mut self, channels: Vec<String>) -> JsonRpcRequest {
62        let params = serde_json::json!({
63            "channels": channels
64        });
65
66        self.build_request("public/unsubscribe", Some(params))
67    }
68
69    /// Build public unsubscribe_all request
70    ///
71    /// Unsubscribes from all public channels. Takes no parameters.
72    ///
73    /// # Returns
74    ///
75    /// A JSON-RPC request for unsubscribing from all public channels
76    pub fn build_public_unsubscribe_all_request(&mut self) -> JsonRpcRequest {
77        self.build_request(
78            crate::constants::methods::PUBLIC_UNSUBSCRIBE_ALL,
79            Some(serde_json::json!({})),
80        )
81    }
82
83    /// Build private unsubscribe_all request
84    ///
85    /// Unsubscribes from all private channels. Takes no parameters.
86    /// Requires authentication.
87    ///
88    /// # Returns
89    ///
90    /// A JSON-RPC request for unsubscribing from all private channels
91    pub fn build_private_unsubscribe_all_request(&mut self) -> JsonRpcRequest {
92        self.build_request(
93            crate::constants::methods::PRIVATE_UNSUBSCRIBE_ALL,
94            Some(serde_json::json!({})),
95        )
96    }
97
98    /// Build test request
99    pub fn build_test_request(&mut self) -> JsonRpcRequest {
100        self.build_request(crate::constants::methods::PUBLIC_TEST, None)
101    }
102
103    /// Build set_heartbeat request
104    ///
105    /// Enables heartbeat with specified interval. The server will send a heartbeat
106    /// message every `interval` seconds, and expects a response within the same interval.
107    ///
108    /// # Arguments
109    ///
110    /// * `interval` - Heartbeat interval in seconds (10-3600)
111    ///
112    /// # Returns
113    ///
114    /// A JSON-RPC request for setting the heartbeat interval
115    pub fn build_set_heartbeat_request(&mut self, interval: u64) -> JsonRpcRequest {
116        let params = serde_json::json!({
117            "interval": interval
118        });
119        self.build_request(
120            crate::constants::methods::PUBLIC_SET_HEARTBEAT,
121            Some(params),
122        )
123    }
124
125    /// Build disable_heartbeat request
126    ///
127    /// Disables heartbeat messages. The server will stop sending heartbeat messages
128    /// and test_request notifications.
129    ///
130    /// # Returns
131    ///
132    /// A JSON-RPC request for disabling heartbeats
133    pub fn build_disable_heartbeat_request(&mut self) -> JsonRpcRequest {
134        self.build_request(
135            crate::constants::methods::PUBLIC_DISABLE_HEARTBEAT,
136            Some(serde_json::json!({})),
137        )
138    }
139
140    /// Build hello request
141    ///
142    /// Sends client identification to the server. This is used for client tracking
143    /// and debugging purposes.
144    ///
145    /// # Arguments
146    ///
147    /// * `client_name` - Name of the client application
148    /// * `client_version` - Version of the client application
149    ///
150    /// # Returns
151    ///
152    /// A JSON-RPC request for client identification
153    pub fn build_hello_request(
154        &mut self,
155        client_name: &str,
156        client_version: &str,
157    ) -> JsonRpcRequest {
158        let params = serde_json::json!({
159            "client_name": client_name,
160            "client_version": client_version
161        });
162        self.build_request(crate::constants::methods::PUBLIC_HELLO, Some(params))
163    }
164
165    /// Build get time request
166    pub fn build_get_time_request(&mut self) -> JsonRpcRequest {
167        self.build_request("public/get_time", None)
168    }
169
170    /// Build enable_cancel_on_disconnect request
171    ///
172    /// Enables automatic cancellation of all open orders when the WebSocket connection
173    /// is lost. This is a safety feature to prevent unintended order execution when
174    /// the client loses connectivity.
175    ///
176    /// # Returns
177    ///
178    /// A JSON-RPC request for enabling cancel-on-disconnect
179    pub fn build_enable_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
180        self.build_request(
181            crate::constants::methods::PRIVATE_ENABLE_CANCEL_ON_DISCONNECT,
182            Some(serde_json::json!({})),
183        )
184    }
185
186    /// Build disable_cancel_on_disconnect request
187    ///
188    /// Disables automatic cancellation of orders on disconnect. Orders will remain
189    /// active even if the WebSocket connection is lost.
190    ///
191    /// # Returns
192    ///
193    /// A JSON-RPC request for disabling cancel-on-disconnect
194    pub fn build_disable_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
195        self.build_request(
196            crate::constants::methods::PRIVATE_DISABLE_CANCEL_ON_DISCONNECT,
197            Some(serde_json::json!({})),
198        )
199    }
200
201    /// Build get_cancel_on_disconnect request
202    ///
203    /// Retrieves the current cancel-on-disconnect status for the session.
204    ///
205    /// # Returns
206    ///
207    /// A JSON-RPC request for getting the cancel-on-disconnect status
208    pub fn build_get_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
209        self.build_request(
210            crate::constants::methods::PRIVATE_GET_CANCEL_ON_DISCONNECT,
211            Some(serde_json::json!({})),
212        )
213    }
214
215    /// Build mass quote request
216    pub fn build_mass_quote_request(&mut self, request: MassQuoteRequest) -> JsonRpcRequest {
217        let params = serde_json::to_value(request).expect("Failed to serialize mass quote request");
218
219        self.build_request("private/mass_quote", Some(params))
220    }
221
222    /// Build cancel quotes request
223    pub fn build_cancel_quotes_request(&mut self, request: CancelQuotesRequest) -> JsonRpcRequest {
224        let params =
225            serde_json::to_value(request).expect("Failed to serialize cancel quotes request");
226
227        self.build_request("private/cancel_quotes", Some(params))
228    }
229
230    /// Build set MMP config request
231    pub fn build_set_mmp_config_request(&mut self, config: MmpGroupConfig) -> JsonRpcRequest {
232        let mut params = serde_json::json!({
233            "mmp_group": config.mmp_group,
234            "quantity_limit": config.quantity_limit,
235            "delta_limit": config.delta_limit,
236            "interval": config.interval,
237            "frozen_time": config.frozen_time
238        });
239
240        // If interval is 0, this disables the group
241        if config.interval == 0 {
242            params["interval"] = serde_json::Value::Number(serde_json::Number::from(0));
243        }
244
245        self.build_request("private/set_mmp_config", Some(params))
246    }
247
248    /// Build get MMP config request
249    pub fn build_get_mmp_config_request(&mut self, mmp_group: Option<String>) -> JsonRpcRequest {
250        let params = if let Some(group) = mmp_group {
251            serde_json::json!({
252                "mmp_group": group
253            })
254        } else {
255            serde_json::json!({})
256        };
257
258        self.build_request("private/get_mmp_config", Some(params))
259    }
260
261    /// Build reset MMP request
262    pub fn build_reset_mmp_request(&mut self, mmp_group: Option<String>) -> JsonRpcRequest {
263        let params = if let Some(group) = mmp_group {
264            serde_json::json!({
265                "mmp_group": group
266            })
267        } else {
268            serde_json::json!({})
269        };
270
271        self.build_request("private/reset_mmp", Some(params))
272    }
273
274    /// Build get open orders request
275    pub fn build_get_open_orders_request(
276        &mut self,
277        currency: Option<String>,
278        kind: Option<String>,
279        type_filter: Option<String>,
280    ) -> JsonRpcRequest {
281        let mut params = serde_json::json!({});
282
283        if let Some(currency) = currency {
284            params["currency"] = serde_json::Value::String(currency);
285        }
286        if let Some(kind) = kind {
287            params["kind"] = serde_json::Value::String(kind);
288        }
289        if let Some(type_filter) = type_filter {
290            params["type"] = serde_json::Value::String(type_filter);
291        }
292
293        self.build_request("private/get_open_orders", Some(params))
294    }
295
296    /// Build buy order request
297    pub fn build_buy_request(&mut self, request: &OrderRequest) -> JsonRpcRequest {
298        let mut params = serde_json::json!({
299            "instrument_name": request.instrument_name,
300            "amount": request.amount
301        });
302
303        if let Some(ref order_type) = request.order_type {
304            params["type"] = serde_json::Value::String(order_type.as_str().to_string());
305        }
306        if let Some(price) = request.price {
307            params["price"] = serde_json::Value::Number(
308                serde_json::Number::from_f64(price).unwrap_or(serde_json::Number::from(0)),
309            );
310        }
311        if let Some(ref label) = request.label {
312            params["label"] = serde_json::Value::String(label.clone());
313        }
314        if let Some(ref tif) = request.time_in_force {
315            params["time_in_force"] = serde_json::Value::String(tif.as_str().to_string());
316        }
317        if let Some(max_show) = request.max_show {
318            params["max_show"] = serde_json::Value::Number(
319                serde_json::Number::from_f64(max_show).unwrap_or(serde_json::Number::from(0)),
320            );
321        }
322        if let Some(post_only) = request.post_only {
323            params["post_only"] = serde_json::Value::Bool(post_only);
324        }
325        if let Some(reduce_only) = request.reduce_only {
326            params["reduce_only"] = serde_json::Value::Bool(reduce_only);
327        }
328        if let Some(trigger_price) = request.trigger_price {
329            params["trigger_price"] = serde_json::Value::Number(
330                serde_json::Number::from_f64(trigger_price).unwrap_or(serde_json::Number::from(0)),
331            );
332        }
333        if let Some(ref trigger) = request.trigger {
334            let trigger_str = match trigger {
335                Trigger::IndexPrice => "index_price",
336                Trigger::MarkPrice => "mark_price",
337                Trigger::LastPrice => "last_price",
338            };
339            params["trigger"] = serde_json::Value::String(trigger_str.to_string());
340        }
341        if let Some(ref advanced) = request.advanced {
342            params["advanced"] = serde_json::Value::String(advanced.clone());
343        }
344        if let Some(mmp) = request.mmp {
345            params["mmp"] = serde_json::Value::Bool(mmp);
346        }
347        if let Some(valid_until) = request.valid_until {
348            params["valid_until"] =
349                serde_json::Value::Number(serde_json::Number::from(valid_until));
350        }
351
352        self.build_request("private/buy", Some(params))
353    }
354
355    /// Build sell order request
356    pub fn build_sell_request(&mut self, request: &OrderRequest) -> JsonRpcRequest {
357        let mut params = serde_json::json!({
358            "instrument_name": request.instrument_name,
359            "amount": request.amount
360        });
361
362        if let Some(ref order_type) = request.order_type {
363            params["type"] = serde_json::Value::String(order_type.as_str().to_string());
364        }
365        if let Some(price) = request.price {
366            params["price"] = serde_json::Value::Number(
367                serde_json::Number::from_f64(price).unwrap_or(serde_json::Number::from(0)),
368            );
369        }
370        if let Some(ref label) = request.label {
371            params["label"] = serde_json::Value::String(label.clone());
372        }
373        if let Some(ref tif) = request.time_in_force {
374            params["time_in_force"] = serde_json::Value::String(tif.as_str().to_string());
375        }
376        if let Some(max_show) = request.max_show {
377            params["max_show"] = serde_json::Value::Number(
378                serde_json::Number::from_f64(max_show).unwrap_or(serde_json::Number::from(0)),
379            );
380        }
381        if let Some(post_only) = request.post_only {
382            params["post_only"] = serde_json::Value::Bool(post_only);
383        }
384        if let Some(reduce_only) = request.reduce_only {
385            params["reduce_only"] = serde_json::Value::Bool(reduce_only);
386        }
387        if let Some(trigger_price) = request.trigger_price {
388            params["trigger_price"] = serde_json::Value::Number(
389                serde_json::Number::from_f64(trigger_price).unwrap_or(serde_json::Number::from(0)),
390            );
391        }
392        if let Some(ref trigger) = request.trigger {
393            let trigger_str = match trigger {
394                Trigger::IndexPrice => "index_price",
395                Trigger::MarkPrice => "mark_price",
396                Trigger::LastPrice => "last_price",
397            };
398            params["trigger"] = serde_json::Value::String(trigger_str.to_string());
399        }
400        if let Some(ref advanced) = request.advanced {
401            params["advanced"] = serde_json::Value::String(advanced.clone());
402        }
403        if let Some(mmp) = request.mmp {
404            params["mmp"] = serde_json::Value::Bool(mmp);
405        }
406        if let Some(valid_until) = request.valid_until {
407            params["valid_until"] =
408                serde_json::Value::Number(serde_json::Number::from(valid_until));
409        }
410
411        self.build_request("private/sell", Some(params))
412    }
413
414    /// Build cancel order request
415    pub fn build_cancel_request(&mut self, order_id: &str) -> JsonRpcRequest {
416        let params = serde_json::json!({
417            "order_id": order_id
418        });
419
420        self.build_request("private/cancel", Some(params))
421    }
422
423    /// Build cancel all orders request
424    pub fn build_cancel_all_request(&mut self) -> JsonRpcRequest {
425        self.build_request("private/cancel_all", Some(serde_json::json!({})))
426    }
427
428    /// Build cancel all orders by currency request
429    pub fn build_cancel_all_by_currency_request(&mut self, currency: &str) -> JsonRpcRequest {
430        let params = serde_json::json!({
431            "currency": currency
432        });
433
434        self.build_request("private/cancel_all_by_currency", Some(params))
435    }
436
437    /// Build cancel all orders by instrument request
438    pub fn build_cancel_all_by_instrument_request(
439        &mut self,
440        instrument_name: &str,
441    ) -> JsonRpcRequest {
442        let params = serde_json::json!({
443            "instrument_name": instrument_name
444        });
445
446        self.build_request("private/cancel_all_by_instrument", Some(params))
447    }
448
449    /// Build edit order request
450    pub fn build_edit_request(&mut self, request: &EditOrderRequest) -> JsonRpcRequest {
451        let mut params = serde_json::json!({
452            "order_id": request.order_id,
453            "amount": request.amount
454        });
455
456        if let Some(price) = request.price {
457            params["price"] = serde_json::Value::Number(
458                serde_json::Number::from_f64(price).unwrap_or(serde_json::Number::from(0)),
459            );
460        }
461        if let Some(post_only) = request.post_only {
462            params["post_only"] = serde_json::Value::Bool(post_only);
463        }
464        if let Some(reduce_only) = request.reduce_only {
465            params["reduce_only"] = serde_json::Value::Bool(reduce_only);
466        }
467        if let Some(ref advanced) = request.advanced {
468            params["advanced"] = serde_json::Value::String(advanced.clone());
469        }
470        if let Some(trigger_price) = request.trigger_price {
471            params["trigger_price"] = serde_json::Value::Number(
472                serde_json::Number::from_f64(trigger_price).unwrap_or(serde_json::Number::from(0)),
473            );
474        }
475        if let Some(mmp) = request.mmp {
476            params["mmp"] = serde_json::Value::Bool(mmp);
477        }
478        if let Some(valid_until) = request.valid_until {
479            params["valid_until"] =
480                serde_json::Value::Number(serde_json::Number::from(valid_until));
481        }
482
483        self.build_request("private/edit", Some(params))
484    }
485
486    // Account methods
487
488    /// Build a get_positions request
489    ///
490    /// # Arguments
491    ///
492    /// * `currency` - Currency filter (BTC, ETH, USDC, etc.) - optional
493    /// * `kind` - Kind filter (future, option, spot, etc.) - optional
494    ///
495    /// # Returns
496    ///
497    /// A JSON-RPC request for getting positions
498    pub fn build_get_positions_request(
499        &mut self,
500        currency: Option<&str>,
501        kind: Option<&str>,
502    ) -> JsonRpcRequest {
503        let mut params = serde_json::Map::new();
504
505        if let Some(currency) = currency {
506            params.insert(
507                "currency".to_string(),
508                serde_json::Value::String(currency.to_string()),
509            );
510        }
511
512        if let Some(kind) = kind {
513            params.insert(
514                "kind".to_string(),
515                serde_json::Value::String(kind.to_string()),
516            );
517        }
518
519        if params.is_empty() {
520            self.build_request(crate::constants::methods::PRIVATE_GET_POSITIONS, None)
521        } else {
522            self.build_request(
523                crate::constants::methods::PRIVATE_GET_POSITIONS,
524                Some(serde_json::Value::Object(params)),
525            )
526        }
527    }
528
529    /// Build a get_account_summary request
530    ///
531    /// # Arguments
532    ///
533    /// * `currency` - Currency to get summary for (BTC, ETH, USDC, etc.)
534    /// * `extended` - Whether to include extended information
535    ///
536    /// # Returns
537    ///
538    /// A JSON-RPC request for getting account summary
539    pub fn build_get_account_summary_request(
540        &mut self,
541        currency: &str,
542        extended: Option<bool>,
543    ) -> JsonRpcRequest {
544        let mut params = serde_json::Map::new();
545        params.insert(
546            "currency".to_string(),
547            serde_json::Value::String(currency.to_string()),
548        );
549
550        if let Some(extended) = extended {
551            params.insert("extended".to_string(), serde_json::Value::Bool(extended));
552        }
553
554        self.build_request(
555            crate::constants::methods::PRIVATE_GET_ACCOUNT_SUMMARY,
556            Some(serde_json::Value::Object(params)),
557        )
558    }
559
560    /// Build a get_order_state request
561    ///
562    /// # Arguments
563    ///
564    /// * `order_id` - The order ID to get state for
565    ///
566    /// # Returns
567    ///
568    /// A JSON-RPC request for getting order state
569    pub fn build_get_order_state_request(&mut self, order_id: &str) -> JsonRpcRequest {
570        let params = serde_json::json!({
571            "order_id": order_id
572        });
573
574        self.build_request(
575            crate::constants::methods::PRIVATE_GET_ORDER_STATE,
576            Some(params),
577        )
578    }
579
580    /// Build a get_order_history_by_currency request
581    ///
582    /// # Arguments
583    ///
584    /// * `currency` - Currency to get order history for
585    /// * `kind` - Kind filter (future, option, spot, etc.) - optional
586    /// * `count` - Number of items to return - optional
587    ///
588    /// # Returns
589    ///
590    /// A JSON-RPC request for getting order history
591    pub fn build_get_order_history_by_currency_request(
592        &mut self,
593        currency: &str,
594        kind: Option<&str>,
595        count: Option<u32>,
596    ) -> JsonRpcRequest {
597        let mut params = serde_json::Map::new();
598        params.insert(
599            "currency".to_string(),
600            serde_json::Value::String(currency.to_string()),
601        );
602
603        if let Some(kind) = kind {
604            params.insert(
605                "kind".to_string(),
606                serde_json::Value::String(kind.to_string()),
607            );
608        }
609
610        if let Some(count) = count {
611            params.insert(
612                "count".to_string(),
613                serde_json::Value::Number(serde_json::Number::from(count)),
614            );
615        }
616
617        self.build_request(
618            crate::constants::methods::PRIVATE_GET_ORDER_HISTORY_BY_CURRENCY,
619            Some(serde_json::Value::Object(params)),
620        )
621    }
622
623    // Position management methods
624
625    /// Build a close_position request
626    ///
627    /// # Arguments
628    ///
629    /// * `instrument_name` - The instrument to close position for
630    /// * `order_type` - Order type: "limit" or "market"
631    /// * `price` - Price for limit orders (required if order_type is "limit")
632    ///
633    /// # Returns
634    ///
635    /// A JSON-RPC request for closing a position
636    pub fn build_close_position_request(
637        &mut self,
638        instrument_name: &str,
639        order_type: &str,
640        price: Option<f64>,
641    ) -> JsonRpcRequest {
642        let mut params = serde_json::Map::new();
643        params.insert(
644            "instrument_name".to_string(),
645            serde_json::Value::String(instrument_name.to_string()),
646        );
647        params.insert(
648            "type".to_string(),
649            serde_json::Value::String(order_type.to_string()),
650        );
651
652        if let Some(price) = price
653            && let Some(price_num) = serde_json::Number::from_f64(price)
654        {
655            params.insert("price".to_string(), serde_json::Value::Number(price_num));
656        }
657
658        self.build_request(
659            crate::constants::methods::PRIVATE_CLOSE_POSITION,
660            Some(serde_json::Value::Object(params)),
661        )
662    }
663
664    /// Build a move_positions request
665    ///
666    /// # Arguments
667    ///
668    /// * `currency` - Currency for the positions (BTC, ETH, etc.)
669    /// * `source_uid` - Source subaccount ID
670    /// * `target_uid` - Target subaccount ID
671    /// * `trades` - List of positions to move
672    ///
673    /// # Returns
674    ///
675    /// A JSON-RPC request for moving positions between subaccounts
676    pub fn build_move_positions_request(
677        &mut self,
678        currency: &str,
679        source_uid: u64,
680        target_uid: u64,
681        trades: &[crate::model::MovePositionTrade],
682    ) -> JsonRpcRequest {
683        let trades_json: Vec<serde_json::Value> = trades
684            .iter()
685            .map(|t| {
686                let mut trade_obj = serde_json::Map::new();
687                trade_obj.insert(
688                    "instrument_name".to_string(),
689                    serde_json::Value::String(t.instrument_name.clone()),
690                );
691                if let Some(amount_num) = serde_json::Number::from_f64(t.amount) {
692                    trade_obj.insert("amount".to_string(), serde_json::Value::Number(amount_num));
693                }
694                if let Some(price) = t.price
695                    && let Some(price_num) = serde_json::Number::from_f64(price)
696                {
697                    trade_obj.insert("price".to_string(), serde_json::Value::Number(price_num));
698                }
699                serde_json::Value::Object(trade_obj)
700            })
701            .collect();
702
703        let params = serde_json::json!({
704            "currency": currency,
705            "source_uid": source_uid,
706            "target_uid": target_uid,
707            "trades": trades_json
708        });
709
710        self.build_request(
711            crate::constants::methods::PRIVATE_MOVE_POSITIONS,
712            Some(params),
713        )
714    }
715}