1use crate::error::{DeribitFixError, Result as DeribitFixResult};
15use crate::message::MessageBuilder;
16use crate::model::message::FixMessage;
17use crate::model::types::MsgType;
18use chrono::{DateTime, Utc};
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub enum SecurityDefinitionRequestType {
24 RequestSecurityIdentityAndSpecs,
26 RequestSecurityIdentityForSpecs,
28 RequestListSecurityTypes,
30 RequestListSecurities,
32}
33
34impl From<SecurityDefinitionRequestType> for i32 {
35 fn from(request_type: SecurityDefinitionRequestType) -> Self {
36 match request_type {
37 SecurityDefinitionRequestType::RequestSecurityIdentityAndSpecs => 0,
38 SecurityDefinitionRequestType::RequestSecurityIdentityForSpecs => 1,
39 SecurityDefinitionRequestType::RequestListSecurityTypes => 2,
40 SecurityDefinitionRequestType::RequestListSecurities => 3,
41 }
42 }
43}
44
45impl TryFrom<i32> for SecurityDefinitionRequestType {
46 type Error = DeribitFixError;
47
48 fn try_from(value: i32) -> Result<Self, Self::Error> {
49 match value {
50 0 => Ok(SecurityDefinitionRequestType::RequestSecurityIdentityAndSpecs),
51 1 => Ok(SecurityDefinitionRequestType::RequestSecurityIdentityForSpecs),
52 2 => Ok(SecurityDefinitionRequestType::RequestListSecurityTypes),
53 3 => Ok(SecurityDefinitionRequestType::RequestListSecurities),
54 _ => Err(DeribitFixError::MessageParsing(format!(
55 "Invalid SecurityDefinitionRequestType: {}",
56 value
57 ))),
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct SecurityDefinitionRequest {
65 pub security_req_id: String,
67 pub request_type: SecurityDefinitionRequestType,
69 pub symbol: Option<String>,
71 pub security_type: Option<String>,
73 pub currency: Option<String>,
75 pub text: Option<String>,
77 pub subscription_request_type: Option<i32>,
79}
80
81impl SecurityDefinitionRequest {
82 pub fn new(security_req_id: String, request_type: SecurityDefinitionRequestType) -> Self {
84 Self {
85 security_req_id,
86 request_type,
87 symbol: None,
88 security_type: None,
89 currency: None,
90 text: None,
91 subscription_request_type: None,
92 }
93 }
94
95 pub fn request_identity_and_specs(security_req_id: String) -> Self {
97 Self::new(
98 security_req_id,
99 SecurityDefinitionRequestType::RequestSecurityIdentityAndSpecs,
100 )
101 }
102
103 pub fn request_list_securities(security_req_id: String) -> Self {
105 Self::new(
106 security_req_id,
107 SecurityDefinitionRequestType::RequestListSecurities,
108 )
109 }
110
111 pub fn with_symbol(mut self, symbol: String) -> Self {
113 self.symbol = Some(symbol);
114 self
115 }
116
117 pub fn with_security_type(mut self, security_type: String) -> Self {
119 self.security_type = Some(security_type);
120 self
121 }
122
123 pub fn with_currency(mut self, currency: String) -> Self {
125 self.currency = Some(currency);
126 self
127 }
128
129 pub fn with_text(mut self, text: String) -> Self {
131 self.text = Some(text);
132 self
133 }
134
135 pub fn with_subscription_request_type(mut self, subscription_type: i32) -> Self {
137 self.subscription_request_type = Some(subscription_type);
138 self
139 }
140
141 pub fn to_fix_message(
143 &self,
144 sender_comp_id: String,
145 target_comp_id: String,
146 msg_seq_num: u32,
147 ) -> DeribitFixResult<FixMessage> {
148 let mut builder = MessageBuilder::new()
149 .msg_type(MsgType::SecurityDefinitionRequest)
150 .sender_comp_id(sender_comp_id)
151 .target_comp_id(target_comp_id)
152 .msg_seq_num(msg_seq_num)
153 .field(320, self.security_req_id.clone()) .field(856, i32::from(self.request_type).to_string()); if let Some(ref symbol) = self.symbol {
158 builder = builder.field(55, symbol.clone()); }
160
161 if let Some(ref security_type) = self.security_type {
162 builder = builder.field(167, security_type.clone()); }
164
165 if let Some(ref currency) = self.currency {
166 builder = builder.field(15, currency.clone()); }
168
169 if let Some(ref text) = self.text {
170 builder = builder.field(58, text.clone()); }
172
173 if let Some(subscription_type) = self.subscription_request_type {
174 builder = builder.field(263, subscription_type.to_string()); }
176
177 builder.build()
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct SecurityDefinition {
184 pub security_req_id: String,
186 pub security_response_id: String,
188 pub symbol: String,
190 pub security_type: Option<String>,
192 pub currency: Option<String>,
194 pub security_desc: Option<String>,
196 pub strike_price: Option<f64>,
198 pub strike_currency: Option<String>,
200 pub put_or_call: Option<i32>,
202 pub contract_multiplier: Option<f64>,
204 pub maturity_date: Option<String>,
206 pub issue_date: Option<String>,
208 pub min_trade_vol: Option<f64>,
210 pub security_def_response_type: Option<i32>,
212 pub last_update_time: Option<DateTime<Utc>>,
214}
215
216impl SecurityDefinition {
217 pub fn new(security_req_id: String, security_response_id: String, symbol: String) -> Self {
219 Self {
220 security_req_id,
221 security_response_id,
222 symbol,
223 security_type: None,
224 currency: None,
225 security_desc: None,
226 strike_price: None,
227 strike_currency: None,
228 put_or_call: None,
229 contract_multiplier: None,
230 maturity_date: None,
231 issue_date: None,
232 min_trade_vol: None,
233 security_def_response_type: None,
234 last_update_time: Some(Utc::now()),
235 }
236 }
237
238 pub fn from_fix_message(message: &FixMessage) -> DeribitFixResult<Self> {
240 let security_req_id = message
241 .get_field(320)
242 .ok_or_else(|| {
243 DeribitFixError::MessageParsing("Missing SecurityReqID (320)".to_string())
244 })?
245 .clone();
246
247 let security_response_id = message
248 .get_field(322)
249 .ok_or_else(|| {
250 DeribitFixError::MessageParsing("Missing SecurityResponseID (322)".to_string())
251 })?
252 .clone();
253
254 let symbol = message
255 .get_field(55)
256 .ok_or_else(|| DeribitFixError::MessageParsing("Missing Symbol (55)".to_string()))?
257 .clone();
258
259 let security_type = message.get_field(167).cloned();
260 let currency = message.get_field(15).cloned();
261 let security_desc = message.get_field(107).cloned();
262
263 let strike_price = message.get_field(202).and_then(|s| s.parse::<f64>().ok());
264
265 let strike_currency = message.get_field(947).cloned();
266
267 let put_or_call = message.get_field(201).and_then(|s| s.parse::<i32>().ok());
268
269 let contract_multiplier = message.get_field(231).and_then(|s| s.parse::<f64>().ok());
270
271 let maturity_date = message.get_field(541).cloned();
272 let issue_date = message.get_field(225).cloned();
273
274 let min_trade_vol = message.get_field(562).and_then(|s| s.parse::<f64>().ok());
275
276 let security_def_response_type =
277 message.get_field(1570).and_then(|s| s.parse::<i32>().ok());
278
279 Ok(Self {
280 security_req_id,
281 security_response_id,
282 symbol,
283 security_type,
284 currency,
285 security_desc,
286 strike_price,
287 strike_currency,
288 put_or_call,
289 contract_multiplier,
290 maturity_date,
291 issue_date,
292 min_trade_vol,
293 security_def_response_type,
294 last_update_time: Some(Utc::now()),
295 })
296 }
297
298 pub fn to_fix_message(
300 &self,
301 sender_comp_id: String,
302 target_comp_id: String,
303 msg_seq_num: u32,
304 ) -> DeribitFixResult<FixMessage> {
305 let mut builder = MessageBuilder::new()
306 .msg_type(MsgType::SecurityDefinition)
307 .sender_comp_id(sender_comp_id)
308 .target_comp_id(target_comp_id)
309 .msg_seq_num(msg_seq_num)
310 .field(320, self.security_req_id.clone()) .field(322, self.security_response_id.clone()) .field(55, self.symbol.clone()); if let Some(ref security_type) = self.security_type {
316 builder = builder.field(167, security_type.clone()); }
318
319 if let Some(ref currency) = self.currency {
320 builder = builder.field(15, currency.clone()); }
322
323 if let Some(ref security_desc) = self.security_desc {
324 builder = builder.field(107, security_desc.clone()); }
326
327 if let Some(strike_price) = self.strike_price {
328 builder = builder.field(202, strike_price.to_string()); }
330
331 if let Some(ref strike_currency) = self.strike_currency {
332 builder = builder.field(947, strike_currency.clone()); }
334
335 if let Some(put_or_call) = self.put_or_call {
336 builder = builder.field(201, put_or_call.to_string()); }
338
339 if let Some(contract_multiplier) = self.contract_multiplier {
340 builder = builder.field(231, contract_multiplier.to_string()); }
342
343 if let Some(ref maturity_date) = self.maturity_date {
344 builder = builder.field(541, maturity_date.clone()); }
346
347 if let Some(ref issue_date) = self.issue_date {
348 builder = builder.field(225, issue_date.clone()); }
350
351 if let Some(min_trade_vol) = self.min_trade_vol {
352 builder = builder.field(562, min_trade_vol.to_string()); }
354
355 if let Some(response_type) = self.security_def_response_type {
356 builder = builder.field(1570, response_type.to_string()); }
358
359 builder.build()
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_security_definition_request_type_conversion() {
369 assert_eq!(
370 i32::from(SecurityDefinitionRequestType::RequestSecurityIdentityAndSpecs),
371 0
372 );
373 assert_eq!(
374 i32::from(SecurityDefinitionRequestType::RequestSecurityIdentityForSpecs),
375 1
376 );
377
378 assert_eq!(
379 SecurityDefinitionRequestType::try_from(0).unwrap(),
380 SecurityDefinitionRequestType::RequestSecurityIdentityAndSpecs
381 );
382 assert_eq!(
383 SecurityDefinitionRequestType::try_from(1).unwrap(),
384 SecurityDefinitionRequestType::RequestSecurityIdentityForSpecs
385 );
386
387 assert!(SecurityDefinitionRequestType::try_from(99).is_err());
388 }
389
390 #[test]
391 fn test_security_definition_request_creation() {
392 let request =
393 SecurityDefinitionRequest::request_identity_and_specs("SECDEF_123".to_string());
394 assert_eq!(request.security_req_id, "SECDEF_123");
395 assert_eq!(
396 request.request_type,
397 SecurityDefinitionRequestType::RequestSecurityIdentityAndSpecs
398 );
399 }
400
401 #[test]
402 fn test_security_definition_request_with_filters() {
403 let request = SecurityDefinitionRequest::request_list_securities("SECDEF_456".to_string())
404 .with_symbol("BTC-PERPETUAL".to_string())
405 .with_currency("USD".to_string())
406 .with_security_type("FUT".to_string());
407
408 assert_eq!(request.security_req_id, "SECDEF_456");
409 assert_eq!(request.symbol, Some("BTC-PERPETUAL".to_string()));
410 assert_eq!(request.currency, Some("USD".to_string()));
411 assert_eq!(request.security_type, Some("FUT".to_string()));
412 }
413
414 #[test]
415 fn test_security_definition_request_to_fix_message() {
416 let request =
417 SecurityDefinitionRequest::request_identity_and_specs("SECDEF_789".to_string())
418 .with_symbol("ETH-PERPETUAL".to_string());
419
420 let fix_message = request
421 .to_fix_message("SENDER".to_string(), "TARGET".to_string(), 1)
422 .unwrap();
423
424 assert_eq!(fix_message.get_field(35), Some(&"c".to_string())); assert_eq!(fix_message.get_field(320), Some(&"SECDEF_789".to_string())); assert_eq!(fix_message.get_field(856), Some(&"0".to_string())); assert_eq!(
428 fix_message.get_field(55),
429 Some(&"ETH-PERPETUAL".to_string())
430 ); }
432
433 #[test]
434 fn test_security_definition_creation() {
435 let definition = SecurityDefinition::new(
436 "SECDEF_123".to_string(),
437 "RESP_456".to_string(),
438 "BTC-PERPETUAL".to_string(),
439 );
440
441 assert_eq!(definition.security_req_id, "SECDEF_123");
442 assert_eq!(definition.security_response_id, "RESP_456");
443 assert_eq!(definition.symbol, "BTC-PERPETUAL");
444 assert!(definition.last_update_time.is_some());
445 }
446
447 #[test]
448 fn test_security_definition_from_fix_message() {
449 let mut fix_message = FixMessage::new();
450 fix_message.set_field(320, "SECDEF_123".to_string());
451 fix_message.set_field(322, "RESP_456".to_string());
452 fix_message.set_field(55, "BTC-PERPETUAL".to_string());
453 fix_message.set_field(167, "FUT".to_string());
454 fix_message.set_field(15, "USD".to_string());
455 fix_message.set_field(107, "Bitcoin Perpetual Future".to_string());
456
457 let definition = SecurityDefinition::from_fix_message(&fix_message).unwrap();
458
459 assert_eq!(definition.security_req_id, "SECDEF_123");
460 assert_eq!(definition.security_response_id, "RESP_456");
461 assert_eq!(definition.symbol, "BTC-PERPETUAL");
462 assert_eq!(definition.security_type, Some("FUT".to_string()));
463 assert_eq!(definition.currency, Some("USD".to_string()));
464 assert_eq!(
465 definition.security_desc,
466 Some("Bitcoin Perpetual Future".to_string())
467 );
468 }
469
470 #[test]
471 fn test_security_definition_to_fix_message() {
472 let definition = SecurityDefinition::new(
473 "SECDEF_123".to_string(),
474 "RESP_456".to_string(),
475 "BTC-PERPETUAL".to_string(),
476 );
477
478 let fix_message = definition
479 .to_fix_message("SENDER".to_string(), "TARGET".to_string(), 1)
480 .unwrap();
481
482 assert_eq!(fix_message.get_field(35), Some(&"d".to_string())); assert_eq!(fix_message.get_field(320), Some(&"SECDEF_123".to_string())); assert_eq!(fix_message.get_field(322), Some(&"RESP_456".to_string())); assert_eq!(
486 fix_message.get_field(55),
487 Some(&"BTC-PERPETUAL".to_string())
488 ); }
490}