deribit_fix/message/
security_status.rs

1//! Security Status Request and Security Status messages
2//!
3//! This module implements Security Status Request (35=e) and Security Status (35=f) messages
4//! for requesting and receiving security status information from the Deribit FIX API.
5//!
6//! Security Status Request allows clients to request the current status of a security,
7//! while Security Status provides the response with trading status and market data.
8
9#![warn(missing_docs)]
10
11use crate::error::{DeribitFixError, Result as DeribitFixResult};
12use crate::message::MessageBuilder;
13use crate::model::message::FixMessage;
14use crate::model::types::MsgType;
15use serde::{Deserialize, Serialize};
16
17/// Security Status Request message (35=e)
18///
19/// Provides the ability to request the status of a security including
20/// trading status, volumes, and price information.
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct SecurityStatusRequest {
23    /// ID of the request (tag 324)
24    pub security_status_req_id: String,
25    /// Instrument symbol (tag 55)
26    pub symbol: String,
27    /// Subscription request type (tag 263)
28    /// 0 = Snapshot, 1 = Snapshot + Updates (Subscribe), 2 = Unsubscribe
29    pub subscription_request_type: char,
30}
31
32impl SecurityStatusRequest {
33    /// Create a new Security Status Request
34    pub fn new(
35        security_status_req_id: String,
36        symbol: String,
37        subscription_request_type: char,
38    ) -> Self {
39        Self {
40            security_status_req_id,
41            symbol,
42            subscription_request_type,
43        }
44    }
45
46    /// Create a snapshot request (no subscription)
47    pub fn snapshot(security_status_req_id: String, symbol: String) -> Self {
48        Self::new(security_status_req_id, symbol, '0')
49    }
50
51    /// Create a subscription request (snapshot + updates)
52    pub fn subscribe(security_status_req_id: String, symbol: String) -> Self {
53        Self::new(security_status_req_id, symbol, '1')
54    }
55
56    /// Create an unsubscribe request
57    pub fn unsubscribe(security_status_req_id: String, symbol: String) -> Self {
58        Self::new(security_status_req_id, symbol, '2')
59    }
60
61    /// Convert to FIX message
62    pub fn to_fix_message(
63        &self,
64        sender_comp_id: String,
65        target_comp_id: String,
66        msg_seq_num: u32,
67    ) -> DeribitFixResult<FixMessage> {
68        let builder = MessageBuilder::new()
69            .msg_type(MsgType::SecurityStatusRequest)
70            .sender_comp_id(sender_comp_id)
71            .target_comp_id(target_comp_id)
72            .msg_seq_num(msg_seq_num)
73            .field(324, self.security_status_req_id.clone()) // SecurityStatusReqID
74            .field(55, self.symbol.clone()) // Symbol
75            .field(263, self.subscription_request_type.to_string()); // SubscriptionRequestType
76
77        builder.build()
78    }
79}
80
81/// Security Status message (35=f)
82///
83/// Provides information about the current status of a security including
84/// trading status, volumes, and various price metrics.
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct SecurityStatus {
87    /// ID of the request (tag 324) - optional in response
88    pub security_status_req_id: Option<String>,
89    /// Instrument symbol (tag 55)
90    pub symbol: String,
91    /// Security trading status (tag 326)
92    /// 7 = Ready to trade, 8 = Not Available for trading, 20 = Unknown or Invalid
93    pub security_trading_status: Option<i32>,
94    /// Volume in buy contracts (tag 330)
95    pub buy_volume: Option<f64>,
96    /// Volume in sell contracts (tag 331)
97    pub sell_volume: Option<f64>,
98    /// Price of the 24h highest trade (tag 332)
99    pub high_px: Option<f64>,
100    /// Price of the 24h lowest trade (tag 333)
101    pub low_px: Option<f64>,
102    /// The price of the latest trade (tag 31)
103    pub last_px: Option<f64>,
104    /// Explanatory text (tag 58)
105    pub text: Option<String>,
106}
107
108impl SecurityStatus {
109    /// Create a new Security Status message
110    pub fn new(symbol: String) -> Self {
111        Self {
112            security_status_req_id: None,
113            symbol,
114            security_trading_status: None,
115            buy_volume: None,
116            sell_volume: None,
117            high_px: None,
118            low_px: None,
119            last_px: None,
120            text: None,
121        }
122    }
123
124    /// Set the security status request ID
125    pub fn with_security_status_req_id(mut self, req_id: String) -> Self {
126        self.security_status_req_id = Some(req_id);
127        self
128    }
129
130    /// Set the trading status
131    pub fn with_trading_status(mut self, status: i32) -> Self {
132        self.security_trading_status = Some(status);
133        self
134    }
135
136    /// Set buy volume
137    pub fn with_buy_volume(mut self, volume: f64) -> Self {
138        self.buy_volume = Some(volume);
139        self
140    }
141
142    /// Set sell volume
143    pub fn with_sell_volume(mut self, volume: f64) -> Self {
144        self.sell_volume = Some(volume);
145        self
146    }
147
148    /// Set high price
149    pub fn with_high_px(mut self, price: f64) -> Self {
150        self.high_px = Some(price);
151        self
152    }
153
154    /// Set low price
155    pub fn with_low_px(mut self, price: f64) -> Self {
156        self.low_px = Some(price);
157        self
158    }
159
160    /// Set last price
161    pub fn with_last_px(mut self, price: f64) -> Self {
162        self.last_px = Some(price);
163        self
164    }
165
166    /// Set explanatory text
167    pub fn with_text(mut self, text: String) -> Self {
168        self.text = Some(text);
169        self
170    }
171
172    /// Parse from FIX message
173    pub fn from_fix_message(message: &FixMessage) -> DeribitFixResult<Self> {
174        let symbol = message
175            .get_field(55)
176            .ok_or_else(|| DeribitFixError::MessageParsing("Symbol (55) is required".to_string()))?
177            .clone();
178
179        let mut security_status = Self::new(symbol);
180
181        // Optional fields
182        if let Some(req_id) = message.get_field(324) {
183            security_status.security_status_req_id = Some(req_id.clone());
184        }
185
186        if let Some(status_str) = message.get_field(326)
187            && let Ok(status) = status_str.parse::<i32>()
188        {
189            security_status.security_trading_status = Some(status);
190        }
191
192        if let Some(buy_vol_str) = message.get_field(330)
193            && let Ok(buy_vol) = buy_vol_str.parse::<f64>()
194        {
195            security_status.buy_volume = Some(buy_vol);
196        }
197
198        if let Some(sell_vol_str) = message.get_field(331)
199            && let Ok(sell_vol) = sell_vol_str.parse::<f64>()
200        {
201            security_status.sell_volume = Some(sell_vol);
202        }
203
204        if let Some(high_str) = message.get_field(332)
205            && let Ok(high) = high_str.parse::<f64>()
206        {
207            security_status.high_px = Some(high);
208        }
209
210        if let Some(low_str) = message.get_field(333)
211            && let Ok(low) = low_str.parse::<f64>()
212        {
213            security_status.low_px = Some(low);
214        }
215
216        if let Some(last_str) = message.get_field(31)
217            && let Ok(last) = last_str.parse::<f64>()
218        {
219            security_status.last_px = Some(last);
220        }
221
222        if let Some(text) = message.get_field(58) {
223            security_status.text = Some(text.clone());
224        }
225
226        Ok(security_status)
227    }
228
229    /// Convert to FIX message
230    pub fn to_fix_message(
231        &self,
232        sender_comp_id: String,
233        target_comp_id: String,
234        msg_seq_num: u32,
235    ) -> DeribitFixResult<FixMessage> {
236        let mut builder = MessageBuilder::new()
237            .msg_type(MsgType::SecurityStatus)
238            .sender_comp_id(sender_comp_id)
239            .target_comp_id(target_comp_id)
240            .msg_seq_num(msg_seq_num)
241            .field(55, self.symbol.clone()); // Symbol
242
243        // Optional fields
244        if let Some(ref req_id) = self.security_status_req_id {
245            builder = builder.field(324, req_id.clone()); // SecurityStatusReqID
246        }
247
248        if let Some(status) = self.security_trading_status {
249            builder = builder.field(326, status.to_string()); // SecurityTradingStatus
250        }
251
252        if let Some(buy_vol) = self.buy_volume {
253            builder = builder.field(330, buy_vol.to_string()); // BuyVolume
254        }
255
256        if let Some(sell_vol) = self.sell_volume {
257            builder = builder.field(331, sell_vol.to_string()); // SellVolume
258        }
259
260        if let Some(high) = self.high_px {
261            builder = builder.field(332, high.to_string()); // HighPx
262        }
263
264        if let Some(low) = self.low_px {
265            builder = builder.field(333, low.to_string()); // LowPx
266        }
267
268        if let Some(last) = self.last_px {
269            builder = builder.field(31, last.to_string()); // LastPx
270        }
271
272        if let Some(ref text) = self.text {
273            builder = builder.field(58, text.clone()); // Text
274        }
275
276        builder.build()
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_security_status_request_creation() {
286        let req =
287            SecurityStatusRequest::new("REQ123".to_string(), "BTC-PERPETUAL".to_string(), '0');
288        assert_eq!(req.security_status_req_id, "REQ123");
289        assert_eq!(req.symbol, "BTC-PERPETUAL");
290        assert_eq!(req.subscription_request_type, '0');
291    }
292
293    #[test]
294    fn test_security_status_request_builders() {
295        let snapshot =
296            SecurityStatusRequest::snapshot("REQ1".to_string(), "BTC-PERPETUAL".to_string());
297        assert_eq!(snapshot.subscription_request_type, '0');
298
299        let subscribe =
300            SecurityStatusRequest::subscribe("REQ2".to_string(), "BTC-PERPETUAL".to_string());
301        assert_eq!(subscribe.subscription_request_type, '1');
302
303        let unsubscribe =
304            SecurityStatusRequest::unsubscribe("REQ3".to_string(), "BTC-PERPETUAL".to_string());
305        assert_eq!(unsubscribe.subscription_request_type, '2');
306    }
307
308    #[test]
309    fn test_security_status_request_to_fix_message() {
310        let req =
311            SecurityStatusRequest::snapshot("REQ123".to_string(), "BTC-PERPETUAL".to_string());
312        let fix_msg = req
313            .to_fix_message("CLIENT".to_string(), "DERIBIT".to_string(), 1)
314            .unwrap();
315
316        assert_eq!(fix_msg.get_field(35).unwrap(), "e"); // MsgType
317        assert_eq!(fix_msg.get_field(324).unwrap(), "REQ123"); // SecurityStatusReqID
318        assert_eq!(fix_msg.get_field(55).unwrap(), "BTC-PERPETUAL"); // Symbol
319        assert_eq!(fix_msg.get_field(263).unwrap(), "0"); // SubscriptionRequestType
320    }
321
322    #[test]
323    fn test_security_status_creation() {
324        let status = SecurityStatus::new("BTC-PERPETUAL".to_string())
325            .with_trading_status(7)
326            .with_buy_volume(100.0)
327            .with_sell_volume(200.0)
328            .with_high_px(50000.0)
329            .with_low_px(49000.0)
330            .with_last_px(49500.0)
331            .with_text("success".to_string());
332
333        assert_eq!(status.symbol, "BTC-PERPETUAL");
334        assert_eq!(status.security_trading_status, Some(7));
335        assert_eq!(status.buy_volume, Some(100.0));
336        assert_eq!(status.sell_volume, Some(200.0));
337        assert_eq!(status.high_px, Some(50000.0));
338        assert_eq!(status.low_px, Some(49000.0));
339        assert_eq!(status.last_px, Some(49500.0));
340        assert_eq!(status.text, Some("success".to_string()));
341    }
342
343    #[test]
344    fn test_security_status_to_fix_message() {
345        let status = SecurityStatus::new("BTC-PERPETUAL".to_string())
346            .with_security_status_req_id("REQ123".to_string())
347            .with_trading_status(7)
348            .with_last_px(49500.0);
349
350        let fix_msg = status
351            .to_fix_message("DERIBIT".to_string(), "CLIENT".to_string(), 2)
352            .unwrap();
353
354        assert_eq!(fix_msg.get_field(35).unwrap(), "f"); // MsgType
355        assert_eq!(fix_msg.get_field(55).unwrap(), "BTC-PERPETUAL"); // Symbol
356        assert_eq!(fix_msg.get_field(324).unwrap(), "REQ123"); // SecurityStatusReqID
357        assert_eq!(fix_msg.get_field(326).unwrap(), "7"); // SecurityTradingStatus
358        assert_eq!(fix_msg.get_field(31).unwrap(), "49500"); // LastPx
359    }
360}