#![warn(missing_docs)]
use crate::error::{DeribitFixError, Result as DeribitFixResult};
use crate::message::MessageBuilder;
use crate::model::message::FixMessage;
use crate::model::types::MsgType;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SecurityStatusRequest {
pub security_status_req_id: String,
pub symbol: String,
pub subscription_request_type: char,
}
impl SecurityStatusRequest {
pub fn new(
security_status_req_id: String,
symbol: String,
subscription_request_type: char,
) -> Self {
Self {
security_status_req_id,
symbol,
subscription_request_type,
}
}
pub fn snapshot(security_status_req_id: String, symbol: String) -> Self {
Self::new(security_status_req_id, symbol, '0')
}
pub fn subscribe(security_status_req_id: String, symbol: String) -> Self {
Self::new(security_status_req_id, symbol, '1')
}
pub fn unsubscribe(security_status_req_id: String, symbol: String) -> Self {
Self::new(security_status_req_id, symbol, '2')
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<FixMessage> {
let builder = MessageBuilder::new()
.msg_type(MsgType::SecurityStatusRequest)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.field(324, self.security_status_req_id.clone()) .field(55, self.symbol.clone()) .field(263, self.subscription_request_type.to_string());
builder.build()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SecurityStatus {
pub security_status_req_id: Option<String>,
pub symbol: String,
pub security_trading_status: Option<i32>,
pub buy_volume: Option<f64>,
pub sell_volume: Option<f64>,
pub high_px: Option<f64>,
pub low_px: Option<f64>,
pub last_px: Option<f64>,
pub text: Option<String>,
}
impl SecurityStatus {
pub fn new(symbol: String) -> Self {
Self {
security_status_req_id: None,
symbol,
security_trading_status: None,
buy_volume: None,
sell_volume: None,
high_px: None,
low_px: None,
last_px: None,
text: None,
}
}
pub fn with_security_status_req_id(mut self, req_id: String) -> Self {
self.security_status_req_id = Some(req_id);
self
}
pub fn with_trading_status(mut self, status: i32) -> Self {
self.security_trading_status = Some(status);
self
}
pub fn with_buy_volume(mut self, volume: f64) -> Self {
self.buy_volume = Some(volume);
self
}
pub fn with_sell_volume(mut self, volume: f64) -> Self {
self.sell_volume = Some(volume);
self
}
pub fn with_high_px(mut self, price: f64) -> Self {
self.high_px = Some(price);
self
}
pub fn with_low_px(mut self, price: f64) -> Self {
self.low_px = Some(price);
self
}
pub fn with_last_px(mut self, price: f64) -> Self {
self.last_px = Some(price);
self
}
pub fn with_text(mut self, text: String) -> Self {
self.text = Some(text);
self
}
pub fn from_fix_message(message: &FixMessage) -> DeribitFixResult<Self> {
let symbol = message
.get_field(55)
.ok_or_else(|| DeribitFixError::MessageParsing("Symbol (55) is required".to_string()))?
.clone();
let mut security_status = Self::new(symbol);
if let Some(req_id) = message.get_field(324) {
security_status.security_status_req_id = Some(req_id.clone());
}
if let Some(status_str) = message.get_field(326)
&& let Ok(status) = status_str.parse::<i32>()
{
security_status.security_trading_status = Some(status);
}
if let Some(buy_vol_str) = message.get_field(330)
&& let Ok(buy_vol) = buy_vol_str.parse::<f64>()
{
security_status.buy_volume = Some(buy_vol);
}
if let Some(sell_vol_str) = message.get_field(331)
&& let Ok(sell_vol) = sell_vol_str.parse::<f64>()
{
security_status.sell_volume = Some(sell_vol);
}
if let Some(high_str) = message.get_field(332)
&& let Ok(high) = high_str.parse::<f64>()
{
security_status.high_px = Some(high);
}
if let Some(low_str) = message.get_field(333)
&& let Ok(low) = low_str.parse::<f64>()
{
security_status.low_px = Some(low);
}
if let Some(last_str) = message.get_field(31)
&& let Ok(last) = last_str.parse::<f64>()
{
security_status.last_px = Some(last);
}
if let Some(text) = message.get_field(58) {
security_status.text = Some(text.clone());
}
Ok(security_status)
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<FixMessage> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::SecurityStatus)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.field(55, self.symbol.clone());
if let Some(ref req_id) = self.security_status_req_id {
builder = builder.field(324, req_id.clone()); }
if let Some(status) = self.security_trading_status {
builder = builder.field(326, status.to_string()); }
if let Some(buy_vol) = self.buy_volume {
builder = builder.field(330, buy_vol.to_string()); }
if let Some(sell_vol) = self.sell_volume {
builder = builder.field(331, sell_vol.to_string()); }
if let Some(high) = self.high_px {
builder = builder.field(332, high.to_string()); }
if let Some(low) = self.low_px {
builder = builder.field(333, low.to_string()); }
if let Some(last) = self.last_px {
builder = builder.field(31, last.to_string()); }
if let Some(ref text) = self.text {
builder = builder.field(58, text.clone()); }
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_status_request_creation() {
let req =
SecurityStatusRequest::new("REQ123".to_string(), "BTC-PERPETUAL".to_string(), '0');
assert_eq!(req.security_status_req_id, "REQ123");
assert_eq!(req.symbol, "BTC-PERPETUAL");
assert_eq!(req.subscription_request_type, '0');
}
#[test]
fn test_security_status_request_builders() {
let snapshot =
SecurityStatusRequest::snapshot("REQ1".to_string(), "BTC-PERPETUAL".to_string());
assert_eq!(snapshot.subscription_request_type, '0');
let subscribe =
SecurityStatusRequest::subscribe("REQ2".to_string(), "BTC-PERPETUAL".to_string());
assert_eq!(subscribe.subscription_request_type, '1');
let unsubscribe =
SecurityStatusRequest::unsubscribe("REQ3".to_string(), "BTC-PERPETUAL".to_string());
assert_eq!(unsubscribe.subscription_request_type, '2');
}
#[test]
fn test_security_status_request_to_fix_message() {
let req =
SecurityStatusRequest::snapshot("REQ123".to_string(), "BTC-PERPETUAL".to_string());
let fix_msg = req
.to_fix_message("CLIENT".to_string(), "DERIBIT".to_string(), 1)
.unwrap();
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"); }
#[test]
fn test_security_status_creation() {
let status = SecurityStatus::new("BTC-PERPETUAL".to_string())
.with_trading_status(7)
.with_buy_volume(100.0)
.with_sell_volume(200.0)
.with_high_px(50000.0)
.with_low_px(49000.0)
.with_last_px(49500.0)
.with_text("success".to_string());
assert_eq!(status.symbol, "BTC-PERPETUAL");
assert_eq!(status.security_trading_status, Some(7));
assert_eq!(status.buy_volume, Some(100.0));
assert_eq!(status.sell_volume, Some(200.0));
assert_eq!(status.high_px, Some(50000.0));
assert_eq!(status.low_px, Some(49000.0));
assert_eq!(status.last_px, Some(49500.0));
assert_eq!(status.text, Some("success".to_string()));
}
#[test]
fn test_security_status_to_fix_message() {
let status = SecurityStatus::new("BTC-PERPETUAL".to_string())
.with_security_status_req_id("REQ123".to_string())
.with_trading_status(7)
.with_last_px(49500.0);
let fix_msg = status
.to_fix_message("DERIBIT".to_string(), "CLIENT".to_string(), 2)
.unwrap();
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"); }
}