1use crate::error::Result as DeribitFixResult;
14use crate::message::MessageBuilder;
15use crate::model::types::MsgType;
16use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum SecurityListRequestType {
22 Snapshot = 0,
24 SnapshotAndUpdates = 4,
26}
27
28impl From<SecurityListRequestType> for i32 {
29 fn from(request_type: SecurityListRequestType) -> Self {
30 request_type as i32
31 }
32}
33
34impl TryFrom<i32> for SecurityListRequestType {
35 type Error = String;
36
37 fn try_from(value: i32) -> Result<Self, Self::Error> {
38 match value {
39 0 => Ok(SecurityListRequestType::Snapshot),
40 4 => Ok(SecurityListRequestType::SnapshotAndUpdates),
41 _ => Err(format!("Invalid SecurityListRequestType: {value}")),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum SubscriptionRequestType {
49 Snapshot = 0,
51 SnapshotPlusUpdates = 1,
53 Unsubscribe = 2,
55}
56
57impl From<SubscriptionRequestType> for i32 {
58 fn from(request_type: SubscriptionRequestType) -> Self {
59 request_type as i32
60 }
61}
62
63impl TryFrom<i32> for SubscriptionRequestType {
64 type Error = String;
65
66 fn try_from(value: i32) -> Result<Self, Self::Error> {
67 match value {
68 0 => Ok(SubscriptionRequestType::Snapshot),
69 1 => Ok(SubscriptionRequestType::SnapshotPlusUpdates),
70 2 => Ok(SubscriptionRequestType::Unsubscribe),
71 _ => Err(format!("Invalid SubscriptionRequestType: {value}")),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78pub enum SecurityType {
79 FxSpot,
81 Future,
83 Option,
85 FutureCombo,
87 OptionCombo,
89 Index,
91}
92
93impl SecurityType {
94 pub fn as_fix_str(&self) -> &'static str {
96 match self {
97 SecurityType::FxSpot => "FXSPOT",
98 SecurityType::Future => "FUT",
99 SecurityType::Option => "OPT",
100 SecurityType::FutureCombo => "FUTCO",
101 SecurityType::OptionCombo => "OPTCO",
102 SecurityType::Index => "INDEX",
103 }
104 }
105
106 pub fn from_fix_str(s: &str) -> Result<Self, String> {
108 match s {
109 "FXSPOT" => Ok(SecurityType::FxSpot),
110 "FUT" => Ok(SecurityType::Future),
111 "OPT" => Ok(SecurityType::Option),
112 "FUTCO" => Ok(SecurityType::FutureCombo),
113 "OPTCO" => Ok(SecurityType::OptionCombo),
114 "INDEX" => Ok(SecurityType::Index),
115 _ => Err(format!("Invalid SecurityType: {s}")),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct SecurityListRequest {
123 pub security_req_id: String,
125 pub security_list_request_type: SecurityListRequestType,
127 pub subscription_request_type: Option<SubscriptionRequestType>,
129 pub display_multicast_instrument_id: Option<bool>,
131 pub display_increment_steps: Option<bool>,
133 pub currency: Option<String>,
135 pub secondary_currency: Option<String>,
137 pub security_type: Option<SecurityType>,
139}
140
141impl SecurityListRequest {
142 pub fn new(security_req_id: String, request_type: SecurityListRequestType) -> Self {
144 Self {
145 security_req_id,
146 security_list_request_type: request_type,
147 subscription_request_type: None,
148 display_multicast_instrument_id: None,
149 display_increment_steps: None,
150 currency: None,
151 secondary_currency: None,
152 security_type: None,
153 }
154 }
155
156 pub fn snapshot(security_req_id: String) -> Self {
158 Self::new(security_req_id, SecurityListRequestType::Snapshot)
159 }
160
161 pub fn subscription(security_req_id: String) -> Self {
163 let mut request = Self::new(security_req_id, SecurityListRequestType::SnapshotAndUpdates);
164 request.subscription_request_type = Some(SubscriptionRequestType::SnapshotPlusUpdates);
165 request
166 }
167
168 pub fn with_currency(mut self, currency: String) -> Self {
170 self.currency = Some(currency);
171 self
172 }
173
174 pub fn with_secondary_currency(mut self, secondary_currency: String) -> Self {
176 self.secondary_currency = Some(secondary_currency);
177 self
178 }
179
180 pub fn with_security_type(mut self, security_type: SecurityType) -> Self {
182 self.security_type = Some(security_type);
183 self
184 }
185
186 pub fn with_multicast_instrument_id(mut self, enable: bool) -> Self {
188 self.display_multicast_instrument_id = Some(enable);
189 self
190 }
191
192 pub fn with_increment_steps(mut self, enable: bool) -> Self {
194 self.display_increment_steps = Some(enable);
195 self
196 }
197
198 pub fn to_fix_message(
200 &self,
201 sender_comp_id: String,
202 target_comp_id: String,
203 msg_seq_num: u32,
204 ) -> DeribitFixResult<crate::model::message::FixMessage> {
205 let mut builder = MessageBuilder::new()
206 .msg_type(MsgType::SecurityListRequest)
207 .sender_comp_id(sender_comp_id)
208 .target_comp_id(target_comp_id)
209 .msg_seq_num(msg_seq_num)
210 .sending_time(Utc::now())
211 .field(320, self.security_req_id.clone()) .field(559, i32::from(self.security_list_request_type).to_string()); if let Some(subscription_type) = self.subscription_request_type {
216 builder = builder.field(263, i32::from(subscription_type).to_string()); }
218
219 if let Some(display_multicast) = self.display_multicast_instrument_id {
220 builder = builder.field(
221 9013,
222 if display_multicast {
223 "Y".to_string()
224 } else {
225 "N".to_string()
226 },
227 ); }
229
230 if let Some(display_steps) = self.display_increment_steps {
231 builder = builder.field(
232 9018,
233 if display_steps {
234 "Y".to_string()
235 } else {
236 "N".to_string()
237 },
238 ); }
240
241 if let Some(ref currency) = self.currency {
242 builder = builder.field(15, currency.clone()); }
244
245 if let Some(ref secondary_currency) = self.secondary_currency {
246 builder = builder.field(5544, secondary_currency.clone()); }
248
249 if let Some(ref security_type) = self.security_type {
250 builder = builder.field(167, security_type.as_fix_str().to_string()); }
252
253 builder.build()
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
259pub enum PutOrCall {
260 Put = 0,
262 Call = 1,
264}
265
266impl From<PutOrCall> for i32 {
267 fn from(put_or_call: PutOrCall) -> Self {
268 put_or_call as i32
269 }
270}
271
272impl TryFrom<i32> for PutOrCall {
273 type Error = String;
274
275 fn try_from(value: i32) -> Result<Self, Self::Error> {
276 match value {
277 0 => Ok(PutOrCall::Put),
278 1 => Ok(PutOrCall::Call),
279 _ => Err(format!("Invalid PutOrCall: {value}")),
280 }
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
286pub enum SecurityStatus {
287 Active = 1,
289 Terminated = 2,
291 Closed = 4,
293 Published = 10,
295 Settled = 12,
297}
298
299impl From<SecurityStatus> for i32 {
300 fn from(status: SecurityStatus) -> Self {
301 status as i32
302 }
303}
304
305impl TryFrom<i32> for SecurityStatus {
306 type Error = String;
307
308 fn try_from(value: i32) -> Result<Self, Self::Error> {
309 match value {
310 1 => Ok(SecurityStatus::Active),
311 2 => Ok(SecurityStatus::Terminated),
312 4 => Ok(SecurityStatus::Closed),
313 10 => Ok(SecurityStatus::Published),
314 12 => Ok(SecurityStatus::Settled),
315 _ => Err(format!("Invalid SecurityStatus: {value}")),
316 }
317 }
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct SecurityAltId {
323 pub security_alt_id: String,
325 pub security_alt_id_source: String,
327}
328
329impl SecurityAltId {
330 pub fn multicast(id: String) -> Self {
332 Self {
333 security_alt_id: id,
334 security_alt_id_source: "101".to_string(), }
336 }
337
338 pub fn combo(id: String) -> Self {
340 Self {
341 security_alt_id: id,
342 security_alt_id_source: "102".to_string(), }
344 }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct TickRule {
350 pub start_tick_price_range: f64,
352 pub tick_increment: f64,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct SecurityInfo {
359 pub symbol: String,
361 pub security_desc: Option<String>,
363 pub security_type: Option<SecurityType>,
365 pub put_or_call: Option<PutOrCall>,
367 pub strike_price: Option<f64>,
369 pub strike_currency: Option<String>,
371 pub currency: Option<String>,
373 pub price_quote_currency: Option<String>,
375 pub instrument_price_precision: Option<i32>,
377 pub min_price_increment: Option<f64>,
379 pub underlying_symbol: Option<String>,
381 pub issue_date: Option<DateTime<Utc>>,
383 pub maturity_date: Option<DateTime<Utc>>,
385 pub maturity_time: Option<DateTime<Utc>>,
387 pub min_trade_vol: Option<f64>,
389 pub settl_type: Option<String>,
391 pub settl_currency: Option<String>,
393 pub comm_currency: Option<String>,
395 pub contract_multiplier: Option<f64>,
397 pub security_alt_ids: Vec<SecurityAltId>,
399 pub tick_rules: Vec<TickRule>,
401 pub security_status: Option<SecurityStatus>,
403}
404
405impl SecurityInfo {
406 pub fn new(symbol: String) -> Self {
408 Self {
409 symbol,
410 security_desc: None,
411 security_type: None,
412 put_or_call: None,
413 strike_price: None,
414 strike_currency: None,
415 currency: None,
416 price_quote_currency: None,
417 instrument_price_precision: None,
418 min_price_increment: None,
419 underlying_symbol: None,
420 issue_date: None,
421 maturity_date: None,
422 maturity_time: None,
423 min_trade_vol: None,
424 settl_type: None,
425 settl_currency: None,
426 comm_currency: None,
427 contract_multiplier: None,
428 security_alt_ids: Vec::new(),
429 tick_rules: Vec::new(),
430 security_status: None,
431 }
432 }
433
434 pub fn is_option(&self) -> bool {
436 matches!(
437 self.security_type,
438 Some(SecurityType::Option | SecurityType::OptionCombo)
439 )
440 }
441
442 pub fn is_future(&self) -> bool {
444 matches!(
445 self.security_type,
446 Some(SecurityType::Future | SecurityType::FutureCombo)
447 )
448 }
449
450 pub fn is_spot(&self) -> bool {
452 matches!(self.security_type, Some(SecurityType::FxSpot))
453 }
454
455 pub fn with_security_desc(mut self, desc: String) -> Self {
457 self.security_desc = Some(desc);
458 self
459 }
460
461 pub fn with_security_type(mut self, security_type: SecurityType) -> Self {
463 self.security_type = Some(security_type);
464 self
465 }
466
467 pub fn with_put_or_call(mut self, put_or_call: PutOrCall) -> Self {
469 self.put_or_call = Some(put_or_call);
470 self
471 }
472
473 pub fn with_strike_price(mut self, strike_price: f64) -> Self {
475 self.strike_price = Some(strike_price);
476 self
477 }
478
479 pub fn with_strike_currency(mut self, strike_currency: String) -> Self {
481 self.strike_currency = Some(strike_currency);
482 self
483 }
484
485 pub fn with_currency(mut self, currency: String) -> Self {
487 self.currency = Some(currency);
488 self
489 }
490
491 pub fn with_price_quote_currency(mut self, currency: String) -> Self {
493 self.price_quote_currency = Some(currency);
494 self
495 }
496
497 pub fn with_instrument_price_precision(mut self, precision: i32) -> Self {
499 self.instrument_price_precision = Some(precision);
500 self
501 }
502
503 pub fn with_min_price_increment(mut self, increment: f64) -> Self {
505 self.min_price_increment = Some(increment);
506 self
507 }
508
509 pub fn with_underlying_symbol(mut self, underlying: String) -> Self {
511 self.underlying_symbol = Some(underlying);
512 self
513 }
514
515 pub fn with_issue_date(mut self, issue_date: DateTime<Utc>) -> Self {
517 self.issue_date = Some(issue_date);
518 self
519 }
520
521 pub fn with_maturity_date(mut self, maturity_date: DateTime<Utc>) -> Self {
523 self.maturity_date = Some(maturity_date);
524 self
525 }
526
527 pub fn with_maturity_time(mut self, maturity_time: DateTime<Utc>) -> Self {
529 self.maturity_time = Some(maturity_time);
530 self
531 }
532
533 pub fn with_min_trade_vol(mut self, min_vol: f64) -> Self {
535 self.min_trade_vol = Some(min_vol);
536 self
537 }
538
539 pub fn with_settl_type(mut self, settl_type: String) -> Self {
541 self.settl_type = Some(settl_type);
542 self
543 }
544
545 pub fn with_settl_currency(mut self, currency: String) -> Self {
547 self.settl_currency = Some(currency);
548 self
549 }
550
551 pub fn with_comm_currency(mut self, currency: String) -> Self {
553 self.comm_currency = Some(currency);
554 self
555 }
556
557 pub fn with_contract_multiplier(mut self, multiplier: f64) -> Self {
559 self.contract_multiplier = Some(multiplier);
560 self
561 }
562
563 pub fn add_security_alt_id(mut self, alt_id: SecurityAltId) -> Self {
565 self.security_alt_ids.push(alt_id);
566 self
567 }
568
569 pub fn add_tick_rule(mut self, tick_rule: TickRule) -> Self {
571 self.tick_rules.push(tick_rule);
572 self
573 }
574
575 pub fn with_security_status(mut self, status: SecurityStatus) -> Self {
577 self.security_status = Some(status);
578 self
579 }
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
584pub struct SecurityList {
585 pub security_req_id: String,
587 pub security_response_id: String,
589 pub security_request_result: i32,
591 pub securities: Vec<SecurityInfo>,
593}
594
595impl SecurityList {
596 pub fn new(
598 security_req_id: String,
599 security_response_id: String,
600 securities: Vec<SecurityInfo>,
601 ) -> Self {
602 Self {
603 security_req_id,
604 security_response_id,
605 security_request_result: 0, securities,
607 }
608 }
609
610 pub fn success(
612 security_req_id: String,
613 security_response_id: String,
614 securities: Vec<SecurityInfo>,
615 ) -> Self {
616 Self::new(security_req_id, security_response_id, securities)
617 }
618
619 pub fn count(&self) -> usize {
621 self.securities.len()
622 }
623
624 pub fn is_successful(&self) -> bool {
626 self.security_request_result == 0
627 }
628
629 pub fn filter_by_type(&self, security_type: SecurityType) -> Vec<&SecurityInfo> {
631 self.securities
632 .iter()
633 .filter(|s| s.security_type == Some(security_type))
634 .collect()
635 }
636
637 pub fn filter_by_currency(&self, currency: &str) -> Vec<&SecurityInfo> {
639 self.securities
640 .iter()
641 .filter(|s| s.currency.as_deref() == Some(currency))
642 .collect()
643 }
644
645 pub fn to_fix_message(
647 &self,
648 sender_comp_id: String,
649 target_comp_id: String,
650 msg_seq_num: u32,
651 ) -> DeribitFixResult<crate::model::message::FixMessage> {
652 let mut builder = MessageBuilder::new()
653 .msg_type(MsgType::SecurityList)
654 .sender_comp_id(sender_comp_id)
655 .target_comp_id(target_comp_id)
656 .msg_seq_num(msg_seq_num)
657 .sending_time(Utc::now())
658 .field(320, self.security_req_id.clone()) .field(322, self.security_response_id.clone()) .field(560, self.security_request_result.to_string()) .field(146, self.securities.len().to_string()); for security in &self.securities {
665 builder = builder.field(55, security.symbol.clone()); if let Some(ref desc) = security.security_desc {
670 builder = builder.field(107, desc.clone()); }
672
673 if let Some(ref sec_type) = security.security_type {
674 builder = builder.field(167, sec_type.as_fix_str().to_string()); }
676
677 if let Some(put_or_call) = security.put_or_call {
678 builder = builder.field(201, i32::from(put_or_call).to_string()); }
680
681 if let Some(strike_price) = security.strike_price {
682 builder = builder.field(202, strike_price.to_string()); }
684
685 if let Some(ref strike_currency) = security.strike_currency {
686 builder = builder.field(947, strike_currency.clone()); }
688
689 if let Some(ref currency) = security.currency {
690 builder = builder.field(15, currency.clone()); }
692
693 if let Some(ref price_quote_currency) = security.price_quote_currency {
694 builder = builder.field(1524, price_quote_currency.clone()); }
696
697 if let Some(instrument_price_precision) = security.instrument_price_precision {
698 builder = builder.field(2576, instrument_price_precision.to_string()); }
700
701 if let Some(min_price_increment) = security.min_price_increment {
702 builder = builder.field(969, min_price_increment.to_string()); }
704
705 if let Some(ref underlying_symbol) = security.underlying_symbol {
706 builder = builder.field(311, underlying_symbol.clone()); }
708
709 if let Some(issue_date) = security.issue_date {
710 builder = builder.field(225, issue_date.format("%Y%m%d-%H:%M:%S%.3f").to_string()); }
712
713 if let Some(maturity_date) = security.maturity_date {
714 builder = builder.field(541, maturity_date.format("%Y%m%d").to_string()); }
716
717 if let Some(maturity_time) = security.maturity_time {
718 builder = builder.field(
719 1079,
720 maturity_time.format("%Y%m%d-%H:%M:%S%.3f").to_string(),
721 ); }
723
724 if let Some(min_trade_vol) = security.min_trade_vol {
725 builder = builder.field(562, min_trade_vol.to_string()); }
727
728 if let Some(ref settl_type) = security.settl_type {
729 builder = builder.field(63, settl_type.clone()); }
731
732 if let Some(ref settl_currency) = security.settl_currency {
733 builder = builder.field(120, settl_currency.clone()); }
735
736 if let Some(ref comm_currency) = security.comm_currency {
737 builder = builder.field(479, comm_currency.clone()); }
739
740 if let Some(contract_multiplier) = security.contract_multiplier {
741 builder = builder.field(231, contract_multiplier.to_string()); }
743
744 if !security.security_alt_ids.is_empty() {
746 builder = builder.field(454, security.security_alt_ids.len().to_string()); for alt_id in &security.security_alt_ids {
749 builder = builder.field(455, alt_id.security_alt_id.clone()); builder = builder.field(456, alt_id.security_alt_id_source.clone()); }
752 }
753
754 if !security.tick_rules.is_empty() {
756 builder = builder.field(1205, security.tick_rules.len().to_string()); for tick_rule in &security.tick_rules {
759 builder = builder.field(1206, tick_rule.start_tick_price_range.to_string()); builder = builder.field(1208, tick_rule.tick_increment.to_string()); }
762 }
763
764 if let Some(security_status) = security.security_status {
765 builder = builder.field(965, i32::from(security_status).to_string()); }
767 }
768
769 builder.build()
770 }
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776
777 #[test]
778 fn test_security_list_request_creation() {
779 let request = SecurityListRequest::snapshot("REQ123".to_string());
780 assert_eq!(request.security_req_id, "REQ123");
781 assert_eq!(
782 request.security_list_request_type,
783 SecurityListRequestType::Snapshot
784 );
785 assert!(request.subscription_request_type.is_none());
786 }
787
788 #[test]
789 fn test_security_list_request_subscription() {
790 let request = SecurityListRequest::subscription("SUB456".to_string());
791 assert_eq!(request.security_req_id, "SUB456");
792 assert_eq!(
793 request.security_list_request_type,
794 SecurityListRequestType::SnapshotAndUpdates
795 );
796 assert_eq!(
797 request.subscription_request_type,
798 Some(SubscriptionRequestType::SnapshotPlusUpdates)
799 );
800 }
801
802 #[test]
803 fn test_security_list_request_with_filters() {
804 let request = SecurityListRequest::snapshot("FILTER789".to_string())
805 .with_currency("BTC".to_string())
806 .with_security_type(SecurityType::Future)
807 .with_multicast_instrument_id(true);
808
809 assert_eq!(request.currency, Some("BTC".to_string()));
810 assert_eq!(request.security_type, Some(SecurityType::Future));
811 assert_eq!(request.display_multicast_instrument_id, Some(true));
812 }
813
814 #[test]
815 fn test_security_type_conversion() {
816 assert_eq!(SecurityType::Future.as_fix_str(), "FUT");
817 assert_eq!(SecurityType::Option.as_fix_str(), "OPT");
818 assert_eq!(SecurityType::FxSpot.as_fix_str(), "FXSPOT");
819
820 assert_eq!(
821 SecurityType::from_fix_str("FUT").unwrap(),
822 SecurityType::Future
823 );
824 assert_eq!(
825 SecurityType::from_fix_str("OPT").unwrap(),
826 SecurityType::Option
827 );
828 assert!(SecurityType::from_fix_str("INVALID").is_err());
829 }
830
831 #[test]
832 fn test_security_info_creation() {
833 let mut security = SecurityInfo::new("BTC-PERPETUAL".to_string());
834 security.security_type = Some(SecurityType::Future);
835 security.currency = Some("BTC".to_string());
836
837 assert_eq!(security.symbol, "BTC-PERPETUAL");
838 assert!(security.is_future());
839 assert!(!security.is_option());
840 assert!(!security.is_spot());
841 }
842
843 #[test]
844 fn test_security_list_response() {
845 let securities = vec![
846 SecurityInfo::new("BTC-PERPETUAL".to_string()),
847 SecurityInfo::new("ETH-PERPETUAL".to_string()),
848 ];
849
850 let response =
851 SecurityList::success("REQ123".to_string(), "RESP456".to_string(), securities);
852
853 assert_eq!(response.security_req_id, "REQ123");
854 assert_eq!(response.security_response_id, "RESP456");
855 assert_eq!(response.count(), 2);
856 assert!(response.is_successful());
857 }
858
859 #[test]
860 fn test_put_or_call_conversion() {
861 assert_eq!(i32::from(PutOrCall::Put), 0);
862 assert_eq!(i32::from(PutOrCall::Call), 1);
863 assert_eq!(PutOrCall::try_from(0).unwrap(), PutOrCall::Put);
864 assert_eq!(PutOrCall::try_from(1).unwrap(), PutOrCall::Call);
865 assert!(PutOrCall::try_from(2).is_err());
866 }
867
868 #[test]
869 fn test_security_status_conversion() {
870 assert_eq!(i32::from(SecurityStatus::Active), 1);
871 assert_eq!(i32::from(SecurityStatus::Terminated), 2);
872 assert_eq!(SecurityStatus::try_from(1).unwrap(), SecurityStatus::Active);
873 assert_eq!(
874 SecurityStatus::try_from(2).unwrap(),
875 SecurityStatus::Terminated
876 );
877 assert!(SecurityStatus::try_from(99).is_err());
878 }
879
880 #[test]
881 fn test_security_alt_id_creation() {
882 let multicast_id = SecurityAltId::multicast("MC123".to_string());
883 assert_eq!(multicast_id.security_alt_id, "MC123");
884 assert_eq!(multicast_id.security_alt_id_source, "101");
885
886 let combo_id = SecurityAltId::combo("COMBO456".to_string());
887 assert_eq!(combo_id.security_alt_id, "COMBO456");
888 assert_eq!(combo_id.security_alt_id_source, "102");
889 }
890
891 #[test]
892 fn test_tick_rule_creation() {
893 let tick_rule = TickRule {
894 start_tick_price_range: 100.0,
895 tick_increment: 0.5,
896 };
897
898 assert_eq!(tick_rule.start_tick_price_range, 100.0);
899 assert_eq!(tick_rule.tick_increment, 0.5);
900 }
901
902 #[test]
903 fn test_security_info_with_all_fields() {
904 use chrono::Utc;
905
906 let maturity_date = Utc::now() + chrono::Duration::days(30);
907 let issue_date = Utc::now() - chrono::Duration::days(365);
908
909 let security = SecurityInfo::new("BTC-28JUL23-30000-C".to_string())
910 .with_security_desc("BTC Call Option".to_string())
911 .with_security_type(SecurityType::Option)
912 .with_put_or_call(PutOrCall::Call)
913 .with_strike_price(30000.0)
914 .with_strike_currency("USD".to_string())
915 .with_currency("BTC".to_string())
916 .with_price_quote_currency("USD".to_string())
917 .with_instrument_price_precision(4)
918 .with_min_price_increment(0.0001)
919 .with_underlying_symbol("BTC".to_string())
920 .with_issue_date(issue_date)
921 .with_maturity_date(maturity_date)
922 .with_maturity_time(maturity_date)
923 .with_min_trade_vol(0.1)
924 .with_settl_type("M1".to_string())
925 .with_settl_currency("USD".to_string())
926 .with_comm_currency("USD".to_string())
927 .with_contract_multiplier(1.0)
928 .add_security_alt_id(SecurityAltId::multicast("MC123".to_string()))
929 .add_tick_rule(TickRule {
930 start_tick_price_range: 0.0,
931 tick_increment: 0.0001,
932 })
933 .with_security_status(SecurityStatus::Active);
934
935 assert_eq!(security.symbol, "BTC-28JUL23-30000-C");
936 assert_eq!(security.security_desc, Some("BTC Call Option".to_string()));
937 assert_eq!(security.security_type, Some(SecurityType::Option));
938 assert_eq!(security.put_or_call, Some(PutOrCall::Call));
939 assert_eq!(security.strike_price, Some(30000.0));
940 assert_eq!(security.strike_currency, Some("USD".to_string()));
941 assert_eq!(security.currency, Some("BTC".to_string()));
942 assert_eq!(security.price_quote_currency, Some("USD".to_string()));
943 assert_eq!(security.instrument_price_precision, Some(4));
944 assert_eq!(security.min_price_increment, Some(0.0001));
945 assert_eq!(security.underlying_symbol, Some("BTC".to_string()));
946 assert_eq!(security.issue_date, Some(issue_date));
947 assert_eq!(security.maturity_date, Some(maturity_date));
948 assert_eq!(security.maturity_time, Some(maturity_date));
949 assert_eq!(security.min_trade_vol, Some(0.1));
950 assert_eq!(security.settl_type, Some("M1".to_string()));
951 assert_eq!(security.settl_currency, Some("USD".to_string()));
952 assert_eq!(security.comm_currency, Some("USD".to_string()));
953 assert_eq!(security.contract_multiplier, Some(1.0));
954 assert_eq!(security.security_alt_ids.len(), 1);
955 assert_eq!(security.tick_rules.len(), 1);
956 assert_eq!(security.security_status, Some(SecurityStatus::Active));
957 assert!(security.is_option());
958 }
959
960 #[test]
961 fn test_security_list_to_fix_message_with_all_fields() {
962 let security = SecurityInfo::new("BTC-PERPETUAL".to_string())
963 .with_security_desc("BTC Perpetual Future".to_string())
964 .with_security_type(SecurityType::Future)
965 .with_currency("BTC".to_string())
966 .with_price_quote_currency("USD".to_string())
967 .with_instrument_price_precision(2)
968 .with_min_price_increment(0.5)
969 .with_min_trade_vol(1.0)
970 .with_settl_currency("USD".to_string())
971 .with_contract_multiplier(1.0)
972 .add_security_alt_id(SecurityAltId::multicast("MC456".to_string()))
973 .add_tick_rule(TickRule {
974 start_tick_price_range: 0.0,
975 tick_increment: 0.5,
976 })
977 .with_security_status(SecurityStatus::Active);
978
979 let securities = vec![security];
980 let security_list =
981 SecurityList::success("REQ123".to_string(), "RESP456".to_string(), securities);
982
983 let fix_message = security_list
984 .to_fix_message("SENDER".to_string(), "TARGET".to_string(), 1)
985 .unwrap();
986 let fix_str = fix_message.to_string();
987
988 assert!(fix_str.contains("35=y")); assert!(fix_str.contains("320=REQ123")); assert!(fix_str.contains("322=RESP456")); assert!(fix_str.contains("560=0")); assert!(fix_str.contains("146=1")); assert!(fix_str.contains("55=BTC-PERPETUAL")); assert!(fix_str.contains("107=BTC Perpetual Future")); assert!(fix_str.contains("167=FUT")); assert!(fix_str.contains("15=BTC")); assert!(fix_str.contains("1524=USD")); assert!(fix_str.contains("2576=2")); assert!(fix_str.contains("969=0.5")); assert!(fix_str.contains("562=1")); assert!(fix_str.contains("120=USD")); assert!(fix_str.contains("231=1")); assert!(fix_str.contains("454=1")); assert!(fix_str.contains("455=MC456")); assert!(fix_str.contains("456=101")); assert!(fix_str.contains("1205=1")); assert!(fix_str.contains("1206=0")); assert!(fix_str.contains("1208=0.5")); assert!(fix_str.contains("965=1")); }
1014}