1use crate::error::Result as DeribitFixResult;
18use crate::message::MessageBuilder;
19use crate::model::types::MsgType;
20use chrono::{DateTime, Utc};
21use serde::{Deserialize, Serialize};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25pub enum MdSubscriptionRequestType {
26 Snapshot = 0,
28 SnapshotPlusUpdates = 1,
30 Unsubscribe = 2,
32}
33
34impl From<MdSubscriptionRequestType> for i32 {
35 fn from(value: MdSubscriptionRequestType) -> Self {
36 value as i32
37 }
38}
39
40impl TryFrom<i32> for MdSubscriptionRequestType {
41 type Error = String;
42
43 fn try_from(value: i32) -> Result<Self, Self::Error> {
44 match value {
45 0 => Ok(MdSubscriptionRequestType::Snapshot),
46 1 => Ok(MdSubscriptionRequestType::SnapshotPlusUpdates),
47 2 => Ok(MdSubscriptionRequestType::Unsubscribe),
48 _ => Err(format!("Invalid MdSubscriptionRequestType: {value}")),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55pub enum MdUpdateType {
56 FullRefresh = 0,
58 IncrementalRefresh = 1,
60}
61
62impl From<MdUpdateType> for i32 {
63 fn from(value: MdUpdateType) -> Self {
64 value as i32
65 }
66}
67
68impl TryFrom<i32> for MdUpdateType {
69 type Error = String;
70
71 fn try_from(value: i32) -> Result<Self, Self::Error> {
72 match value {
73 0 => Ok(MdUpdateType::FullRefresh),
74 1 => Ok(MdUpdateType::IncrementalRefresh),
75 _ => Err(format!("Invalid MdUpdateType: {value}")),
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum MdEntryType {
83 Bid = 0,
85 Offer = 1,
87 Trade = 2,
89 IndexValue = 3,
91 SettlementPrice = 6,
93}
94
95impl From<MdEntryType> for i32 {
96 fn from(value: MdEntryType) -> Self {
97 value as i32
98 }
99}
100
101impl TryFrom<i32> for MdEntryType {
102 type Error = String;
103
104 fn try_from(value: i32) -> Result<Self, Self::Error> {
105 match value {
106 0 => Ok(MdEntryType::Bid),
107 1 => Ok(MdEntryType::Offer),
108 2 => Ok(MdEntryType::Trade),
109 3 => Ok(MdEntryType::IndexValue),
110 6 => Ok(MdEntryType::SettlementPrice),
111 _ => Err(format!("Invalid MdEntryType: {value}")),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
118pub enum MdUpdateAction {
119 New = 0,
121 Change = 1,
123 Delete = 2,
125}
126
127impl From<MdUpdateAction> for char {
128 fn from(value: MdUpdateAction) -> Self {
129 match value {
130 MdUpdateAction::New => '0',
131 MdUpdateAction::Change => '1',
132 MdUpdateAction::Delete => '2',
133 }
134 }
135}
136
137impl TryFrom<char> for MdUpdateAction {
138 type Error = String;
139
140 fn try_from(value: char) -> Result<Self, Self::Error> {
141 match value {
142 '0' => Ok(MdUpdateAction::New),
143 '1' => Ok(MdUpdateAction::Change),
144 '2' => Ok(MdUpdateAction::Delete),
145 _ => Err(format!("Invalid MdUpdateAction: {value}")),
146 }
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152pub enum MdReqRejReason {
153 UnknownSymbol = 0,
155 DuplicateMdReqId = 1,
157 InsufficientBandwidth = 2,
159 InsufficientPermissions = 3,
161 UnsupportedSubscriptionRequestType = 4,
163 UnsupportedMarketDepth = 5,
165 UnsupportedMdUpdateType = 6,
167 UnsupportedAggregatedBook = 7,
169 UnsupportedMdEntryType = 8,
171 UnsupportedTradingSessionId = 9,
173 UnsupportedScope = 10,
175 UnsupportedOpenCloseSettlFlag = 11,
177 UnsupportedMdImplicitDelete = 12,
179 InsufficientCredit = 13,
181}
182
183impl From<MdReqRejReason> for char {
184 fn from(value: MdReqRejReason) -> Self {
185 match value {
186 MdReqRejReason::UnknownSymbol => '0',
187 MdReqRejReason::DuplicateMdReqId => '1',
188 MdReqRejReason::InsufficientBandwidth => '2',
189 MdReqRejReason::InsufficientPermissions => '3',
190 MdReqRejReason::UnsupportedSubscriptionRequestType => '4',
191 MdReqRejReason::UnsupportedMarketDepth => '5',
192 MdReqRejReason::UnsupportedMdUpdateType => '6',
193 MdReqRejReason::UnsupportedAggregatedBook => '7',
194 MdReqRejReason::UnsupportedMdEntryType => '8',
195 MdReqRejReason::UnsupportedTradingSessionId => '9',
196 MdReqRejReason::UnsupportedScope => 'A',
197 MdReqRejReason::UnsupportedOpenCloseSettlFlag => 'B',
198 MdReqRejReason::UnsupportedMdImplicitDelete => 'C',
199 MdReqRejReason::InsufficientCredit => 'D',
200 }
201 }
202}
203
204impl TryFrom<char> for MdReqRejReason {
205 type Error = String;
206
207 fn try_from(value: char) -> Result<Self, Self::Error> {
208 match value {
209 '0' => Ok(MdReqRejReason::UnknownSymbol),
210 '1' => Ok(MdReqRejReason::DuplicateMdReqId),
211 '2' => Ok(MdReqRejReason::InsufficientBandwidth),
212 '3' => Ok(MdReqRejReason::InsufficientPermissions),
213 '4' => Ok(MdReqRejReason::UnsupportedSubscriptionRequestType),
214 '5' => Ok(MdReqRejReason::UnsupportedMarketDepth),
215 '6' => Ok(MdReqRejReason::UnsupportedMdUpdateType),
216 '7' => Ok(MdReqRejReason::UnsupportedAggregatedBook),
217 '8' => Ok(MdReqRejReason::UnsupportedMdEntryType),
218 '9' => Ok(MdReqRejReason::UnsupportedTradingSessionId),
219 'A' => Ok(MdReqRejReason::UnsupportedScope),
220 'B' => Ok(MdReqRejReason::UnsupportedOpenCloseSettlFlag),
221 'C' => Ok(MdReqRejReason::UnsupportedMdImplicitDelete),
222 'D' => Ok(MdReqRejReason::InsufficientCredit),
223 _ => Err(format!("Invalid MdReqRejReason: {value}")),
224 }
225 }
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct MarketDataRequest {
231 pub md_req_id: String,
233 pub subscription_request_type: MdSubscriptionRequestType,
235 pub market_depth: Option<i32>,
237 pub md_update_type: Option<MdUpdateType>,
239 pub skip_block_trades: Option<bool>,
241 pub show_block_trade_id: Option<bool>,
243 pub trade_amount: Option<i32>,
245 pub since_timestamp: Option<i64>,
247 pub entry_types: Vec<MdEntryType>,
249 pub symbols: Vec<String>,
251}
252
253impl MarketDataRequest {
254 pub fn snapshot(
256 md_req_id: String,
257 symbols: Vec<String>,
258 entry_types: Vec<MdEntryType>,
259 ) -> Self {
260 Self {
261 md_req_id,
262 subscription_request_type: MdSubscriptionRequestType::Snapshot,
263 market_depth: None,
264 md_update_type: None,
265 skip_block_trades: None,
266 show_block_trade_id: None,
267 trade_amount: None,
268 since_timestamp: None,
269 entry_types,
270 symbols,
271 }
272 }
273
274 pub fn subscription(
276 md_req_id: String,
277 symbols: Vec<String>,
278 entry_types: Vec<MdEntryType>,
279 md_update_type: MdUpdateType,
280 ) -> Self {
281 Self {
282 md_req_id,
283 subscription_request_type: MdSubscriptionRequestType::SnapshotPlusUpdates,
284 market_depth: None,
285 md_update_type: Some(md_update_type),
286 skip_block_trades: None,
287 show_block_trade_id: None,
288 trade_amount: None,
289 since_timestamp: None,
290 entry_types,
291 symbols,
292 }
293 }
294
295 pub fn unsubscribe(md_req_id: String) -> Self {
297 Self {
298 md_req_id,
299 subscription_request_type: MdSubscriptionRequestType::Unsubscribe,
300 market_depth: None,
301 md_update_type: None,
302 skip_block_trades: None,
303 show_block_trade_id: None,
304 trade_amount: None,
305 since_timestamp: None,
306 entry_types: Vec::new(),
307 symbols: Vec::new(),
308 }
309 }
310
311 pub fn to_fix_message(
313 &self,
314 sender_comp_id: String,
315 target_comp_id: String,
316 msg_seq_num: u32,
317 ) -> DeribitFixResult<String> {
318 let mut builder = MessageBuilder::new()
319 .msg_type(MsgType::MarketDataRequest)
320 .sender_comp_id(sender_comp_id)
321 .target_comp_id(target_comp_id)
322 .msg_seq_num(msg_seq_num)
323 .sending_time(Utc::now())
324 .field(262, self.md_req_id.clone()) .field(263, i32::from(self.subscription_request_type).to_string()); if let Some(depth) = self.market_depth {
329 builder = builder.field(264, depth.to_string()); }
331
332 if let Some(update_type) = self.md_update_type {
333 builder = builder.field(265, i32::from(update_type).to_string()); }
335
336 builder = builder.field(267, self.entry_types.len().to_string()); for entry_type in &self.entry_types {
339 builder = builder.field(269, i32::from(*entry_type).to_string()); }
341
342 if let Some(skip_block_trades) = self.skip_block_trades {
344 builder = builder.field(9011, if skip_block_trades { "Y" } else { "N" }.to_string()); }
346
347 if let Some(show_block_trade_id) = self.show_block_trade_id {
348 builder = builder.field(
349 9012,
350 if show_block_trade_id { "Y" } else { "N" }.to_string(),
351 ); }
353
354 if let Some(trade_amount) = self.trade_amount {
355 builder = builder.field(100007, trade_amount.to_string()); }
357
358 if let Some(since_timestamp) = self.since_timestamp {
359 builder = builder.field(100008, since_timestamp.to_string()); }
361
362 if !self.symbols.is_empty() {
364 builder = builder.field(146, self.symbols.len().to_string()); for symbol in &self.symbols {
366 builder = builder.field(55, symbol.clone()); }
368 }
369
370 Ok(builder.build()?.to_string())
371 }
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct MarketDataRequestReject {
377 pub md_req_id: String,
379 pub md_req_rej_reason: MdReqRejReason,
381 pub text: Option<String>,
383}
384
385impl MarketDataRequestReject {
386 pub fn new(md_req_id: String, reason: MdReqRejReason) -> Self {
388 Self {
389 md_req_id,
390 md_req_rej_reason: reason,
391 text: None,
392 }
393 }
394
395 pub fn with_text(md_req_id: String, reason: MdReqRejReason, text: String) -> Self {
397 Self {
398 md_req_id,
399 md_req_rej_reason: reason,
400 text: Some(text),
401 }
402 }
403
404 pub fn to_fix_message(
406 &self,
407 sender_comp_id: String,
408 target_comp_id: String,
409 msg_seq_num: u32,
410 ) -> DeribitFixResult<String> {
411 let mut builder = MessageBuilder::new()
412 .msg_type(MsgType::MarketDataRequestReject)
413 .sender_comp_id(sender_comp_id)
414 .target_comp_id(target_comp_id)
415 .msg_seq_num(msg_seq_num)
416 .sending_time(Utc::now())
417 .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 {
421 builder = builder.field(58, text.clone()); }
423
424 Ok(builder.build()?.to_string())
425 }
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct MdEntry {
431 pub md_entry_type: MdEntryType,
433 pub md_entry_px: Option<f64>,
435 pub md_entry_size: Option<f64>,
437 pub md_entry_date: Option<DateTime<Utc>>,
439 pub md_update_action: Option<MdUpdateAction>,
441 pub trade_id: Option<String>,
443 pub side: Option<char>,
445 pub order_id: Option<String>,
447 pub secondary_order_id: Option<String>,
449 pub price: Option<f64>,
451 pub text: Option<String>,
453 pub ord_status: Option<char>,
455 pub deribit_label: Option<String>,
457 pub deribit_liquidation: Option<String>,
459 pub trd_match_id: Option<String>,
461}
462
463impl MdEntry {
464 pub fn bid(price: f64, size: f64) -> Self {
466 Self {
467 md_entry_type: MdEntryType::Bid,
468 md_entry_px: Some(price),
469 md_entry_size: Some(size),
470 md_entry_date: None,
471 md_update_action: None,
472 trade_id: None,
473 side: None,
474 order_id: None,
475 secondary_order_id: None,
476 price: None,
477 text: None,
478 ord_status: None,
479 deribit_label: None,
480 deribit_liquidation: None,
481 trd_match_id: None,
482 }
483 }
484
485 pub fn offer(price: f64, size: f64) -> Self {
487 Self {
488 md_entry_type: MdEntryType::Offer,
489 md_entry_px: Some(price),
490 md_entry_size: Some(size),
491 md_entry_date: None,
492 md_update_action: None,
493 trade_id: None,
494 side: None,
495 order_id: None,
496 secondary_order_id: None,
497 price: None,
498 text: None,
499 ord_status: None,
500 deribit_label: None,
501 deribit_liquidation: None,
502 trd_match_id: None,
503 }
504 }
505
506 pub fn trade(
508 price: f64,
509 size: f64,
510 side: char,
511 trade_id: String,
512 timestamp: DateTime<Utc>,
513 ) -> Self {
514 Self {
515 md_entry_type: MdEntryType::Trade,
516 md_entry_px: Some(price),
517 md_entry_size: Some(size),
518 md_entry_date: Some(timestamp),
519 md_update_action: None,
520 trade_id: Some(trade_id),
521 side: Some(side),
522 order_id: None,
523 secondary_order_id: None,
524 price: None,
525 text: None,
526 ord_status: None,
527 deribit_label: None,
528 deribit_liquidation: None,
529 trd_match_id: None,
530 }
531 }
532
533 pub fn with_update_action(mut self, action: MdUpdateAction) -> Self {
535 self.md_update_action = Some(action);
536 self
537 }
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct MarketDataSnapshotFullRefresh {
543 pub symbol: String,
545 pub md_req_id: Option<String>,
547 pub underlying_symbol: Option<String>,
549 pub underlying_px: Option<f64>,
551 pub contract_multiplier: Option<f64>,
553 pub put_or_call: Option<i32>,
555 pub trade_volume_24h: Option<f64>,
557 pub mark_price: Option<f64>,
559 pub open_interest: Option<f64>,
561 pub current_funding: Option<f64>,
563 pub funding_8h: Option<f64>,
565 pub entries: Vec<MdEntry>,
567}
568
569impl MarketDataSnapshotFullRefresh {
570 pub fn new(symbol: String) -> Self {
572 Self {
573 symbol,
574 md_req_id: None,
575 underlying_symbol: None,
576 underlying_px: None,
577 contract_multiplier: None,
578 put_or_call: None,
579 trade_volume_24h: None,
580 mark_price: None,
581 open_interest: None,
582 current_funding: None,
583 funding_8h: None,
584 entries: Vec::new(),
585 }
586 }
587
588 pub fn with_request_id(mut self, md_req_id: String) -> Self {
590 self.md_req_id = Some(md_req_id);
591 self
592 }
593
594 pub fn with_entries(mut self, entries: Vec<MdEntry>) -> Self {
596 self.entries = entries;
597 self
598 }
599
600 pub fn with_underlying_symbol(mut self, underlying_symbol: String) -> Self {
602 self.underlying_symbol = Some(underlying_symbol);
603 self
604 }
605
606 pub fn with_underlying_px(mut self, underlying_px: f64) -> Self {
608 self.underlying_px = Some(underlying_px);
609 self
610 }
611
612 pub fn with_contract_multiplier(mut self, contract_multiplier: f64) -> Self {
614 self.contract_multiplier = Some(contract_multiplier);
615 self
616 }
617
618 pub fn with_put_or_call(mut self, put_or_call: i32) -> Self {
620 self.put_or_call = Some(put_or_call);
621 self
622 }
623
624 pub fn with_trade_volume_24h(mut self, trade_volume_24h: f64) -> Self {
626 self.trade_volume_24h = Some(trade_volume_24h);
627 self
628 }
629
630 pub fn with_mark_price(mut self, mark_price: f64) -> Self {
632 self.mark_price = Some(mark_price);
633 self
634 }
635
636 pub fn with_open_interest(mut self, open_interest: f64) -> Self {
638 self.open_interest = Some(open_interest);
639 self
640 }
641
642 pub fn with_current_funding(mut self, current_funding: f64) -> Self {
644 self.current_funding = Some(current_funding);
645 self
646 }
647
648 pub fn with_funding_8h(mut self, funding_8h: f64) -> Self {
650 self.funding_8h = Some(funding_8h);
651 self
652 }
653
654 pub fn to_fix_message(
656 &self,
657 sender_comp_id: String,
658 target_comp_id: String,
659 msg_seq_num: u32,
660 ) -> DeribitFixResult<String> {
661 let mut builder = MessageBuilder::new()
662 .msg_type(MsgType::MarketDataSnapshotFullRefresh)
663 .sender_comp_id(sender_comp_id)
664 .target_comp_id(target_comp_id)
665 .msg_seq_num(msg_seq_num)
666 .sending_time(Utc::now())
667 .field(55, self.symbol.clone()); if let Some(ref md_req_id) = self.md_req_id {
670 builder = builder.field(262, md_req_id.clone()); }
672
673 if let Some(ref underlying_symbol) = self.underlying_symbol {
675 builder = builder.field(311, underlying_symbol.clone()); }
677
678 if let Some(underlying_px) = self.underlying_px {
679 builder = builder.field(810, underlying_px.to_string()); }
681
682 if let Some(contract_multiplier) = self.contract_multiplier {
683 builder = builder.field(231, contract_multiplier.to_string()); }
685
686 if let Some(put_or_call) = self.put_or_call {
687 builder = builder.field(201, put_or_call.to_string()); }
689
690 if let Some(trade_volume_24h) = self.trade_volume_24h {
691 builder = builder.field(100087, trade_volume_24h.to_string()); }
693
694 if let Some(mark_price) = self.mark_price {
695 builder = builder.field(100090, mark_price.to_string()); }
697
698 if let Some(open_interest) = self.open_interest {
699 builder = builder.field(746, open_interest.to_string()); }
701
702 if let Some(current_funding) = self.current_funding {
703 builder = builder.field(100092, current_funding.to_string()); }
705
706 if let Some(funding_8h) = self.funding_8h {
707 builder = builder.field(100093, funding_8h.to_string()); }
709
710 builder = builder.field(268, self.entries.len().to_string()); for entry in &self.entries {
714 builder = builder.field(269, i32::from(entry.md_entry_type).to_string()); if let Some(px) = entry.md_entry_px {
717 builder = builder.field(270, px.to_string()); }
719
720 if let Some(size) = entry.md_entry_size {
721 builder = builder.field(271, size.to_string()); }
723
724 if let Some(date) = entry.md_entry_date {
725 builder = builder.field(272, date.timestamp_millis().to_string()); }
727
728 if let Some(ref trade_id) = entry.trade_id {
729 builder = builder.field(100009, trade_id.clone()); }
731
732 if let Some(side) = entry.side {
733 builder = builder.field(54, side.to_string()); }
735
736 if let Some(price) = entry.price {
738 builder = builder.field(44, price.to_string()); }
740
741 if let Some(ref text) = entry.text {
742 builder = builder.field(58, text.clone()); }
744
745 if let Some(ref order_id) = entry.order_id {
746 builder = builder.field(37, order_id.clone()); }
748
749 if let Some(ref secondary_order_id) = entry.secondary_order_id {
750 builder = builder.field(198, secondary_order_id.clone()); }
752
753 if let Some(ord_status) = entry.ord_status {
754 builder = builder.field(39, ord_status.to_string()); }
756
757 if let Some(ref deribit_label) = entry.deribit_label {
758 builder = builder.field(100010, deribit_label.clone()); }
760
761 if let Some(ref deribit_liquidation) = entry.deribit_liquidation {
762 builder = builder.field(100091, deribit_liquidation.clone()); }
764
765 if let Some(ref trd_match_id) = entry.trd_match_id {
766 builder = builder.field(880, trd_match_id.clone()); }
768 }
769
770 Ok(builder.build()?.to_string())
771 }
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
776pub struct MarketDataIncrementalRefresh {
777 pub symbol: String,
779 pub md_req_id: Option<String>,
781 pub entries: Vec<MdEntry>,
783}
784
785impl MarketDataIncrementalRefresh {
786 pub fn new(symbol: String) -> Self {
788 Self {
789 symbol,
790 md_req_id: None,
791 entries: Vec::new(),
792 }
793 }
794
795 pub fn with_request_id(mut self, md_req_id: String) -> Self {
797 self.md_req_id = Some(md_req_id);
798 self
799 }
800
801 pub fn with_entries(mut self, entries: Vec<MdEntry>) -> Self {
803 self.entries = entries;
804 self
805 }
806
807 pub fn to_fix_message(
809 &self,
810 sender_comp_id: String,
811 target_comp_id: String,
812 msg_seq_num: u32,
813 ) -> DeribitFixResult<String> {
814 let mut builder = MessageBuilder::new()
815 .msg_type(MsgType::MarketDataIncrementalRefresh)
816 .sender_comp_id(sender_comp_id)
817 .target_comp_id(target_comp_id)
818 .msg_seq_num(msg_seq_num)
819 .sending_time(Utc::now())
820 .field(55, self.symbol.clone()); if let Some(ref md_req_id) = self.md_req_id {
823 builder = builder.field(262, md_req_id.clone()); }
825
826 builder = builder.field(268, self.entries.len().to_string()); for entry in &self.entries {
830 if let Some(action) = entry.md_update_action {
831 builder = builder.field(279, char::from(action).to_string()); }
833
834 builder = builder.field(269, i32::from(entry.md_entry_type).to_string()); if let Some(px) = entry.md_entry_px {
837 builder = builder.field(270, px.to_string()); }
839
840 if let Some(size) = entry.md_entry_size {
841 builder = builder.field(271, size.to_string()); }
843
844 if let Some(date) = entry.md_entry_date {
845 builder = builder.field(272, date.timestamp_millis().to_string()); }
847
848 if let Some(ref trade_id) = entry.trade_id {
849 builder = builder.field(100009, trade_id.clone()); }
851
852 if let Some(side) = entry.side {
853 builder = builder.field(54, side.to_string()); }
855 }
856
857 Ok(builder.build()?.to_string())
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864
865 #[test]
866 fn test_md_subscription_request_type_conversion() {
867 assert_eq!(i32::from(MdSubscriptionRequestType::Snapshot), 0);
868 assert_eq!(i32::from(MdSubscriptionRequestType::SnapshotPlusUpdates), 1);
869 assert_eq!(i32::from(MdSubscriptionRequestType::Unsubscribe), 2);
870
871 assert_eq!(
872 MdSubscriptionRequestType::try_from(0).unwrap(),
873 MdSubscriptionRequestType::Snapshot
874 );
875 assert_eq!(
876 MdSubscriptionRequestType::try_from(1).unwrap(),
877 MdSubscriptionRequestType::SnapshotPlusUpdates
878 );
879 assert_eq!(
880 MdSubscriptionRequestType::try_from(2).unwrap(),
881 MdSubscriptionRequestType::Unsubscribe
882 );
883 assert!(MdSubscriptionRequestType::try_from(99).is_err());
884 }
885
886 #[test]
887 fn test_md_entry_type_conversion() {
888 assert_eq!(i32::from(MdEntryType::Bid), 0);
889 assert_eq!(i32::from(MdEntryType::Offer), 1);
890 assert_eq!(i32::from(MdEntryType::Trade), 2);
891
892 assert_eq!(MdEntryType::try_from(0).unwrap(), MdEntryType::Bid);
893 assert_eq!(MdEntryType::try_from(1).unwrap(), MdEntryType::Offer);
894 assert_eq!(MdEntryType::try_from(2).unwrap(), MdEntryType::Trade);
895 assert!(MdEntryType::try_from(99).is_err());
896 }
897
898 #[test]
899 fn test_market_data_request_creation() {
900 let request = MarketDataRequest::snapshot(
901 "REQ123".to_string(),
902 vec!["BTC-PERPETUAL".to_string()],
903 vec![MdEntryType::Bid, MdEntryType::Offer],
904 );
905 assert_eq!(request.md_req_id, "REQ123");
906 assert_eq!(
907 request.subscription_request_type,
908 MdSubscriptionRequestType::Snapshot
909 );
910 assert_eq!(request.symbols.len(), 1);
911 assert_eq!(request.entry_types.len(), 2);
912 }
913
914 #[test]
915 fn test_market_data_request_reject_creation() {
916 let reject =
917 MarketDataRequestReject::new("REQ123".to_string(), MdReqRejReason::UnknownSymbol);
918 assert_eq!(reject.md_req_id, "REQ123");
919 assert_eq!(reject.md_req_rej_reason, MdReqRejReason::UnknownSymbol);
920 assert!(reject.text.is_none());
921 }
922
923 #[test]
924 fn test_md_req_rej_reason_conversion() {
925 assert_eq!(char::from(MdReqRejReason::UnknownSymbol), '0');
926 assert_eq!(char::from(MdReqRejReason::InsufficientCredit), 'D');
927
928 assert_eq!(
929 MdReqRejReason::try_from('0').unwrap(),
930 MdReqRejReason::UnknownSymbol
931 );
932 assert_eq!(
933 MdReqRejReason::try_from('D').unwrap(),
934 MdReqRejReason::InsufficientCredit
935 );
936 assert!(MdReqRejReason::try_from('Z').is_err());
937 }
938
939 #[test]
940 fn test_md_entry_creation() {
941 let bid = MdEntry::bid(50000.0, 1.5);
942 assert_eq!(bid.md_entry_type, MdEntryType::Bid);
943 assert_eq!(bid.md_entry_px, Some(50000.0));
944 assert_eq!(bid.md_entry_size, Some(1.5));
945 assert!(bid.md_update_action.is_none());
946
947 let offer = MdEntry::offer(50100.0, 2.0);
948 assert_eq!(offer.md_entry_type, MdEntryType::Offer);
949 assert_eq!(offer.md_entry_px, Some(50100.0));
950 assert_eq!(offer.md_entry_size, Some(2.0));
951 }
952
953 #[test]
954 fn test_market_data_snapshot_creation() {
955 let snapshot = MarketDataSnapshotFullRefresh::new("BTC-PERPETUAL".to_string())
956 .with_request_id("REQ123".to_string())
957 .with_entries(vec![
958 MdEntry::bid(50000.0, 1.0),
959 MdEntry::offer(50100.0, 1.5),
960 ]);
961
962 assert_eq!(snapshot.symbol, "BTC-PERPETUAL");
963 assert_eq!(snapshot.md_req_id, Some("REQ123".to_string()));
964 assert_eq!(snapshot.entries.len(), 2);
965 }
966
967 #[test]
968 fn test_market_data_incremental_creation() {
969 let incremental = MarketDataIncrementalRefresh::new("BTC-PERPETUAL".to_string())
970 .with_entries(vec![
971 MdEntry::bid(50000.0, 1.0).with_update_action(MdUpdateAction::New),
972 MdEntry::offer(50100.0, 1.5).with_update_action(MdUpdateAction::Change),
973 ]);
974
975 assert_eq!(incremental.symbol, "BTC-PERPETUAL");
976 assert_eq!(incremental.entries.len(), 2);
977 assert_eq!(
978 incremental.entries[0].md_update_action,
979 Some(MdUpdateAction::New)
980 );
981 assert_eq!(
982 incremental.entries[1].md_update_action,
983 Some(MdUpdateAction::Change)
984 );
985 }
986
987 #[test]
988 fn test_md_update_action_conversion() {
989 assert_eq!(char::from(MdUpdateAction::New), '0');
990 assert_eq!(char::from(MdUpdateAction::Change), '1');
991 assert_eq!(char::from(MdUpdateAction::Delete), '2');
992
993 assert_eq!(MdUpdateAction::try_from('0').unwrap(), MdUpdateAction::New);
994 assert_eq!(
995 MdUpdateAction::try_from('1').unwrap(),
996 MdUpdateAction::Change
997 );
998 assert_eq!(
999 MdUpdateAction::try_from('2').unwrap(),
1000 MdUpdateAction::Delete
1001 );
1002 assert!(MdUpdateAction::try_from('9').is_err());
1003 }
1004}