use crate::error::EncodeError;
use std::collections::HashMap;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MessageType {
Heartbeat,
TestRequest,
ResendRequest,
Reject,
SequenceReset,
Logout,
IndicationOfInterest,
Advertisement,
ExecutionReport,
OrderCancelReject,
Logon,
News,
Email,
NewOrderSingle,
NewOrderList,
OrderCancelRequest,
OrderCancelReplaceRequest,
OrderStatusRequest,
AllocationInstruction,
ListCancelRequest,
ListExecute,
ListStatusRequest,
ListStatus,
AllocationInstructionAck,
DontKnowTrade,
QuoteRequest,
Quote,
SettlementInstructions,
MarketDataRequest,
MarketDataSnapshotFullRefresh,
MarketDataIncrementalRefresh,
MarketDataRequestReject,
QuoteCancel,
QuoteStatusRequest,
MassQuoteAcknowledgement,
SecurityDefinitionRequest,
SecurityDefinition,
SecurityStatusRequest,
SecurityStatus,
TradingSessionStatusRequest,
TradingSessionStatus,
MassQuote,
BusinessMessageReject,
BidRequest,
BidResponse,
ListStrikePrice,
Custom(String),
}
impl MessageType {
pub fn as_str(&self) -> &str {
match self {
MessageType::Heartbeat => "0",
MessageType::TestRequest => "1",
MessageType::ResendRequest => "2",
MessageType::Reject => "3",
MessageType::SequenceReset => "4",
MessageType::Logout => "5",
MessageType::IndicationOfInterest => "6",
MessageType::Advertisement => "7",
MessageType::ExecutionReport => "8",
MessageType::OrderCancelReject => "9",
MessageType::Logon => "A",
MessageType::News => "B",
MessageType::Email => "C",
MessageType::NewOrderSingle => "D",
MessageType::NewOrderList => "E",
MessageType::OrderCancelRequest => "F",
MessageType::OrderCancelReplaceRequest => "G",
MessageType::OrderStatusRequest => "H",
MessageType::AllocationInstruction => "J",
MessageType::ListCancelRequest => "K",
MessageType::ListExecute => "L",
MessageType::ListStatusRequest => "M",
MessageType::ListStatus => "N",
MessageType::AllocationInstructionAck => "P",
MessageType::DontKnowTrade => "Q",
MessageType::QuoteRequest => "R",
MessageType::Quote => "S",
MessageType::SettlementInstructions => "T",
MessageType::MarketDataRequest => "V",
MessageType::MarketDataSnapshotFullRefresh => "W",
MessageType::MarketDataIncrementalRefresh => "X",
MessageType::MarketDataRequestReject => "Y",
MessageType::QuoteCancel => "Z",
MessageType::QuoteStatusRequest => "a",
MessageType::MassQuoteAcknowledgement => "b",
MessageType::SecurityDefinitionRequest => "c",
MessageType::SecurityDefinition => "d",
MessageType::SecurityStatusRequest => "e",
MessageType::SecurityStatus => "f",
MessageType::TradingSessionStatusRequest => "g",
MessageType::TradingSessionStatus => "h",
MessageType::MassQuote => "i",
MessageType::BusinessMessageReject => "j",
MessageType::BidRequest => "k",
MessageType::BidResponse => "l",
MessageType::ListStrikePrice => "m",
MessageType::Custom(ref s) => s,
}
}
pub fn from_str(s: &str) -> Self {
match s {
"0" => MessageType::Heartbeat,
"1" => MessageType::TestRequest,
"2" => MessageType::ResendRequest,
"3" => MessageType::Reject,
"4" => MessageType::SequenceReset,
"5" => MessageType::Logout,
"6" => MessageType::IndicationOfInterest,
"7" => MessageType::Advertisement,
"8" => MessageType::ExecutionReport,
"9" => MessageType::OrderCancelReject,
"A" => MessageType::Logon,
"B" => MessageType::News,
"C" => MessageType::Email,
"D" => MessageType::NewOrderSingle,
"E" => MessageType::NewOrderList,
"F" => MessageType::OrderCancelRequest,
"G" => MessageType::OrderCancelReplaceRequest,
"H" => MessageType::OrderStatusRequest,
"J" => MessageType::AllocationInstruction,
"K" => MessageType::ListCancelRequest,
"L" => MessageType::ListExecute,
"M" => MessageType::ListStatusRequest,
"N" => MessageType::ListStatus,
"P" => MessageType::AllocationInstructionAck,
"Q" => MessageType::DontKnowTrade,
"R" => MessageType::QuoteRequest,
"S" => MessageType::Quote,
"T" => MessageType::SettlementInstructions,
"V" => MessageType::MarketDataRequest,
"W" => MessageType::MarketDataSnapshotFullRefresh,
"X" => MessageType::MarketDataIncrementalRefresh,
"Y" => MessageType::MarketDataRequestReject,
"Z" => MessageType::QuoteCancel,
"a" => MessageType::QuoteStatusRequest,
"b" => MessageType::MassQuoteAcknowledgement,
"c" => MessageType::SecurityDefinitionRequest,
"d" => MessageType::SecurityDefinition,
"e" => MessageType::SecurityStatusRequest,
"f" => MessageType::SecurityStatus,
"g" => MessageType::TradingSessionStatusRequest,
"h" => MessageType::TradingSessionStatus,
"i" => MessageType::MassQuote,
"j" => MessageType::BusinessMessageReject,
"k" => MessageType::BidRequest,
"l" => MessageType::BidResponse,
"m" => MessageType::ListStrikePrice,
_ => MessageType::Custom(s.to_string()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FieldValue {
String(String),
Int(i64),
UInt(u64),
Float(f64),
Bool(bool),
Bytes(Vec<u8>),
Decimal {
mantissa: i64,
scale: i32,
},
Timestamp(u64),
Optional(Option<Box<FieldValue>>),
}
impl FieldValue {
pub fn as_string(&self) -> Option<&str> {
match self {
FieldValue::String(s) => Some(s),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
FieldValue::Int(i) => Some(*i),
FieldValue::UInt(u) => (*u as i64).into(),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
FieldValue::Float(f) => Some(*f),
FieldValue::Int(i) => Some(*i as f64),
FieldValue::UInt(u) => Some(*u as f64),
FieldValue::Decimal { mantissa, scale } => Some(*mantissa as f64 / 10_f64.powi(*scale)),
_ => None,
}
}
pub fn is_present(&self) -> bool {
match self {
FieldValue::Optional(None) => false,
_ => true,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FixMessage {
pub message_type: MessageType,
pub fields: HashMap<u32, FieldValue>,
pub seq_num: Option<u32>,
pub sender_comp_id: Option<String>,
pub target_comp_id: Option<String>,
pub sending_time: Option<u64>,
}
impl FixMessage {
pub fn new(message_type: MessageType) -> Self {
Self {
message_type,
fields: HashMap::new(),
seq_num: None,
sender_comp_id: None,
target_comp_id: None,
sending_time: None,
}
}
pub fn new_order_single(symbol: String, price: f64, quantity: f64, side: String) -> Self {
let mut message = Self::new(MessageType::NewOrderSingle);
message.set_field(55, FieldValue::String(symbol)); message.set_field(44, FieldValue::Float(price)); message.set_field(38, FieldValue::Float(quantity)); message.set_field(54, FieldValue::String(side)); message.set_field(40, FieldValue::String("2".to_string())); message.set_field(59, FieldValue::String("0".to_string()));
message
}
pub fn execution_report(
order_id: String,
exec_id: String,
exec_type: String,
ord_status: String,
symbol: String,
side: String,
exec_qty: f64,
exec_price: f64,
) -> Self {
let mut message = Self::new(MessageType::ExecutionReport);
message.set_field(37, FieldValue::String(order_id)); message.set_field(17, FieldValue::String(exec_id)); message.set_field(150, FieldValue::String(exec_type)); message.set_field(39, FieldValue::String(ord_status)); message.set_field(55, FieldValue::String(symbol)); message.set_field(54, FieldValue::String(side)); message.set_field(32, FieldValue::Float(exec_qty)); message.set_field(31, FieldValue::Float(exec_price));
message
}
pub fn market_data_request(
md_req_id: String,
subscription_type: String,
market_depth: u32,
symbols: Vec<String>,
) -> Self {
let mut message = Self::new(MessageType::MarketDataRequest);
message.set_field(262, FieldValue::String(md_req_id)); message.set_field(263, FieldValue::String(subscription_type)); message.set_field(264, FieldValue::UInt(market_depth as u64));
message.set_field(146, FieldValue::UInt(symbols.len() as u64)); for (i, symbol) in symbols.into_iter().enumerate() {
message.set_field(55 + (i as u32) * 1000, FieldValue::String(symbol));
}
message
}
pub fn set_field(&mut self, tag: u32, value: FieldValue) {
self.fields.insert(tag, value);
}
pub fn get_field(&self, tag: u32) -> Option<&FieldValue> {
self.fields.get(&tag)
}
pub fn remove_field(&mut self, tag: u32) -> Option<FieldValue> {
self.fields.remove(&tag)
}
pub fn has_field(&self, tag: u32) -> bool {
self.fields.contains_key(&tag)
}
pub fn field_tags(&self) -> impl Iterator<Item = u32> + '_ {
self.fields.keys().copied()
}
pub fn with_seq_num(mut self, seq_num: u32) -> Self {
self.seq_num = Some(seq_num);
self
}
pub fn with_sender_comp_id(mut self, sender_comp_id: String) -> Self {
self.sender_comp_id = Some(sender_comp_id);
self
}
pub fn with_target_comp_id(mut self, target_comp_id: String) -> Self {
self.target_comp_id = Some(target_comp_id);
self
}
pub fn with_sending_time(mut self, sending_time: u64) -> Self {
self.sending_time = Some(sending_time);
self
}
pub fn validate(&self) -> Result<(), EncodeError> {
match self.message_type {
MessageType::NewOrderSingle => {
self.require_field(55)?; self.require_field(54)?; self.require_field(38)?; self.require_field(40)?; }
MessageType::ExecutionReport => {
self.require_field(37)?; self.require_field(17)?; self.require_field(150)?; self.require_field(39)?; self.require_field(55)?; self.require_field(54)?; }
MessageType::MarketDataRequest => {
self.require_field(262)?; self.require_field(263)?; }
_ => {}
}
Ok(())
}
fn require_field(&self, tag: u32) -> Result<(), EncodeError> {
if !self.has_field(tag) {
return Err(EncodeError::MissingRequiredField { field_id: tag });
}
Ok(())
}
pub fn estimated_size(&self) -> usize {
let mut size = 100;
for (_tag, value) in &self.fields {
size += 8; size += match value {
FieldValue::String(s) => s.len() + 4,
FieldValue::Int(_) | FieldValue::UInt(_) => 12,
FieldValue::Float(_) => 12,
FieldValue::Bool(_) => 4,
FieldValue::Bytes(b) => b.len() + 4,
FieldValue::Decimal { .. } => 16,
FieldValue::Timestamp(_) => 12,
FieldValue::Optional(Some(v)) => v.estimated_size(),
FieldValue::Optional(None) => 0,
};
}
size
}
}
impl FieldValue {
fn estimated_size(&self) -> usize {
match self {
FieldValue::String(s) => s.len() + 4,
FieldValue::Int(_) | FieldValue::UInt(_) => 12,
FieldValue::Float(_) => 12,
FieldValue::Bool(_) => 4,
FieldValue::Bytes(b) => b.len() + 4,
FieldValue::Decimal { .. } => 16,
FieldValue::Timestamp(_) => 12,
FieldValue::Optional(Some(v)) => v.estimated_size(),
FieldValue::Optional(None) => 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_type_conversion() {
assert_eq!(MessageType::NewOrderSingle.as_str(), "D");
assert_eq!(MessageType::from_str("D"), MessageType::NewOrderSingle);
assert_eq!(MessageType::ExecutionReport.as_str(), "8");
assert_eq!(MessageType::from_str("8"), MessageType::ExecutionReport);
}
#[test]
fn test_new_order_single_creation() {
let message = FixMessage::new_order_single(
"BTCUSD".to_string(),
50000.0,
1.5,
"1".to_string(), );
assert_eq!(message.message_type, MessageType::NewOrderSingle);
assert_eq!(
message.get_field(55).unwrap().as_string().unwrap(),
"BTCUSD"
);
assert_eq!(message.get_field(44).unwrap().as_float().unwrap(), 50000.0);
assert_eq!(message.get_field(38).unwrap().as_float().unwrap(), 1.5);
assert_eq!(message.get_field(54).unwrap().as_string().unwrap(), "1");
}
#[test]
fn test_field_value_conversions() {
let string_val = FieldValue::String("test".to_string());
assert_eq!(string_val.as_string().unwrap(), "test");
let int_val = FieldValue::Int(42);
assert_eq!(int_val.as_int().unwrap(), 42);
assert_eq!(int_val.as_float().unwrap(), 42.0);
let decimal_val = FieldValue::Decimal {
mantissa: 12345,
scale: 2,
};
assert_eq!(decimal_val.as_float().unwrap(), 123.45);
}
#[test]
fn test_message_validation() {
let mut message = FixMessage::new(MessageType::NewOrderSingle);
assert!(message.validate().is_err());
message.set_field(55, FieldValue::String("BTCUSD".to_string()));
message.set_field(54, FieldValue::String("1".to_string()));
message.set_field(38, FieldValue::Float(1.0));
message.set_field(40, FieldValue::String("2".to_string()));
assert!(message.validate().is_ok());
}
#[test]
fn test_optional_field_values() {
let present = FieldValue::Optional(Some(Box::new(FieldValue::String("test".to_string()))));
let absent = FieldValue::Optional(None);
assert!(present.is_present());
assert!(!absent.is_present());
}
#[test]
fn test_execution_report_creation() {
let message = FixMessage::execution_report(
"ORDER123".to_string(),
"EXEC456".to_string(),
"F".to_string(), "2".to_string(), "BTCUSD".to_string(),
"1".to_string(), 1.0,
50000.0,
);
assert_eq!(message.message_type, MessageType::ExecutionReport);
assert_eq!(
message.get_field(37).unwrap().as_string().unwrap(),
"ORDER123"
);
assert_eq!(
message.get_field(17).unwrap().as_string().unwrap(),
"EXEC456"
);
}
}