deribit_base/model/
request.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6
7use crate::model::order::{OrderSide, OrderType, TimeInForce};
8use crate::{impl_json_debug_pretty, impl_json_display};
9use serde::{Deserialize, Serialize};
10
11/// FIX protocol compatible structures
12pub mod fix {
13    use super::*;
14
15    /// New order request structure for FIX protocol
16    #[derive(Clone, Serialize, Deserialize)]
17    pub struct NewOrderRequest {
18        /// Instrument symbol (e.g., "BTC-PERPETUAL")
19        pub symbol: String,
20        /// Order side (buy/sell)
21        pub side: OrderSide,
22        /// Order type
23        pub order_type: OrderType,
24        /// Order quantity
25        pub quantity: f64,
26        /// Order price (required for limit orders)
27        pub price: Option<f64>,
28        /// Time in force
29        pub time_in_force: TimeInForce,
30        /// Client order ID
31        pub client_order_id: Option<String>,
32    }
33
34    impl NewOrderRequest {
35        /// Create a new market buy order
36        pub fn market_buy(symbol: String, quantity: f64) -> Self {
37            Self {
38                symbol,
39                side: OrderSide::Buy,
40                order_type: OrderType::Market,
41                quantity,
42                price: None,
43                time_in_force: TimeInForce::ImmediateOrCancel,
44                client_order_id: None,
45            }
46        }
47
48        /// Create a new market sell order
49        pub fn market_sell(symbol: String, quantity: f64) -> Self {
50            Self {
51                symbol,
52                side: OrderSide::Sell,
53                order_type: OrderType::Market,
54                quantity,
55                price: None,
56                time_in_force: TimeInForce::ImmediateOrCancel,
57                client_order_id: None,
58            }
59        }
60
61        /// Create a new limit buy order
62        pub fn limit_buy(symbol: String, quantity: f64, price: f64) -> Self {
63            Self {
64                symbol,
65                side: OrderSide::Buy,
66                order_type: OrderType::Limit,
67                quantity,
68                price: Some(price),
69                time_in_force: TimeInForce::GoodTilCancelled,
70                client_order_id: None,
71            }
72        }
73
74        /// Create a new limit sell order
75        pub fn limit_sell(symbol: String, quantity: f64, price: f64) -> Self {
76            Self {
77                symbol,
78                side: OrderSide::Sell,
79                order_type: OrderType::Limit,
80                quantity,
81                price: Some(price),
82                time_in_force: TimeInForce::GoodTilCancelled,
83                client_order_id: None,
84            }
85        }
86
87        /// Set client order ID
88        pub fn with_client_order_id(mut self, client_order_id: String) -> Self {
89            self.client_order_id = Some(client_order_id);
90            self
91        }
92
93        /// Set time in force
94        pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
95            self.time_in_force = tif;
96            self
97        }
98    }
99
100    /// Convert from REST/WebSocket NewOrderRequest to FIX NewOrderRequest
101    impl From<super::NewOrderRequest> for NewOrderRequest {
102        fn from(rest_order: super::NewOrderRequest) -> Self {
103            Self {
104                symbol: rest_order.instrument_name,
105                side: rest_order.side,
106                order_type: rest_order.order_type,
107                quantity: rest_order.amount,
108                price: rest_order.price,
109                time_in_force: rest_order.time_in_force,
110                client_order_id: rest_order.client_order_id,
111            }
112        }
113    }
114
115    /// Convert from FIX NewOrderRequest to REST/WebSocket NewOrderRequest
116    impl From<NewOrderRequest> for super::NewOrderRequest {
117        fn from(fix_order: NewOrderRequest) -> Self {
118            Self {
119                instrument_name: fix_order.symbol,
120                amount: fix_order.quantity,
121                order_type: fix_order.order_type,
122                side: fix_order.side,
123                price: fix_order.price,
124                time_in_force: fix_order.time_in_force,
125                post_only: None,
126                reduce_only: None,
127                label: None,
128                stop_price: None,
129                trigger: None,
130                advanced: None,
131                max_show: None,
132                reject_post_only: None,
133                valid_until: None,
134                client_order_id: fix_order.client_order_id,
135            }
136        }
137    }
138}
139
140/// Generic request for creating new orders
141#[derive(Clone, Serialize, Deserialize)]
142pub struct NewOrderRequest {
143    /// Instrument name
144    pub instrument_name: String,
145    /// Order amount
146    pub amount: f64,
147    /// Order type
148    #[serde(rename = "type")]
149    pub order_type: OrderType,
150    /// Order side (buy/sell)
151    pub side: OrderSide,
152    /// Order price (required for limit orders)
153    pub price: Option<f64>,
154    /// Time in force
155    pub time_in_force: TimeInForce,
156    /// Post-only flag
157    pub post_only: Option<bool>,
158    /// Reduce-only flag
159    pub reduce_only: Option<bool>,
160    /// Order label
161    pub label: Option<String>,
162    /// Stop price for stop orders
163    pub stop_price: Option<f64>,
164    /// Trigger type for stop orders
165    pub trigger: Option<TriggerType>,
166    /// Advanced order type
167    pub advanced: Option<AdvancedOrderType>,
168    /// Maximum show amount (iceberg orders)
169    pub max_show: Option<f64>,
170    /// Reject post-only flag
171    pub reject_post_only: Option<bool>,
172    /// Valid until timestamp
173    pub valid_until: Option<i64>,
174    /// Client order ID for tracking
175    pub client_order_id: Option<String>,
176}
177
178impl NewOrderRequest {
179    /// Create a new market buy order
180    pub fn market_buy(instrument_name: String, amount: f64) -> Self {
181        Self {
182            instrument_name,
183            amount,
184            order_type: OrderType::Market,
185            side: OrderSide::Buy,
186            price: None,
187            time_in_force: TimeInForce::ImmediateOrCancel,
188            post_only: None,
189            reduce_only: None,
190            label: None,
191            stop_price: None,
192            trigger: None,
193            advanced: None,
194            max_show: None,
195            reject_post_only: None,
196            valid_until: None,
197            client_order_id: None,
198        }
199    }
200
201    /// Create a new market sell order
202    pub fn market_sell(instrument_name: String, amount: f64) -> Self {
203        Self {
204            instrument_name,
205            amount,
206            order_type: OrderType::Market,
207            side: OrderSide::Sell,
208            price: None,
209            time_in_force: TimeInForce::ImmediateOrCancel,
210            post_only: None,
211            reduce_only: None,
212            label: None,
213            stop_price: None,
214            trigger: None,
215            advanced: None,
216            max_show: None,
217            reject_post_only: None,
218            valid_until: None,
219            client_order_id: None,
220        }
221    }
222
223    /// Create a new limit buy order
224    pub fn limit_buy(instrument_name: String, amount: f64, price: f64) -> Self {
225        Self {
226            instrument_name,
227            amount,
228            order_type: OrderType::Limit,
229            side: OrderSide::Buy,
230            price: Some(price),
231            time_in_force: TimeInForce::GoodTilCancelled,
232            post_only: None,
233            reduce_only: None,
234            label: None,
235            stop_price: None,
236            trigger: None,
237            advanced: None,
238            max_show: None,
239            reject_post_only: None,
240            valid_until: None,
241            client_order_id: None,
242        }
243    }
244
245    /// Create a new limit sell order
246    pub fn limit_sell(instrument_name: String, amount: f64, price: f64) -> Self {
247        Self {
248            instrument_name,
249            amount,
250            order_type: OrderType::Limit,
251            side: OrderSide::Sell,
252            price: Some(price),
253            time_in_force: TimeInForce::GoodTilCancelled,
254            post_only: None,
255            reduce_only: None,
256            label: None,
257            stop_price: None,
258            trigger: None,
259            advanced: None,
260            max_show: None,
261            reject_post_only: None,
262            valid_until: None,
263            client_order_id: None,
264        }
265    }
266
267    /// Set the order as post-only
268    pub fn with_post_only(mut self, post_only: bool) -> Self {
269        self.post_only = Some(post_only);
270        self
271    }
272
273    /// Set the order as reduce-only
274    pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
275        self.reduce_only = Some(reduce_only);
276        self
277    }
278
279    /// Set order label
280    pub fn with_label(mut self, label: String) -> Self {
281        self.label = Some(label);
282        self
283    }
284
285    /// Set time in force
286    pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
287        self.time_in_force = tif;
288        self
289    }
290}
291
292/// Trigger type for stop orders
293#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "snake_case")]
295pub enum TriggerType {
296    /// Index price trigger
297    IndexPrice,
298    /// Mark price trigger
299    MarkPrice,
300    /// Last price trigger
301    LastPrice,
302}
303
304/// Advanced order type
305#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "lowercase")]
307pub enum AdvancedOrderType {
308    /// USD denomination
309    Usd,
310    /// Implied volatility
311    Implv,
312}
313
314/// Order modification request
315#[derive(Clone, Serialize, Deserialize)]
316pub struct ModifyOrderRequest {
317    /// Order ID to modify
318    pub order_id: String,
319    /// New amount
320    pub amount: Option<f64>,
321    /// New price
322    pub price: Option<f64>,
323    /// New stop price
324    pub stop_price: Option<f64>,
325    /// New post-only flag
326    pub post_only: Option<bool>,
327    /// New reduce-only flag
328    pub reduce_only: Option<bool>,
329    /// New reject post-only flag
330    pub reject_post_only: Option<bool>,
331    /// New advanced order type
332    pub advanced: Option<AdvancedOrderType>,
333}
334
335/// Order cancellation request
336#[derive(Clone, Serialize, Deserialize)]
337pub struct CancelOrderRequest {
338    /// Order ID to cancel
339    pub order_id: String,
340}
341
342/// Cancel all orders request
343#[derive(Clone, Serialize, Deserialize)]
344pub struct CancelAllOrdersRequest {
345    /// Currency filter
346    pub currency: Option<String>,
347    /// Instrument kind filter
348    pub kind: Option<String>,
349    /// Instrument type filter
350    #[serde(rename = "type")]
351    pub instrument_type: Option<String>,
352}
353
354/// Position close request
355#[derive(Clone, Serialize, Deserialize)]
356pub struct ClosePositionRequest {
357    /// Instrument name
358    pub instrument_name: String,
359    /// Order type for closing
360    #[serde(rename = "type")]
361    pub order_type: OrderType,
362    /// Price for limit orders
363    pub price: Option<f64>,
364}
365
366/// Authentication request
367#[derive(Clone, Serialize, Deserialize)]
368pub struct AuthRequest {
369    /// Grant type
370    pub grant_type: String,
371    /// Client ID
372    pub client_id: String,
373    /// Client secret
374    pub client_secret: String,
375    /// Refresh token (for refresh grant)
376    pub refresh_token: Option<String>,
377    /// Scope
378    pub scope: Option<String>,
379}
380
381impl AuthRequest {
382    /// Create a client credentials authentication request
383    pub fn client_credentials(client_id: String, client_secret: String) -> Self {
384        Self {
385            grant_type: "client_credentials".to_string(),
386            client_id,
387            client_secret,
388            refresh_token: None,
389            scope: None,
390        }
391    }
392
393    /// Create a refresh token authentication request
394    pub fn refresh_token(client_id: String, client_secret: String, refresh_token: String) -> Self {
395        Self {
396            grant_type: "refresh_token".to_string(),
397            client_id,
398            client_secret,
399            refresh_token: Some(refresh_token),
400            scope: None,
401        }
402    }
403}
404
405// Debug implementations using pretty JSON formatting
406impl_json_debug_pretty!(
407    fix::NewOrderRequest,
408    NewOrderRequest,
409    TriggerType,
410    AdvancedOrderType,
411    ModifyOrderRequest,
412    CancelOrderRequest,
413    CancelAllOrdersRequest,
414    ClosePositionRequest,
415    AuthRequest
416);
417
418// Display implementations using compact JSON formatting
419impl_json_display!(
420    fix::NewOrderRequest,
421    NewOrderRequest,
422    TriggerType,
423    AdvancedOrderType,
424    ModifyOrderRequest,
425    CancelOrderRequest,
426    CancelAllOrdersRequest,
427    ClosePositionRequest,
428    AuthRequest
429);