1#![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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct SecurityStatusRequest {
23 pub security_status_req_id: String,
25 pub symbol: String,
27 pub subscription_request_type: char,
30}
31
32impl SecurityStatusRequest {
33 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 pub fn snapshot(security_status_req_id: String, symbol: String) -> Self {
48 Self::new(security_status_req_id, symbol, '0')
49 }
50
51 pub fn subscribe(security_status_req_id: String, symbol: String) -> Self {
53 Self::new(security_status_req_id, symbol, '1')
54 }
55
56 pub fn unsubscribe(security_status_req_id: String, symbol: String) -> Self {
58 Self::new(security_status_req_id, symbol, '2')
59 }
60
61 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()) .field(55, self.symbol.clone()) .field(263, self.subscription_request_type.to_string()); builder.build()
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct SecurityStatus {
87 pub security_status_req_id: Option<String>,
89 pub symbol: String,
91 pub security_trading_status: Option<i32>,
94 pub buy_volume: Option<f64>,
96 pub sell_volume: Option<f64>,
98 pub high_px: Option<f64>,
100 pub low_px: Option<f64>,
102 pub last_px: Option<f64>,
104 pub text: Option<String>,
106}
107
108impl SecurityStatus {
109 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 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 pub fn with_trading_status(mut self, status: i32) -> Self {
132 self.security_trading_status = Some(status);
133 self
134 }
135
136 pub fn with_buy_volume(mut self, volume: f64) -> Self {
138 self.buy_volume = Some(volume);
139 self
140 }
141
142 pub fn with_sell_volume(mut self, volume: f64) -> Self {
144 self.sell_volume = Some(volume);
145 self
146 }
147
148 pub fn with_high_px(mut self, price: f64) -> Self {
150 self.high_px = Some(price);
151 self
152 }
153
154 pub fn with_low_px(mut self, price: f64) -> Self {
156 self.low_px = Some(price);
157 self
158 }
159
160 pub fn with_last_px(mut self, price: f64) -> Self {
162 self.last_px = Some(price);
163 self
164 }
165
166 pub fn with_text(mut self, text: String) -> Self {
168 self.text = Some(text);
169 self
170 }
171
172 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 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 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()); if let Some(ref req_id) = self.security_status_req_id {
245 builder = builder.field(324, req_id.clone()); }
247
248 if let Some(status) = self.security_trading_status {
249 builder = builder.field(326, status.to_string()); }
251
252 if let Some(buy_vol) = self.buy_volume {
253 builder = builder.field(330, buy_vol.to_string()); }
255
256 if let Some(sell_vol) = self.sell_volume {
257 builder = builder.field(331, sell_vol.to_string()); }
259
260 if let Some(high) = self.high_px {
261 builder = builder.field(332, high.to_string()); }
263
264 if let Some(low) = self.low_px {
265 builder = builder.field(333, low.to_string()); }
267
268 if let Some(last) = self.last_px {
269 builder = builder.field(31, last.to_string()); }
271
272 if let Some(ref text) = self.text {
273 builder = builder.field(58, text.clone()); }
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"); assert_eq!(fix_msg.get_field(324).unwrap(), "REQ123"); assert_eq!(fix_msg.get_field(55).unwrap(), "BTC-PERPETUAL"); assert_eq!(fix_msg.get_field(263).unwrap(), "0"); }
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"); assert_eq!(fix_msg.get_field(55).unwrap(), "BTC-PERPETUAL"); assert_eq!(fix_msg.get_field(324).unwrap(), "REQ123"); assert_eq!(fix_msg.get_field(326).unwrap(), "7"); assert_eq!(fix_msg.get_field(31).unwrap(), "49500"); }
360}