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 MdSubscriptionRequestType {
Snapshot = 0,
SnapshotPlusUpdates = 1,
Unsubscribe = 2,
}
impl From<MdSubscriptionRequestType> for i32 {
fn from(value: MdSubscriptionRequestType) -> Self {
value as i32
}
}
impl TryFrom<i32> for MdSubscriptionRequestType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(MdSubscriptionRequestType::Snapshot),
1 => Ok(MdSubscriptionRequestType::SnapshotPlusUpdates),
2 => Ok(MdSubscriptionRequestType::Unsubscribe),
_ => Err(format!("Invalid MdSubscriptionRequestType: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MdUpdateType {
FullRefresh = 0,
IncrementalRefresh = 1,
}
impl From<MdUpdateType> for i32 {
fn from(value: MdUpdateType) -> Self {
value as i32
}
}
impl TryFrom<i32> for MdUpdateType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(MdUpdateType::FullRefresh),
1 => Ok(MdUpdateType::IncrementalRefresh),
_ => Err(format!("Invalid MdUpdateType: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MdEntryType {
Bid = 0,
Offer = 1,
Trade = 2,
IndexValue = 3,
SettlementPrice = 6,
}
impl From<MdEntryType> for i32 {
fn from(value: MdEntryType) -> Self {
value as i32
}
}
impl TryFrom<i32> for MdEntryType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(MdEntryType::Bid),
1 => Ok(MdEntryType::Offer),
2 => Ok(MdEntryType::Trade),
3 => Ok(MdEntryType::IndexValue),
6 => Ok(MdEntryType::SettlementPrice),
_ => Err(format!("Invalid MdEntryType: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MdUpdateAction {
New = 0,
Change = 1,
Delete = 2,
}
impl From<MdUpdateAction> for char {
fn from(value: MdUpdateAction) -> Self {
match value {
MdUpdateAction::New => '0',
MdUpdateAction::Change => '1',
MdUpdateAction::Delete => '2',
}
}
}
impl TryFrom<char> for MdUpdateAction {
type Error = String;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'0' => Ok(MdUpdateAction::New),
'1' => Ok(MdUpdateAction::Change),
'2' => Ok(MdUpdateAction::Delete),
_ => Err(format!("Invalid MdUpdateAction: {value}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MdReqRejReason {
UnknownSymbol = 0,
DuplicateMdReqId = 1,
InsufficientBandwidth = 2,
InsufficientPermissions = 3,
UnsupportedSubscriptionRequestType = 4,
UnsupportedMarketDepth = 5,
UnsupportedMdUpdateType = 6,
UnsupportedAggregatedBook = 7,
UnsupportedMdEntryType = 8,
UnsupportedTradingSessionId = 9,
UnsupportedScope = 10,
UnsupportedOpenCloseSettlFlag = 11,
UnsupportedMdImplicitDelete = 12,
InsufficientCredit = 13,
}
impl From<MdReqRejReason> for char {
fn from(value: MdReqRejReason) -> Self {
match value {
MdReqRejReason::UnknownSymbol => '0',
MdReqRejReason::DuplicateMdReqId => '1',
MdReqRejReason::InsufficientBandwidth => '2',
MdReqRejReason::InsufficientPermissions => '3',
MdReqRejReason::UnsupportedSubscriptionRequestType => '4',
MdReqRejReason::UnsupportedMarketDepth => '5',
MdReqRejReason::UnsupportedMdUpdateType => '6',
MdReqRejReason::UnsupportedAggregatedBook => '7',
MdReqRejReason::UnsupportedMdEntryType => '8',
MdReqRejReason::UnsupportedTradingSessionId => '9',
MdReqRejReason::UnsupportedScope => 'A',
MdReqRejReason::UnsupportedOpenCloseSettlFlag => 'B',
MdReqRejReason::UnsupportedMdImplicitDelete => 'C',
MdReqRejReason::InsufficientCredit => 'D',
}
}
}
impl TryFrom<char> for MdReqRejReason {
type Error = String;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'0' => Ok(MdReqRejReason::UnknownSymbol),
'1' => Ok(MdReqRejReason::DuplicateMdReqId),
'2' => Ok(MdReqRejReason::InsufficientBandwidth),
'3' => Ok(MdReqRejReason::InsufficientPermissions),
'4' => Ok(MdReqRejReason::UnsupportedSubscriptionRequestType),
'5' => Ok(MdReqRejReason::UnsupportedMarketDepth),
'6' => Ok(MdReqRejReason::UnsupportedMdUpdateType),
'7' => Ok(MdReqRejReason::UnsupportedAggregatedBook),
'8' => Ok(MdReqRejReason::UnsupportedMdEntryType),
'9' => Ok(MdReqRejReason::UnsupportedTradingSessionId),
'A' => Ok(MdReqRejReason::UnsupportedScope),
'B' => Ok(MdReqRejReason::UnsupportedOpenCloseSettlFlag),
'C' => Ok(MdReqRejReason::UnsupportedMdImplicitDelete),
'D' => Ok(MdReqRejReason::InsufficientCredit),
_ => Err(format!("Invalid MdReqRejReason: {value}")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketDataRequest {
pub md_req_id: String,
pub subscription_request_type: MdSubscriptionRequestType,
pub market_depth: Option<i32>,
pub md_update_type: Option<MdUpdateType>,
pub skip_block_trades: Option<bool>,
pub show_block_trade_id: Option<bool>,
pub trade_amount: Option<i32>,
pub since_timestamp: Option<i64>,
pub entry_types: Vec<MdEntryType>,
pub symbols: Vec<String>,
}
impl MarketDataRequest {
pub fn snapshot(
md_req_id: String,
symbols: Vec<String>,
entry_types: Vec<MdEntryType>,
) -> Self {
Self {
md_req_id,
subscription_request_type: MdSubscriptionRequestType::Snapshot,
market_depth: None,
md_update_type: None,
skip_block_trades: None,
show_block_trade_id: None,
trade_amount: None,
since_timestamp: None,
entry_types,
symbols,
}
}
pub fn subscription(
md_req_id: String,
symbols: Vec<String>,
entry_types: Vec<MdEntryType>,
md_update_type: MdUpdateType,
) -> Self {
Self {
md_req_id,
subscription_request_type: MdSubscriptionRequestType::SnapshotPlusUpdates,
market_depth: None,
md_update_type: Some(md_update_type),
skip_block_trades: None,
show_block_trade_id: None,
trade_amount: None,
since_timestamp: None,
entry_types,
symbols,
}
}
pub fn unsubscribe(md_req_id: String) -> Self {
Self {
md_req_id,
subscription_request_type: MdSubscriptionRequestType::Unsubscribe,
market_depth: None,
md_update_type: None,
skip_block_trades: None,
show_block_trade_id: None,
trade_amount: None,
since_timestamp: None,
entry_types: Vec::new(),
symbols: Vec::new(),
}
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<String> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::MarketDataRequest)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.sending_time(Utc::now())
.field(262, self.md_req_id.clone()) .field(263, i32::from(self.subscription_request_type).to_string());
if let Some(depth) = self.market_depth {
builder = builder.field(264, depth.to_string()); }
if let Some(update_type) = self.md_update_type {
builder = builder.field(265, i32::from(update_type).to_string()); }
builder = builder.field(267, self.entry_types.len().to_string()); for entry_type in &self.entry_types {
builder = builder.field(269, i32::from(*entry_type).to_string()); }
if let Some(skip_block_trades) = self.skip_block_trades {
builder = builder.field(9011, if skip_block_trades { "Y" } else { "N" }.to_string()); }
if let Some(show_block_trade_id) = self.show_block_trade_id {
builder = builder.field(
9012,
if show_block_trade_id { "Y" } else { "N" }.to_string(),
); }
if let Some(trade_amount) = self.trade_amount {
builder = builder.field(100007, trade_amount.to_string()); }
if let Some(since_timestamp) = self.since_timestamp {
builder = builder.field(100008, since_timestamp.to_string()); }
if !self.symbols.is_empty() {
builder = builder.field(146, self.symbols.len().to_string()); for symbol in &self.symbols {
builder = builder.field(55, symbol.clone()); }
}
Ok(builder.build()?.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketDataRequestReject {
pub md_req_id: String,
pub md_req_rej_reason: MdReqRejReason,
pub text: Option<String>,
}
impl MarketDataRequestReject {
pub fn new(md_req_id: String, reason: MdReqRejReason) -> Self {
Self {
md_req_id,
md_req_rej_reason: reason,
text: None,
}
}
pub fn with_text(md_req_id: String, reason: MdReqRejReason, text: String) -> Self {
Self {
md_req_id,
md_req_rej_reason: reason,
text: Some(text),
}
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<String> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::MarketDataRequestReject)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.sending_time(Utc::now())
.field(262, self.md_req_id.clone()) .field(281, char::from(self.md_req_rej_reason).to_string());
if let Some(ref text) = self.text {
builder = builder.field(58, text.clone()); }
Ok(builder.build()?.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MdEntry {
pub md_entry_type: MdEntryType,
pub md_entry_px: Option<f64>,
pub md_entry_size: Option<f64>,
pub md_entry_date: Option<DateTime<Utc>>,
pub md_update_action: Option<MdUpdateAction>,
pub trade_id: Option<String>,
pub side: Option<char>,
pub order_id: Option<String>,
pub secondary_order_id: Option<String>,
pub price: Option<f64>,
pub text: Option<String>,
pub ord_status: Option<char>,
pub deribit_label: Option<String>,
pub deribit_liquidation: Option<String>,
pub trd_match_id: Option<String>,
}
impl MdEntry {
pub fn bid(price: f64, size: f64) -> Self {
Self {
md_entry_type: MdEntryType::Bid,
md_entry_px: Some(price),
md_entry_size: Some(size),
md_entry_date: None,
md_update_action: None,
trade_id: None,
side: None,
order_id: None,
secondary_order_id: None,
price: None,
text: None,
ord_status: None,
deribit_label: None,
deribit_liquidation: None,
trd_match_id: None,
}
}
pub fn offer(price: f64, size: f64) -> Self {
Self {
md_entry_type: MdEntryType::Offer,
md_entry_px: Some(price),
md_entry_size: Some(size),
md_entry_date: None,
md_update_action: None,
trade_id: None,
side: None,
order_id: None,
secondary_order_id: None,
price: None,
text: None,
ord_status: None,
deribit_label: None,
deribit_liquidation: None,
trd_match_id: None,
}
}
pub fn trade(
price: f64,
size: f64,
side: char,
trade_id: String,
timestamp: DateTime<Utc>,
) -> Self {
Self {
md_entry_type: MdEntryType::Trade,
md_entry_px: Some(price),
md_entry_size: Some(size),
md_entry_date: Some(timestamp),
md_update_action: None,
trade_id: Some(trade_id),
side: Some(side),
order_id: None,
secondary_order_id: None,
price: None,
text: None,
ord_status: None,
deribit_label: None,
deribit_liquidation: None,
trd_match_id: None,
}
}
pub fn with_update_action(mut self, action: MdUpdateAction) -> Self {
self.md_update_action = Some(action);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketDataSnapshotFullRefresh {
pub symbol: String,
pub md_req_id: Option<String>,
pub underlying_symbol: Option<String>,
pub underlying_px: Option<f64>,
pub contract_multiplier: Option<f64>,
pub put_or_call: Option<i32>,
pub trade_volume_24h: Option<f64>,
pub mark_price: Option<f64>,
pub open_interest: Option<f64>,
pub current_funding: Option<f64>,
pub funding_8h: Option<f64>,
pub entries: Vec<MdEntry>,
}
impl MarketDataSnapshotFullRefresh {
pub fn new(symbol: String) -> Self {
Self {
symbol,
md_req_id: None,
underlying_symbol: None,
underlying_px: None,
contract_multiplier: None,
put_or_call: None,
trade_volume_24h: None,
mark_price: None,
open_interest: None,
current_funding: None,
funding_8h: None,
entries: Vec::new(),
}
}
pub fn with_request_id(mut self, md_req_id: String) -> Self {
self.md_req_id = Some(md_req_id);
self
}
pub fn with_entries(mut self, entries: Vec<MdEntry>) -> Self {
self.entries = entries;
self
}
pub fn with_underlying_symbol(mut self, underlying_symbol: String) -> Self {
self.underlying_symbol = Some(underlying_symbol);
self
}
pub fn with_underlying_px(mut self, underlying_px: f64) -> Self {
self.underlying_px = Some(underlying_px);
self
}
pub fn with_contract_multiplier(mut self, contract_multiplier: f64) -> Self {
self.contract_multiplier = Some(contract_multiplier);
self
}
pub fn with_put_or_call(mut self, put_or_call: i32) -> Self {
self.put_or_call = Some(put_or_call);
self
}
pub fn with_trade_volume_24h(mut self, trade_volume_24h: f64) -> Self {
self.trade_volume_24h = Some(trade_volume_24h);
self
}
pub fn with_mark_price(mut self, mark_price: f64) -> Self {
self.mark_price = Some(mark_price);
self
}
pub fn with_open_interest(mut self, open_interest: f64) -> Self {
self.open_interest = Some(open_interest);
self
}
pub fn with_current_funding(mut self, current_funding: f64) -> Self {
self.current_funding = Some(current_funding);
self
}
pub fn with_funding_8h(mut self, funding_8h: f64) -> Self {
self.funding_8h = Some(funding_8h);
self
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<String> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::MarketDataSnapshotFullRefresh)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.sending_time(Utc::now())
.field(55, self.symbol.clone());
if let Some(ref md_req_id) = self.md_req_id {
builder = builder.field(262, md_req_id.clone()); }
if let Some(ref underlying_symbol) = self.underlying_symbol {
builder = builder.field(311, underlying_symbol.clone()); }
if let Some(underlying_px) = self.underlying_px {
builder = builder.field(810, underlying_px.to_string()); }
if let Some(contract_multiplier) = self.contract_multiplier {
builder = builder.field(231, contract_multiplier.to_string()); }
if let Some(put_or_call) = self.put_or_call {
builder = builder.field(201, put_or_call.to_string()); }
if let Some(trade_volume_24h) = self.trade_volume_24h {
builder = builder.field(100087, trade_volume_24h.to_string()); }
if let Some(mark_price) = self.mark_price {
builder = builder.field(100090, mark_price.to_string()); }
if let Some(open_interest) = self.open_interest {
builder = builder.field(746, open_interest.to_string()); }
if let Some(current_funding) = self.current_funding {
builder = builder.field(100092, current_funding.to_string()); }
if let Some(funding_8h) = self.funding_8h {
builder = builder.field(100093, funding_8h.to_string()); }
builder = builder.field(268, self.entries.len().to_string());
for entry in &self.entries {
builder = builder.field(269, i32::from(entry.md_entry_type).to_string());
if let Some(px) = entry.md_entry_px {
builder = builder.field(270, px.to_string()); }
if let Some(size) = entry.md_entry_size {
builder = builder.field(271, size.to_string()); }
if let Some(date) = entry.md_entry_date {
builder = builder.field(272, date.timestamp_millis().to_string()); }
if let Some(ref trade_id) = entry.trade_id {
builder = builder.field(100009, trade_id.clone()); }
if let Some(side) = entry.side {
builder = builder.field(54, side.to_string()); }
if let Some(price) = entry.price {
builder = builder.field(44, price.to_string()); }
if let Some(ref text) = entry.text {
builder = builder.field(58, text.clone()); }
if let Some(ref order_id) = entry.order_id {
builder = builder.field(37, order_id.clone()); }
if let Some(ref secondary_order_id) = entry.secondary_order_id {
builder = builder.field(198, secondary_order_id.clone()); }
if let Some(ord_status) = entry.ord_status {
builder = builder.field(39, ord_status.to_string()); }
if let Some(ref deribit_label) = entry.deribit_label {
builder = builder.field(100010, deribit_label.clone()); }
if let Some(ref deribit_liquidation) = entry.deribit_liquidation {
builder = builder.field(100091, deribit_liquidation.clone()); }
if let Some(ref trd_match_id) = entry.trd_match_id {
builder = builder.field(880, trd_match_id.clone()); }
}
Ok(builder.build()?.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketDataIncrementalRefresh {
pub symbol: String,
pub md_req_id: Option<String>,
pub entries: Vec<MdEntry>,
}
impl MarketDataIncrementalRefresh {
pub fn new(symbol: String) -> Self {
Self {
symbol,
md_req_id: None,
entries: Vec::new(),
}
}
pub fn with_request_id(mut self, md_req_id: String) -> Self {
self.md_req_id = Some(md_req_id);
self
}
pub fn with_entries(mut self, entries: Vec<MdEntry>) -> Self {
self.entries = entries;
self
}
pub fn to_fix_message(
&self,
sender_comp_id: String,
target_comp_id: String,
msg_seq_num: u32,
) -> DeribitFixResult<String> {
let mut builder = MessageBuilder::new()
.msg_type(MsgType::MarketDataIncrementalRefresh)
.sender_comp_id(sender_comp_id)
.target_comp_id(target_comp_id)
.msg_seq_num(msg_seq_num)
.sending_time(Utc::now())
.field(55, self.symbol.clone());
if let Some(ref md_req_id) = self.md_req_id {
builder = builder.field(262, md_req_id.clone()); }
builder = builder.field(268, self.entries.len().to_string());
for entry in &self.entries {
if let Some(action) = entry.md_update_action {
builder = builder.field(279, char::from(action).to_string()); }
builder = builder.field(269, i32::from(entry.md_entry_type).to_string());
if let Some(px) = entry.md_entry_px {
builder = builder.field(270, px.to_string()); }
if let Some(size) = entry.md_entry_size {
builder = builder.field(271, size.to_string()); }
if let Some(date) = entry.md_entry_date {
builder = builder.field(272, date.timestamp_millis().to_string()); }
if let Some(ref trade_id) = entry.trade_id {
builder = builder.field(100009, trade_id.clone()); }
if let Some(side) = entry.side {
builder = builder.field(54, side.to_string()); }
}
Ok(builder.build()?.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_md_subscription_request_type_conversion() {
assert_eq!(i32::from(MdSubscriptionRequestType::Snapshot), 0);
assert_eq!(i32::from(MdSubscriptionRequestType::SnapshotPlusUpdates), 1);
assert_eq!(i32::from(MdSubscriptionRequestType::Unsubscribe), 2);
assert_eq!(
MdSubscriptionRequestType::try_from(0).unwrap(),
MdSubscriptionRequestType::Snapshot
);
assert_eq!(
MdSubscriptionRequestType::try_from(1).unwrap(),
MdSubscriptionRequestType::SnapshotPlusUpdates
);
assert_eq!(
MdSubscriptionRequestType::try_from(2).unwrap(),
MdSubscriptionRequestType::Unsubscribe
);
assert!(MdSubscriptionRequestType::try_from(99).is_err());
}
#[test]
fn test_md_entry_type_conversion() {
assert_eq!(i32::from(MdEntryType::Bid), 0);
assert_eq!(i32::from(MdEntryType::Offer), 1);
assert_eq!(i32::from(MdEntryType::Trade), 2);
assert_eq!(MdEntryType::try_from(0).unwrap(), MdEntryType::Bid);
assert_eq!(MdEntryType::try_from(1).unwrap(), MdEntryType::Offer);
assert_eq!(MdEntryType::try_from(2).unwrap(), MdEntryType::Trade);
assert!(MdEntryType::try_from(99).is_err());
}
#[test]
fn test_market_data_request_creation() {
let request = MarketDataRequest::snapshot(
"REQ123".to_string(),
vec!["BTC-PERPETUAL".to_string()],
vec![MdEntryType::Bid, MdEntryType::Offer],
);
assert_eq!(request.md_req_id, "REQ123");
assert_eq!(
request.subscription_request_type,
MdSubscriptionRequestType::Snapshot
);
assert_eq!(request.symbols.len(), 1);
assert_eq!(request.entry_types.len(), 2);
}
#[test]
fn test_market_data_request_reject_creation() {
let reject =
MarketDataRequestReject::new("REQ123".to_string(), MdReqRejReason::UnknownSymbol);
assert_eq!(reject.md_req_id, "REQ123");
assert_eq!(reject.md_req_rej_reason, MdReqRejReason::UnknownSymbol);
assert!(reject.text.is_none());
}
#[test]
fn test_md_req_rej_reason_conversion() {
assert_eq!(char::from(MdReqRejReason::UnknownSymbol), '0');
assert_eq!(char::from(MdReqRejReason::InsufficientCredit), 'D');
assert_eq!(
MdReqRejReason::try_from('0').unwrap(),
MdReqRejReason::UnknownSymbol
);
assert_eq!(
MdReqRejReason::try_from('D').unwrap(),
MdReqRejReason::InsufficientCredit
);
assert!(MdReqRejReason::try_from('Z').is_err());
}
#[test]
fn test_md_entry_creation() {
let bid = MdEntry::bid(50000.0, 1.5);
assert_eq!(bid.md_entry_type, MdEntryType::Bid);
assert_eq!(bid.md_entry_px, Some(50000.0));
assert_eq!(bid.md_entry_size, Some(1.5));
assert!(bid.md_update_action.is_none());
let offer = MdEntry::offer(50100.0, 2.0);
assert_eq!(offer.md_entry_type, MdEntryType::Offer);
assert_eq!(offer.md_entry_px, Some(50100.0));
assert_eq!(offer.md_entry_size, Some(2.0));
}
#[test]
fn test_market_data_snapshot_creation() {
let snapshot = MarketDataSnapshotFullRefresh::new("BTC-PERPETUAL".to_string())
.with_request_id("REQ123".to_string())
.with_entries(vec![
MdEntry::bid(50000.0, 1.0),
MdEntry::offer(50100.0, 1.5),
]);
assert_eq!(snapshot.symbol, "BTC-PERPETUAL");
assert_eq!(snapshot.md_req_id, Some("REQ123".to_string()));
assert_eq!(snapshot.entries.len(), 2);
}
#[test]
fn test_market_data_incremental_creation() {
let incremental = MarketDataIncrementalRefresh::new("BTC-PERPETUAL".to_string())
.with_entries(vec![
MdEntry::bid(50000.0, 1.0).with_update_action(MdUpdateAction::New),
MdEntry::offer(50100.0, 1.5).with_update_action(MdUpdateAction::Change),
]);
assert_eq!(incremental.symbol, "BTC-PERPETUAL");
assert_eq!(incremental.entries.len(), 2);
assert_eq!(
incremental.entries[0].md_update_action,
Some(MdUpdateAction::New)
);
assert_eq!(
incremental.entries[1].md_update_action,
Some(MdUpdateAction::Change)
);
}
#[test]
fn test_md_update_action_conversion() {
assert_eq!(char::from(MdUpdateAction::New), '0');
assert_eq!(char::from(MdUpdateAction::Change), '1');
assert_eq!(char::from(MdUpdateAction::Delete), '2');
assert_eq!(MdUpdateAction::try_from('0').unwrap(), MdUpdateAction::New);
assert_eq!(
MdUpdateAction::try_from('1').unwrap(),
MdUpdateAction::Change
);
assert_eq!(
MdUpdateAction::try_from('2').unwrap(),
MdUpdateAction::Delete
);
assert!(MdUpdateAction::try_from('9').is_err());
}
}