use crate::error::Result as DeribitFixResult;
use crate::message::MessageBuilder;
use crate::model::types::MsgType;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecurityListRequestType {
Snapshot = 0,
SnapshotAndUpdates = 4,
}
impl From<SecurityListRequestType> for i32 {
fn from(request_type: SecurityListRequestType) -> Self {
request_type as i32
}
}
impl TryFrom<i32> for SecurityListRequestType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(SecurityListRequestType::Snapshot),
4 => Ok(SecurityListRequestType::SnapshotAndUpdates),
_ => Err(format!("Invalid SecurityListRequestType: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SubscriptionRequestType {
Snapshot = 0,
SnapshotPlusUpdates = 1,
Unsubscribe = 2,
}
impl From<SubscriptionRequestType> for i32 {
fn from(request_type: SubscriptionRequestType) -> Self {
request_type as i32
}
}
impl TryFrom<i32> for SubscriptionRequestType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(SubscriptionRequestType::Snapshot),
1 => Ok(SubscriptionRequestType::SnapshotPlusUpdates),
2 => Ok(SubscriptionRequestType::Unsubscribe),
_ => Err(format!("Invalid SubscriptionRequestType: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecurityType {
FxSpot,
Future,
Option,
FutureCombo,
OptionCombo,
Index,
}
impl SecurityType {
pub fn as_fix_str(&self) -> &'static str {
match self {
SecurityType::FxSpot => "FXSPOT",
SecurityType::Future => "FUT",
SecurityType::Option => "OPT",
SecurityType::FutureCombo => "FUTCO",
SecurityType::OptionCombo => "OPTCO",
SecurityType::Index => "INDEX",
}
}
pub fn from_fix_str(s: &str) -> Result<Self, String> {
match s {
"FXSPOT" => Ok(SecurityType::FxSpot),
"FUT" => Ok(SecurityType::Future),
"OPT" => Ok(SecurityType::Option),
"FUTCO" => Ok(SecurityType::FutureCombo),
"OPTCO" => Ok(SecurityType::OptionCombo),
"INDEX" => Ok(SecurityType::Index),
_ => Err(format!("Invalid SecurityType: {s}")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityListRequest {
pub security_req_id: String,
pub security_list_request_type: SecurityListRequestType,
pub subscription_request_type: Option<SubscriptionRequestType>,
pub display_multicast_instrument_id: Option<bool>,
pub display_increment_steps: Option<bool>,
pub currency: Option<String>,
pub secondary_currency: Option<String>,
pub security_type: Option<SecurityType>,
}
impl SecurityListRequest {
pub fn new(security_req_id: String, request_type: SecurityListRequestType) -> Self {
Self {
security_req_id,
security_list_request_type: request_type,
subscription_request_type: None,
display_multicast_instrument_id: None,
display_increment_steps: None,
currency: None,
secondary_currency: None,
security_type: None,
}
}
pub fn snapshot(security_req_id: String) -> Self {
Self::new(security_req_id, SecurityListRequestType::Snapshot)
}
pub fn subscription(security_req_id: String) -> Self {
let mut request = Self::new(security_req_id, SecurityListRequestType::SnapshotAndUpdates);
request.subscription_request_type = Some(SubscriptionRequestType::SnapshotPlusUpdates);
request
}
pub fn with_currency(mut self, currency: String) -> Self {
self.currency = Some(currency);
self
}
pub fn with_secondary_currency(mut self, secondary_currency: String) -> Self {
self.secondary_currency = Some(secondary_currency);
self
}
pub fn with_security_type(mut self, security_type: SecurityType) -> Self {
self.security_type = Some(security_type);
self
}
pub fn with_multicast_instrument_id(mut self, enable: bool) -> Self {
self.display_multicast_instrument_id = Some(enable);
self
}
pub fn with_increment_steps(mut self, enable: bool) -> Self {
self.display_increment_steps = Some(enable);
self
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<crate::model::message::FixMessage> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::SecurityListRequest)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.sending_time(Utc::now())
.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 {
builder = builder.field(263, i32::from(subscription_type).to_string()); }
if let Some(display_multicast) = self.display_multicast_instrument_id {
builder = builder.field(
9013,
if display_multicast {
"Y".to_string()
} else {
"N".to_string()
},
); }
if let Some(display_steps) = self.display_increment_steps {
builder = builder.field(
9018,
if display_steps {
"Y".to_string()
} else {
"N".to_string()
},
); }
if let Some(ref currency) = self.currency {
builder = builder.field(15, currency.clone()); }
if let Some(ref secondary_currency) = self.secondary_currency {
builder = builder.field(5544, secondary_currency.clone()); }
if let Some(ref security_type) = self.security_type {
builder = builder.field(167, security_type.as_fix_str().to_string()); }
builder.build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PutOrCall {
Put = 0,
Call = 1,
}
impl From<PutOrCall> for i32 {
fn from(put_or_call: PutOrCall) -> Self {
put_or_call as i32
}
}
impl TryFrom<i32> for PutOrCall {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(PutOrCall::Put),
1 => Ok(PutOrCall::Call),
_ => Err(format!("Invalid PutOrCall: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecurityStatus {
Active = 1,
Terminated = 2,
Closed = 4,
Published = 10,
Settled = 12,
}
impl From<SecurityStatus> for i32 {
fn from(status: SecurityStatus) -> Self {
status as i32
}
}
impl TryFrom<i32> for SecurityStatus {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
1 => Ok(SecurityStatus::Active),
2 => Ok(SecurityStatus::Terminated),
4 => Ok(SecurityStatus::Closed),
10 => Ok(SecurityStatus::Published),
12 => Ok(SecurityStatus::Settled),
_ => Err(format!("Invalid SecurityStatus: {value}")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityAltId {
pub security_alt_id: String,
pub security_alt_id_source: String,
}
impl SecurityAltId {
pub fn multicast(id: String) -> Self {
Self {
security_alt_id: id,
security_alt_id_source: "101".to_string(), }
}
pub fn combo(id: String) -> Self {
Self {
security_alt_id: id,
security_alt_id_source: "102".to_string(), }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TickRule {
pub start_tick_price_range: f64,
pub tick_increment: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityInfo {
pub symbol: String,
pub security_desc: Option<String>,
pub security_type: Option<SecurityType>,
pub put_or_call: Option<PutOrCall>,
pub strike_price: Option<f64>,
pub strike_currency: Option<String>,
pub currency: Option<String>,
pub price_quote_currency: Option<String>,
pub instrument_price_precision: Option<i32>,
pub min_price_increment: Option<f64>,
pub underlying_symbol: Option<String>,
pub issue_date: Option<DateTime<Utc>>,
pub maturity_date: Option<DateTime<Utc>>,
pub maturity_time: Option<DateTime<Utc>>,
pub min_trade_vol: Option<f64>,
pub settl_type: Option<String>,
pub settl_currency: Option<String>,
pub comm_currency: Option<String>,
pub contract_multiplier: Option<f64>,
pub security_alt_ids: Vec<SecurityAltId>,
pub tick_rules: Vec<TickRule>,
pub security_status: Option<SecurityStatus>,
}
impl SecurityInfo {
pub fn new(symbol: String) -> Self {
Self {
symbol,
security_desc: None,
security_type: None,
put_or_call: None,
strike_price: None,
strike_currency: None,
currency: None,
price_quote_currency: None,
instrument_price_precision: None,
min_price_increment: None,
underlying_symbol: None,
issue_date: None,
maturity_date: None,
maturity_time: None,
min_trade_vol: None,
settl_type: None,
settl_currency: None,
comm_currency: None,
contract_multiplier: None,
security_alt_ids: Vec::new(),
tick_rules: Vec::new(),
security_status: None,
}
}
pub fn is_option(&self) -> bool {
matches!(
self.security_type,
Some(SecurityType::Option | SecurityType::OptionCombo)
)
}
pub fn is_future(&self) -> bool {
matches!(
self.security_type,
Some(SecurityType::Future | SecurityType::FutureCombo)
)
}
pub fn is_spot(&self) -> bool {
matches!(self.security_type, Some(SecurityType::FxSpot))
}
pub fn with_security_desc(mut self, desc: String) -> Self {
self.security_desc = Some(desc);
self
}
pub fn with_security_type(mut self, security_type: SecurityType) -> Self {
self.security_type = Some(security_type);
self
}
pub fn with_put_or_call(mut self, put_or_call: PutOrCall) -> Self {
self.put_or_call = Some(put_or_call);
self
}
pub fn with_strike_price(mut self, strike_price: f64) -> Self {
self.strike_price = Some(strike_price);
self
}
pub fn with_strike_currency(mut self, strike_currency: String) -> Self {
self.strike_currency = Some(strike_currency);
self
}
pub fn with_currency(mut self, currency: String) -> Self {
self.currency = Some(currency);
self
}
pub fn with_price_quote_currency(mut self, currency: String) -> Self {
self.price_quote_currency = Some(currency);
self
}
pub fn with_instrument_price_precision(mut self, precision: i32) -> Self {
self.instrument_price_precision = Some(precision);
self
}
pub fn with_min_price_increment(mut self, increment: f64) -> Self {
self.min_price_increment = Some(increment);
self
}
pub fn with_underlying_symbol(mut self, underlying: String) -> Self {
self.underlying_symbol = Some(underlying);
self
}
pub fn with_issue_date(mut self, issue_date: DateTime<Utc>) -> Self {
self.issue_date = Some(issue_date);
self
}
pub fn with_maturity_date(mut self, maturity_date: DateTime<Utc>) -> Self {
self.maturity_date = Some(maturity_date);
self
}
pub fn with_maturity_time(mut self, maturity_time: DateTime<Utc>) -> Self {
self.maturity_time = Some(maturity_time);
self
}
pub fn with_min_trade_vol(mut self, min_vol: f64) -> Self {
self.min_trade_vol = Some(min_vol);
self
}
pub fn with_settl_type(mut self, settl_type: String) -> Self {
self.settl_type = Some(settl_type);
self
}
pub fn with_settl_currency(mut self, currency: String) -> Self {
self.settl_currency = Some(currency);
self
}
pub fn with_comm_currency(mut self, currency: String) -> Self {
self.comm_currency = Some(currency);
self
}
pub fn with_contract_multiplier(mut self, multiplier: f64) -> Self {
self.contract_multiplier = Some(multiplier);
self
}
pub fn add_security_alt_id(mut self, alt_id: SecurityAltId) -> Self {
self.security_alt_ids.push(alt_id);
self
}
pub fn add_tick_rule(mut self, tick_rule: TickRule) -> Self {
self.tick_rules.push(tick_rule);
self
}
pub fn with_security_status(mut self, status: SecurityStatus) -> Self {
self.security_status = Some(status);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityList {
pub security_req_id: String,
pub security_response_id: String,
pub security_request_result: i32,
pub securities: Vec<SecurityInfo>,
}
impl SecurityList {
pub fn new(
security_req_id: String,
security_response_id: String,
securities: Vec<SecurityInfo>,
) -> Self {
Self {
security_req_id,
security_response_id,
security_request_result: 0, securities,
}
}
pub fn success(
security_req_id: String,
security_response_id: String,
securities: Vec<SecurityInfo>,
) -> Self {
Self::new(security_req_id, security_response_id, securities)
}
pub fn count(&self) -> usize {
self.securities.len()
}
pub fn is_successful(&self) -> bool {
self.security_request_result == 0
}
pub fn filter_by_type(&self, security_type: SecurityType) -> Vec<&SecurityInfo> {
self.securities
.iter()
.filter(|s| s.security_type == Some(security_type))
.collect()
}
pub fn filter_by_currency(&self, currency: &str) -> Vec<&SecurityInfo> {
self.securities
.iter()
.filter(|s| s.currency.as_deref() == Some(currency))
.collect()
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<crate::model::message::FixMessage> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::SecurityList)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.sending_time(Utc::now())
.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 {
builder = builder.field(55, security.symbol.clone());
if let Some(ref desc) = security.security_desc {
builder = builder.field(107, desc.clone()); }
if let Some(ref sec_type) = security.security_type {
builder = builder.field(167, sec_type.as_fix_str().to_string()); }
if let Some(put_or_call) = security.put_or_call {
builder = builder.field(201, i32::from(put_or_call).to_string()); }
if let Some(strike_price) = security.strike_price {
builder = builder.field(202, strike_price.to_string()); }
if let Some(ref strike_currency) = security.strike_currency {
builder = builder.field(947, strike_currency.clone()); }
if let Some(ref currency) = security.currency {
builder = builder.field(15, currency.clone()); }
if let Some(ref price_quote_currency) = security.price_quote_currency {
builder = builder.field(1524, price_quote_currency.clone()); }
if let Some(instrument_price_precision) = security.instrument_price_precision {
builder = builder.field(2576, instrument_price_precision.to_string()); }
if let Some(min_price_increment) = security.min_price_increment {
builder = builder.field(969, min_price_increment.to_string()); }
if let Some(ref underlying_symbol) = security.underlying_symbol {
builder = builder.field(311, underlying_symbol.clone()); }
if let Some(issue_date) = security.issue_date {
builder = builder.field(225, issue_date.format("%Y%m%d-%H:%M:%S%.3f").to_string()); }
if let Some(maturity_date) = security.maturity_date {
builder = builder.field(541, maturity_date.format("%Y%m%d").to_string()); }
if let Some(maturity_time) = security.maturity_time {
builder = builder.field(
1079,
maturity_time.format("%Y%m%d-%H:%M:%S%.3f").to_string(),
); }
if let Some(min_trade_vol) = security.min_trade_vol {
builder = builder.field(562, min_trade_vol.to_string()); }
if let Some(ref settl_type) = security.settl_type {
builder = builder.field(63, settl_type.clone()); }
if let Some(ref settl_currency) = security.settl_currency {
builder = builder.field(120, settl_currency.clone()); }
if let Some(ref comm_currency) = security.comm_currency {
builder = builder.field(479, comm_currency.clone()); }
if let Some(contract_multiplier) = security.contract_multiplier {
builder = builder.field(231, contract_multiplier.to_string()); }
if !security.security_alt_ids.is_empty() {
builder = builder.field(454, security.security_alt_ids.len().to_string());
for alt_id in &security.security_alt_ids {
builder = builder.field(455, alt_id.security_alt_id.clone()); builder = builder.field(456, alt_id.security_alt_id_source.clone()); }
}
if !security.tick_rules.is_empty() {
builder = builder.field(1205, security.tick_rules.len().to_string());
for tick_rule in &security.tick_rules {
builder = builder.field(1206, tick_rule.start_tick_price_range.to_string()); builder = builder.field(1208, tick_rule.tick_increment.to_string()); }
}
if let Some(security_status) = security.security_status {
builder = builder.field(965, i32::from(security_status).to_string()); }
}
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_list_request_creation() {
let request = SecurityListRequest::snapshot("REQ123".to_string());
assert_eq!(request.security_req_id, "REQ123");
assert_eq!(
request.security_list_request_type,
SecurityListRequestType::Snapshot
);
assert!(request.subscription_request_type.is_none());
}
#[test]
fn test_security_list_request_subscription() {
let request = SecurityListRequest::subscription("SUB456".to_string());
assert_eq!(request.security_req_id, "SUB456");
assert_eq!(
request.security_list_request_type,
SecurityListRequestType::SnapshotAndUpdates
);
assert_eq!(
request.subscription_request_type,
Some(SubscriptionRequestType::SnapshotPlusUpdates)
);
}
#[test]
fn test_security_list_request_with_filters() {
let request = SecurityListRequest::snapshot("FILTER789".to_string())
.with_currency("BTC".to_string())
.with_security_type(SecurityType::Future)
.with_multicast_instrument_id(true);
assert_eq!(request.currency, Some("BTC".to_string()));
assert_eq!(request.security_type, Some(SecurityType::Future));
assert_eq!(request.display_multicast_instrument_id, Some(true));
}
#[test]
fn test_security_type_conversion() {
assert_eq!(SecurityType::Future.as_fix_str(), "FUT");
assert_eq!(SecurityType::Option.as_fix_str(), "OPT");
assert_eq!(SecurityType::FxSpot.as_fix_str(), "FXSPOT");
assert_eq!(
SecurityType::from_fix_str("FUT").unwrap(),
SecurityType::Future
);
assert_eq!(
SecurityType::from_fix_str("OPT").unwrap(),
SecurityType::Option
);
assert!(SecurityType::from_fix_str("INVALID").is_err());
}
#[test]
fn test_security_info_creation() {
let mut security = SecurityInfo::new("BTC-PERPETUAL".to_string());
security.security_type = Some(SecurityType::Future);
security.currency = Some("BTC".to_string());
assert_eq!(security.symbol, "BTC-PERPETUAL");
assert!(security.is_future());
assert!(!security.is_option());
assert!(!security.is_spot());
}
#[test]
fn test_security_list_response() {
let securities = vec![
SecurityInfo::new("BTC-PERPETUAL".to_string()),
SecurityInfo::new("ETH-PERPETUAL".to_string()),
];
let response =
SecurityList::success("REQ123".to_string(), "RESP456".to_string(), securities);
assert_eq!(response.security_req_id, "REQ123");
assert_eq!(response.security_response_id, "RESP456");
assert_eq!(response.count(), 2);
assert!(response.is_successful());
}
#[test]
fn test_put_or_call_conversion() {
assert_eq!(i32::from(PutOrCall::Put), 0);
assert_eq!(i32::from(PutOrCall::Call), 1);
assert_eq!(PutOrCall::try_from(0).unwrap(), PutOrCall::Put);
assert_eq!(PutOrCall::try_from(1).unwrap(), PutOrCall::Call);
assert!(PutOrCall::try_from(2).is_err());
}
#[test]
fn test_security_status_conversion() {
assert_eq!(i32::from(SecurityStatus::Active), 1);
assert_eq!(i32::from(SecurityStatus::Terminated), 2);
assert_eq!(SecurityStatus::try_from(1).unwrap(), SecurityStatus::Active);
assert_eq!(
SecurityStatus::try_from(2).unwrap(),
SecurityStatus::Terminated
);
assert!(SecurityStatus::try_from(99).is_err());
}
#[test]
fn test_security_alt_id_creation() {
let multicast_id = SecurityAltId::multicast("MC123".to_string());
assert_eq!(multicast_id.security_alt_id, "MC123");
assert_eq!(multicast_id.security_alt_id_source, "101");
let combo_id = SecurityAltId::combo("COMBO456".to_string());
assert_eq!(combo_id.security_alt_id, "COMBO456");
assert_eq!(combo_id.security_alt_id_source, "102");
}
#[test]
fn test_tick_rule_creation() {
let tick_rule = TickRule {
start_tick_price_range: 100.0,
tick_increment: 0.5,
};
assert_eq!(tick_rule.start_tick_price_range, 100.0);
assert_eq!(tick_rule.tick_increment, 0.5);
}
#[test]
fn test_security_info_with_all_fields() {
use chrono::Utc;
let maturity_date = Utc::now() + chrono::Duration::days(30);
let issue_date = Utc::now() - chrono::Duration::days(365);
let security = SecurityInfo::new("BTC-28JUL23-30000-C".to_string())
.with_security_desc("BTC Call Option".to_string())
.with_security_type(SecurityType::Option)
.with_put_or_call(PutOrCall::Call)
.with_strike_price(30000.0)
.with_strike_currency("USD".to_string())
.with_currency("BTC".to_string())
.with_price_quote_currency("USD".to_string())
.with_instrument_price_precision(4)
.with_min_price_increment(0.0001)
.with_underlying_symbol("BTC".to_string())
.with_issue_date(issue_date)
.with_maturity_date(maturity_date)
.with_maturity_time(maturity_date)
.with_min_trade_vol(0.1)
.with_settl_type("M1".to_string())
.with_settl_currency("USD".to_string())
.with_comm_currency("USD".to_string())
.with_contract_multiplier(1.0)
.add_security_alt_id(SecurityAltId::multicast("MC123".to_string()))
.add_tick_rule(TickRule {
start_tick_price_range: 0.0,
tick_increment: 0.0001,
})
.with_security_status(SecurityStatus::Active);
assert_eq!(security.symbol, "BTC-28JUL23-30000-C");
assert_eq!(security.security_desc, Some("BTC Call Option".to_string()));
assert_eq!(security.security_type, Some(SecurityType::Option));
assert_eq!(security.put_or_call, Some(PutOrCall::Call));
assert_eq!(security.strike_price, Some(30000.0));
assert_eq!(security.strike_currency, Some("USD".to_string()));
assert_eq!(security.currency, Some("BTC".to_string()));
assert_eq!(security.price_quote_currency, Some("USD".to_string()));
assert_eq!(security.instrument_price_precision, Some(4));
assert_eq!(security.min_price_increment, Some(0.0001));
assert_eq!(security.underlying_symbol, Some("BTC".to_string()));
assert_eq!(security.issue_date, Some(issue_date));
assert_eq!(security.maturity_date, Some(maturity_date));
assert_eq!(security.maturity_time, Some(maturity_date));
assert_eq!(security.min_trade_vol, Some(0.1));
assert_eq!(security.settl_type, Some("M1".to_string()));
assert_eq!(security.settl_currency, Some("USD".to_string()));
assert_eq!(security.comm_currency, Some("USD".to_string()));
assert_eq!(security.contract_multiplier, Some(1.0));
assert_eq!(security.security_alt_ids.len(), 1);
assert_eq!(security.tick_rules.len(), 1);
assert_eq!(security.security_status, Some(SecurityStatus::Active));
assert!(security.is_option());
}
#[test]
fn test_security_list_to_fix_message_with_all_fields() {
let security = SecurityInfo::new("BTC-PERPETUAL".to_string())
.with_security_desc("BTC Perpetual Future".to_string())
.with_security_type(SecurityType::Future)
.with_currency("BTC".to_string())
.with_price_quote_currency("USD".to_string())
.with_instrument_price_precision(2)
.with_min_price_increment(0.5)
.with_min_trade_vol(1.0)
.with_settl_currency("USD".to_string())
.with_contract_multiplier(1.0)
.add_security_alt_id(SecurityAltId::multicast("MC456".to_string()))
.add_tick_rule(TickRule {
start_tick_price_range: 0.0,
tick_increment: 0.5,
})
.with_security_status(SecurityStatus::Active);
let securities = vec![security];
let security_list =
SecurityList::success("REQ123".to_string(), "RESP456".to_string(), securities);
let fix_message = security_list
.to_fix_message("SENDER".to_string(), "TARGET".to_string(), 1)
.unwrap();
let fix_str = fix_message.to_string();
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")); }
}