deribit_fix/message/orders/
mod.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 22/7/25
5******************************************************************************/
6
7//! Order Management FIX Messages Module
8
9use serde::{Deserialize, Serialize};
10use std::convert::TryFrom;
11
12pub mod cancel_reject;
13pub mod cancel_replace_request;
14pub mod cancel_request;
15pub mod execution_report;
16pub mod mass_cancel;
17pub mod mass_status;
18pub mod new_order;
19
20pub use cancel_reject::*;
21pub use cancel_replace_request::*;
22pub use cancel_request::*;
23pub use execution_report::*;
24pub use mass_cancel::*;
25pub use mass_status::*;
26pub use new_order::*;
27
28/// Order side enumeration
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum OrderSide {
31    /// Buy order
32    Buy,
33    /// Sell order
34    Sell,
35}
36
37impl From<OrderSide> for char {
38    fn from(side: OrderSide) -> Self {
39        match side {
40            OrderSide::Buy => '1',
41            OrderSide::Sell => '2',
42        }
43    }
44}
45
46impl TryFrom<char> for OrderSide {
47    type Error = String;
48
49    fn try_from(value: char) -> Result<Self, Self::Error> {
50        match value {
51            '1' => Ok(OrderSide::Buy),
52            '2' => Ok(OrderSide::Sell),
53            _ => Err(format!("Invalid OrderSide: {value}")),
54        }
55    }
56}
57
58/// Order type enumeration
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60pub enum OrderType {
61    /// Market order
62    Market,
63    /// Limit order
64    Limit,
65    /// Market with left over as limit (market limit)
66    MarketLimit,
67    /// Stop limit (trailing stop)
68    StopLimit,
69    /// Market if touched (stop limit with StopPx)
70    MarketIfTouched,
71    /// Stop limit on bid or offer (stop market with StopPx)
72    StopLimitOnBidOffer,
73}
74
75impl From<OrderType> for char {
76    fn from(order_type: OrderType) -> Self {
77        match order_type {
78            OrderType::Market => '1',
79            OrderType::Limit => '2',
80            OrderType::MarketLimit => 'K',
81            OrderType::StopLimit => '4',
82            OrderType::MarketIfTouched => 'J',
83            OrderType::StopLimitOnBidOffer => 'S',
84        }
85    }
86}
87
88impl TryFrom<char> for OrderType {
89    type Error = String;
90
91    fn try_from(value: char) -> Result<Self, Self::Error> {
92        match value {
93            '1' => Ok(OrderType::Market),
94            '2' => Ok(OrderType::Limit),
95            'K' => Ok(OrderType::MarketLimit),
96            '4' => Ok(OrderType::StopLimit),
97            'J' => Ok(OrderType::MarketIfTouched),
98            'S' => Ok(OrderType::StopLimitOnBidOffer),
99            _ => Err(format!("Invalid OrderType: {value}")),
100        }
101    }
102}
103
104/// Time in force enumeration
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106pub enum TimeInForce {
107    /// Good Till Day
108    GoodTillDay,
109    /// Good Till Cancelled
110    GoodTillCancelled,
111    /// Immediate or Cancel
112    ImmediateOrCancel,
113    /// Fill or Kill
114    FillOrKill,
115}
116
117impl From<TimeInForce> for char {
118    fn from(tif: TimeInForce) -> Self {
119        match tif {
120            TimeInForce::GoodTillDay => '0',
121            TimeInForce::GoodTillCancelled => '1',
122            TimeInForce::ImmediateOrCancel => '3',
123            TimeInForce::FillOrKill => '4',
124        }
125    }
126}
127
128impl TryFrom<char> for TimeInForce {
129    type Error = String;
130
131    fn try_from(value: char) -> Result<Self, Self::Error> {
132        match value {
133            '0' => Ok(TimeInForce::GoodTillDay),
134            '1' => Ok(TimeInForce::GoodTillCancelled),
135            '3' => Ok(TimeInForce::ImmediateOrCancel),
136            '4' => Ok(TimeInForce::FillOrKill),
137            _ => Err(format!("Invalid TimeInForce: {value}")),
138        }
139    }
140}
141
142/// Order status enumeration
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
144pub enum OrderStatus {
145    /// New order
146    New,
147    /// Partially filled
148    PartiallyFilled,
149    /// Filled
150    Filled,
151    /// Cancelled
152    Cancelled,
153    /// Pending cancel
154    PendingCancel,
155    /// Rejected
156    Rejected,
157}
158
159impl From<OrderStatus> for char {
160    fn from(status: OrderStatus) -> Self {
161        match status {
162            OrderStatus::New => '0',
163            OrderStatus::PartiallyFilled => '1',
164            OrderStatus::Filled => '2',
165            OrderStatus::Cancelled => '4',
166            OrderStatus::PendingCancel => '6',
167            OrderStatus::Rejected => '8',
168        }
169    }
170}
171
172impl TryFrom<char> for OrderStatus {
173    type Error = String;
174
175    fn try_from(value: char) -> Result<Self, Self::Error> {
176        match value {
177            '0' => Ok(OrderStatus::New),
178            '1' => Ok(OrderStatus::PartiallyFilled),
179            '2' => Ok(OrderStatus::Filled),
180            '4' => Ok(OrderStatus::Cancelled),
181            '6' => Ok(OrderStatus::PendingCancel),
182            '8' => Ok(OrderStatus::Rejected),
183            _ => Err(format!("Invalid OrderStatus: {value}")),
184        }
185    }
186}
187
188/// Order rejection reason enumeration
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
190pub enum OrderRejectReason {
191    /// No reject (accepted)
192    NoReject,
193    /// Unknown symbol
194    UnknownSymbol,
195    /// Exchange closed
196    ExchangeClosed,
197    /// Order exceeds limit
198    OrderExceedsLimit,
199    /// Too late to enter
200    TooLateToEnter,
201    /// Unknown order
202    UnknownOrder,
203    /// Duplicate order
204    DuplicateOrder,
205    /// Duplicate of verbally communicated order
206    DuplicateVerbalOrder,
207    /// Stale order
208    StaleOrder,
209    /// Trade along required
210    TradeAlongRequired,
211    /// Invalid investor ID
212    InvalidInvestorId,
213    /// Unsupported order characteristic
214    UnsupportedOrderCharacteristic,
215    /// Surveillance option
216    SurveillanceOption,
217    /// Incorrect quantity
218    IncorrectQuantity,
219    /// Incorrect allocated quantity
220    IncorrectAllocatedQuantity,
221    /// Unknown account
222    UnknownAccount,
223    /// Price exceeds current price band
224    PriceExceedsPriceBand,
225    /// Invalid price increment
226    InvalidPriceIncrement,
227    /// Other
228    Other,
229}
230
231impl From<OrderRejectReason> for i32 {
232    fn from(reason: OrderRejectReason) -> Self {
233        match reason {
234            OrderRejectReason::NoReject => 0,
235            OrderRejectReason::UnknownSymbol => 1,
236            OrderRejectReason::ExchangeClosed => 2,
237            OrderRejectReason::OrderExceedsLimit => 3,
238            OrderRejectReason::TooLateToEnter => 4,
239            OrderRejectReason::UnknownOrder => 5,
240            OrderRejectReason::DuplicateOrder => 6,
241            OrderRejectReason::DuplicateVerbalOrder => 7,
242            OrderRejectReason::StaleOrder => 8,
243            OrderRejectReason::TradeAlongRequired => 9,
244            OrderRejectReason::InvalidInvestorId => 10,
245            OrderRejectReason::UnsupportedOrderCharacteristic => 11,
246            OrderRejectReason::SurveillanceOption => 12,
247            OrderRejectReason::IncorrectQuantity => 13,
248            OrderRejectReason::IncorrectAllocatedQuantity => 14,
249            OrderRejectReason::UnknownAccount => 15,
250            OrderRejectReason::PriceExceedsPriceBand => 16,
251            OrderRejectReason::InvalidPriceIncrement => 18,
252            OrderRejectReason::Other => 99,
253        }
254    }
255}
256
257impl TryFrom<i32> for OrderRejectReason {
258    type Error = String;
259
260    fn try_from(value: i32) -> Result<Self, Self::Error> {
261        match value {
262            0 => Ok(OrderRejectReason::NoReject),
263            1 => Ok(OrderRejectReason::UnknownSymbol),
264            2 => Ok(OrderRejectReason::ExchangeClosed),
265            3 => Ok(OrderRejectReason::OrderExceedsLimit),
266            4 => Ok(OrderRejectReason::TooLateToEnter),
267            5 => Ok(OrderRejectReason::UnknownOrder),
268            6 => Ok(OrderRejectReason::DuplicateOrder),
269            7 => Ok(OrderRejectReason::DuplicateVerbalOrder),
270            8 => Ok(OrderRejectReason::StaleOrder),
271            9 => Ok(OrderRejectReason::TradeAlongRequired),
272            10 => Ok(OrderRejectReason::InvalidInvestorId),
273            11 => Ok(OrderRejectReason::UnsupportedOrderCharacteristic),
274            12 => Ok(OrderRejectReason::SurveillanceOption),
275            13 => Ok(OrderRejectReason::IncorrectQuantity),
276            14 => Ok(OrderRejectReason::IncorrectAllocatedQuantity),
277            15 => Ok(OrderRejectReason::UnknownAccount),
278            16 => Ok(OrderRejectReason::PriceExceedsPriceBand),
279            18 => Ok(OrderRejectReason::InvalidPriceIncrement),
280            99 => Ok(OrderRejectReason::Other),
281            _ => Err(format!("Invalid OrderRejectReason: {value}")),
282        }
283    }
284}
285
286/// Mass cancel request type enumeration
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
288pub enum MassCancelRequestType {
289    /// Cancel orders by symbol
290    BySymbol,
291    /// Cancel orders by security type
292    BySecurityType,
293    /// Cancel all orders
294    AllOrders,
295    /// Cancel orders by Deribit label
296    ByDeribitLabel,
297}
298
299impl From<MassCancelRequestType> for i32 {
300    fn from(request_type: MassCancelRequestType) -> Self {
301        match request_type {
302            MassCancelRequestType::BySymbol => 1,
303            MassCancelRequestType::BySecurityType => 5,
304            MassCancelRequestType::AllOrders => 7,
305            MassCancelRequestType::ByDeribitLabel => 10,
306        }
307    }
308}
309
310impl TryFrom<i32> for MassCancelRequestType {
311    type Error = String;
312
313    fn try_from(value: i32) -> Result<Self, Self::Error> {
314        match value {
315            1 => Ok(MassCancelRequestType::BySymbol),
316            5 => Ok(MassCancelRequestType::BySecurityType),
317            7 => Ok(MassCancelRequestType::AllOrders),
318            10 => Ok(MassCancelRequestType::ByDeribitLabel),
319            _ => Err(format!("Invalid MassCancelRequestType: {value}")),
320        }
321    }
322}
323
324/// Mass status request type enumeration
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
326pub enum MassStatusRequestType {
327    /// Status of a specific order
328    SpecificOrder,
329    /// Status of all orders
330    AllOrders,
331    /// Historical orders request
332    Historical,
333}
334
335impl From<MassStatusRequestType> for i32 {
336    fn from(request_type: MassStatusRequestType) -> Self {
337        match request_type {
338            MassStatusRequestType::SpecificOrder => 1,
339            MassStatusRequestType::AllOrders => 7,
340            MassStatusRequestType::Historical => 10,
341        }
342    }
343}
344
345impl TryFrom<i32> for MassStatusRequestType {
346    type Error = String;
347
348    fn try_from(value: i32) -> Result<Self, Self::Error> {
349        match value {
350            1 => Ok(MassStatusRequestType::SpecificOrder),
351            7 => Ok(MassStatusRequestType::AllOrders),
352            10 => Ok(MassStatusRequestType::Historical),
353            _ => Err(format!("Invalid MassStatusRequestType: {value}")),
354        }
355    }
356}
357
358/// Mass status request ID type enumeration
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
360pub enum MassStatusRequestIdType {
361    /// Original client order ID
362    OrigClOrdId,
363    /// Client order ID
364    ClOrdId,
365    /// Deribit label
366    DeribitLabel,
367}
368
369impl From<MassStatusRequestIdType> for i32 {
370    fn from(id_type: MassStatusRequestIdType) -> Self {
371        match id_type {
372            MassStatusRequestIdType::OrigClOrdId => 0,
373            MassStatusRequestIdType::ClOrdId => 1,
374            MassStatusRequestIdType::DeribitLabel => 2,
375        }
376    }
377}
378
379impl TryFrom<i32> for MassStatusRequestIdType {
380    type Error = String;
381
382    fn try_from(value: i32) -> Result<Self, Self::Error> {
383        match value {
384            0 => Ok(MassStatusRequestIdType::OrigClOrdId),
385            1 => Ok(MassStatusRequestIdType::ClOrdId),
386            2 => Ok(MassStatusRequestIdType::DeribitLabel),
387            _ => Err(format!("Invalid MassStatusRequestIdType: {value}")),
388        }
389    }
390}
391
392/// Quantity type enumeration
393#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
394pub enum QuantityType {
395    /// Units (USD for perpetual/inverse futures, base currency for linear futures, contracts for options)
396    Units,
397    /// Contracts
398    Contracts,
399}
400
401impl From<QuantityType> for i32 {
402    fn from(qty_type: QuantityType) -> Self {
403        match qty_type {
404            QuantityType::Units => 0,
405            QuantityType::Contracts => 1,
406        }
407    }
408}
409
410impl TryFrom<i32> for QuantityType {
411    type Error = String;
412
413    fn try_from(value: i32) -> Result<Self, Self::Error> {
414        match value {
415            0 => Ok(QuantityType::Units),
416            1 => Ok(QuantityType::Contracts),
417            _ => Err(format!("Invalid QuantityType: {value}")),
418        }
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn test_order_side_conversion() {
428        assert_eq!(char::from(OrderSide::Buy), '1');
429        assert_eq!(char::from(OrderSide::Sell), '2');
430
431        assert_eq!(OrderSide::try_from('1').unwrap(), OrderSide::Buy);
432        assert_eq!(OrderSide::try_from('2').unwrap(), OrderSide::Sell);
433        assert!(OrderSide::try_from('3').is_err());
434    }
435
436    #[test]
437    fn test_order_type_conversion() {
438        assert_eq!(char::from(OrderType::Market), '1');
439        assert_eq!(char::from(OrderType::Limit), '2');
440        assert_eq!(char::from(OrderType::MarketLimit), 'K');
441
442        assert_eq!(OrderType::try_from('1').unwrap(), OrderType::Market);
443        assert_eq!(OrderType::try_from('2').unwrap(), OrderType::Limit);
444        assert_eq!(OrderType::try_from('K').unwrap(), OrderType::MarketLimit);
445        assert!(OrderType::try_from('Z').is_err());
446    }
447
448    #[test]
449    fn test_time_in_force_conversion() {
450        assert_eq!(char::from(TimeInForce::GoodTillDay), '0');
451        assert_eq!(char::from(TimeInForce::GoodTillCancelled), '1');
452        assert_eq!(char::from(TimeInForce::ImmediateOrCancel), '3');
453        assert_eq!(char::from(TimeInForce::FillOrKill), '4');
454
455        assert_eq!(
456            TimeInForce::try_from('0').unwrap(),
457            TimeInForce::GoodTillDay
458        );
459        assert_eq!(
460            TimeInForce::try_from('1').unwrap(),
461            TimeInForce::GoodTillCancelled
462        );
463        assert_eq!(
464            TimeInForce::try_from('3').unwrap(),
465            TimeInForce::ImmediateOrCancel
466        );
467        assert_eq!(TimeInForce::try_from('4').unwrap(), TimeInForce::FillOrKill);
468        assert!(TimeInForce::try_from('5').is_err());
469    }
470
471    #[test]
472    fn test_order_status_conversion() {
473        assert_eq!(char::from(OrderStatus::New), '0');
474        assert_eq!(char::from(OrderStatus::PartiallyFilled), '1');
475        assert_eq!(char::from(OrderStatus::Filled), '2');
476        assert_eq!(char::from(OrderStatus::Cancelled), '4');
477
478        assert_eq!(OrderStatus::try_from('0').unwrap(), OrderStatus::New);
479        assert_eq!(
480            OrderStatus::try_from('1').unwrap(),
481            OrderStatus::PartiallyFilled
482        );
483        assert_eq!(OrderStatus::try_from('2').unwrap(), OrderStatus::Filled);
484        assert_eq!(OrderStatus::try_from('4').unwrap(), OrderStatus::Cancelled);
485        assert!(OrderStatus::try_from('9').is_err());
486    }
487
488    #[test]
489    fn test_order_reject_reason_conversion() {
490        assert_eq!(i32::from(OrderRejectReason::NoReject), 0);
491        assert_eq!(i32::from(OrderRejectReason::UnknownSymbol), 1);
492        assert_eq!(i32::from(OrderRejectReason::Other), 99);
493
494        assert_eq!(
495            OrderRejectReason::try_from(0).unwrap(),
496            OrderRejectReason::NoReject
497        );
498        assert_eq!(
499            OrderRejectReason::try_from(1).unwrap(),
500            OrderRejectReason::UnknownSymbol
501        );
502        assert_eq!(
503            OrderRejectReason::try_from(99).unwrap(),
504            OrderRejectReason::Other
505        );
506        assert!(OrderRejectReason::try_from(100).is_err());
507    }
508
509    #[test]
510    fn test_mass_cancel_request_type_conversion() {
511        assert_eq!(i32::from(MassCancelRequestType::BySymbol), 1);
512        assert_eq!(i32::from(MassCancelRequestType::BySecurityType), 5);
513        assert_eq!(i32::from(MassCancelRequestType::AllOrders), 7);
514        assert_eq!(i32::from(MassCancelRequestType::ByDeribitLabel), 10);
515
516        assert_eq!(
517            MassCancelRequestType::try_from(1).unwrap(),
518            MassCancelRequestType::BySymbol
519        );
520        assert_eq!(
521            MassCancelRequestType::try_from(5).unwrap(),
522            MassCancelRequestType::BySecurityType
523        );
524        assert_eq!(
525            MassCancelRequestType::try_from(7).unwrap(),
526            MassCancelRequestType::AllOrders
527        );
528        assert_eq!(
529            MassCancelRequestType::try_from(10).unwrap(),
530            MassCancelRequestType::ByDeribitLabel
531        );
532        assert!(MassCancelRequestType::try_from(99).is_err());
533    }
534
535    #[test]
536    fn test_quantity_type_conversion() {
537        assert_eq!(i32::from(QuantityType::Units), 0);
538        assert_eq!(i32::from(QuantityType::Contracts), 1);
539
540        assert_eq!(QuantityType::try_from(0).unwrap(), QuantityType::Units);
541        assert_eq!(QuantityType::try_from(1).unwrap(), QuantityType::Contracts);
542        assert!(QuantityType::try_from(2).is_err());
543    }
544
545    #[test]
546    fn test_mass_status_request_type_conversion() {
547        assert_eq!(i32::from(MassStatusRequestType::SpecificOrder), 1);
548        assert_eq!(i32::from(MassStatusRequestType::AllOrders), 7);
549
550        assert_eq!(
551            MassStatusRequestType::try_from(1).unwrap(),
552            MassStatusRequestType::SpecificOrder
553        );
554        assert_eq!(
555            MassStatusRequestType::try_from(7).unwrap(),
556            MassStatusRequestType::AllOrders
557        );
558        assert!(MassStatusRequestType::try_from(99).is_err());
559    }
560
561    #[test]
562    fn test_mass_status_request_id_type_conversion() {
563        assert_eq!(i32::from(MassStatusRequestIdType::OrigClOrdId), 0);
564        assert_eq!(i32::from(MassStatusRequestIdType::ClOrdId), 1);
565        assert_eq!(i32::from(MassStatusRequestIdType::DeribitLabel), 2);
566
567        assert_eq!(
568            MassStatusRequestIdType::try_from(0).unwrap(),
569            MassStatusRequestIdType::OrigClOrdId
570        );
571        assert_eq!(
572            MassStatusRequestIdType::try_from(1).unwrap(),
573            MassStatusRequestIdType::ClOrdId
574        );
575        assert_eq!(
576            MassStatusRequestIdType::try_from(2).unwrap(),
577            MassStatusRequestIdType::DeribitLabel
578        );
579        assert!(MassStatusRequestIdType::try_from(99).is_err());
580    }
581}