1use std::str::FromStr;
19
20use nautilus_core::serialization::{deserialize_decimal, deserialize_optional_decimal};
21use nautilus_model::{
22 data::{
23 Data, FundingRateUpdate, InstrumentStatus, OrderBookDeltas, greeks::OptionGreekValues,
24 option_chain::OptionGreeks,
25 },
26 events::{
27 AccountState, OrderAccepted, OrderCancelRejected, OrderCanceled, OrderExpired, OrderFilled,
28 OrderModifyRejected, OrderRejected, OrderUpdated,
29 },
30 instruments::InstrumentAny,
31 reports::{FillReport, OrderStatusReport},
32};
33use rust_decimal::{Decimal, prelude::ToPrimitive};
34use serde::{Deserialize, Deserializer, Serialize, de};
35use ustr::Ustr;
36
37use super::enums::{DeribitBookAction, DeribitBookMsgType, DeribitHeartbeatType};
38pub use crate::common::{
39 enums::DeribitInstrumentState,
40 rpc::{DeribitJsonRpcError, DeribitJsonRpcRequest, DeribitJsonRpcResponse},
41};
42use crate::{common::models::DeribitTradeLeg, websocket::error::DeribitWsError};
43
44#[derive(Debug, Clone, Deserialize)]
46pub struct DeribitSubscriptionNotification<T> {
47 pub jsonrpc: String,
49 pub method: String,
51 pub params: DeribitSubscriptionParams<T>,
53}
54
55#[derive(Debug, Clone, Deserialize)]
57pub struct DeribitSubscriptionParams<T> {
58 pub channel: String,
60 pub data: T,
62}
63
64#[derive(Debug, Clone, Serialize)]
66pub struct DeribitAuthParams {
67 pub grant_type: String,
69 pub client_id: String,
71 pub timestamp: u64,
73 pub signature: String,
75 pub nonce: String,
77 pub data: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
83 pub scope: Option<String>,
84}
85
86#[derive(Debug, Clone, Serialize)]
88pub struct DeribitRefreshTokenParams {
89 pub grant_type: String,
91 pub refresh_token: String,
93}
94
95#[derive(Debug, Clone, Deserialize)]
97pub struct DeribitAuthResult {
98 pub access_token: String,
100 pub expires_in: u64,
102 pub refresh_token: String,
104 pub scope: String,
106 pub token_type: String,
108 #[serde(default)]
110 pub enabled_features: Vec<String>,
111}
112
113#[derive(Debug, Clone, Serialize)]
115pub struct DeribitSubscribeParams {
116 pub channels: Vec<String>,
118}
119
120#[derive(Debug, Clone, Deserialize)]
122pub struct DeribitSubscribeResult(pub Vec<String>);
123
124#[derive(Debug, Clone, Serialize)]
126pub struct DeribitHeartbeatParams {
127 pub interval: u64,
129}
130
131#[derive(Debug, Clone, Deserialize)]
133pub struct DeribitHeartbeatData {
134 #[serde(rename = "type")]
136 pub heartbeat_type: DeribitHeartbeatType,
137}
138
139#[derive(Debug, Clone, Deserialize)]
141pub struct DeribitTradeMsg {
142 pub trade_id: String,
144 pub instrument_name: Ustr,
146 #[serde(deserialize_with = "deserialize_decimal")]
148 pub price: Decimal,
149 #[serde(deserialize_with = "deserialize_decimal")]
151 pub amount: Decimal,
152 pub direction: String,
154 pub timestamp: u64,
156 pub trade_seq: u64,
158 pub tick_direction: i8,
160 #[serde(deserialize_with = "deserialize_decimal")]
162 pub index_price: Decimal,
163 #[serde(deserialize_with = "deserialize_decimal")]
165 pub mark_price: Decimal,
166 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
168 pub iv: Option<Decimal>,
169 pub liquidation: Option<String>,
171 pub combo_trade_id: Option<String>,
173 pub block_trade_id: Option<String>,
175 #[serde(default)]
177 pub block_rfq_id: Option<i64>,
178 pub combo_id: Option<String>,
180 #[serde(default)]
182 pub legs: Option<Vec<DeribitTradeLeg>>,
183}
184
185#[derive(Debug, Clone, Deserialize)]
190pub struct DeribitBookMsg {
191 #[serde(rename = "type", default = "default_book_msg_type")]
193 pub msg_type: DeribitBookMsgType,
194 pub instrument_name: Ustr,
196 pub timestamp: u64,
198 pub change_id: u64,
200 pub prev_change_id: Option<u64>,
202 pub bids: Vec<Vec<serde_json::Value>>,
204 pub asks: Vec<Vec<serde_json::Value>>,
206}
207
208fn default_book_msg_type() -> DeribitBookMsgType {
210 DeribitBookMsgType::Snapshot
211}
212
213#[derive(Debug, Clone)]
215pub struct DeribitBookLevel {
216 pub price: Decimal,
218 pub amount: Decimal,
220 pub action: Option<DeribitBookAction>,
222}
223
224#[derive(Debug, Clone, Deserialize)]
226pub struct DeribitTickerMsg {
227 pub instrument_name: Ustr,
229 pub timestamp: u64,
231 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
233 pub best_bid_price: Option<Decimal>,
234 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
236 pub best_bid_amount: Option<Decimal>,
237 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
239 pub best_ask_price: Option<Decimal>,
240 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
242 pub best_ask_amount: Option<Decimal>,
243 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
245 pub last_price: Option<Decimal>,
246 #[serde(deserialize_with = "deserialize_decimal")]
248 pub mark_price: Decimal,
249 #[serde(deserialize_with = "deserialize_decimal")]
251 pub index_price: Decimal,
252 #[serde(deserialize_with = "deserialize_decimal")]
254 pub open_interest: Decimal,
255 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
257 pub current_funding: Option<Decimal>,
258 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
260 pub funding_8h: Option<Decimal>,
261 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
263 pub settlement_price: Option<Decimal>,
264 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
266 pub volume: Option<Decimal>,
267 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
269 pub volume_usd: Option<Decimal>,
270 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
272 pub high: Option<Decimal>,
273 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
275 pub low: Option<Decimal>,
276 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
278 pub price_change: Option<Decimal>,
279 pub state: String,
281 pub greeks: Option<DeribitGreeks>,
284 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
286 pub mark_iv: Option<Decimal>,
287 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
289 pub bid_iv: Option<Decimal>,
290 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
292 pub ask_iv: Option<Decimal>,
293 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
295 pub underlying_price: Option<Decimal>,
296 pub underlying_index: Option<String>,
298}
299
300#[derive(Debug, Clone, Deserialize)]
302pub struct DeribitGreeks {
303 #[serde(deserialize_with = "deserialize_decimal")]
304 pub delta: Decimal,
305 #[serde(deserialize_with = "deserialize_decimal")]
306 pub gamma: Decimal,
307 #[serde(deserialize_with = "deserialize_decimal")]
308 pub vega: Decimal,
309 #[serde(deserialize_with = "deserialize_decimal")]
310 pub theta: Decimal,
311 #[serde(deserialize_with = "deserialize_decimal")]
312 pub rho: Decimal,
313}
314
315impl DeribitGreeks {
316 pub fn to_greek_values(&self) -> OptionGreekValues {
318 OptionGreekValues {
319 delta: self.delta.to_f64().unwrap_or(0.0),
320 gamma: self.gamma.to_f64().unwrap_or(0.0),
321 vega: self.vega.to_f64().unwrap_or(0.0),
322 theta: self.theta.to_f64().unwrap_or(0.0),
323 rho: self.rho.to_f64().unwrap_or(0.0),
324 }
325 }
326}
327
328#[derive(Debug, Clone, Deserialize)]
330pub struct DeribitQuoteMsg {
331 pub instrument_name: Ustr,
333 pub timestamp: u64,
335 #[serde(deserialize_with = "deserialize_decimal")]
337 pub best_bid_price: Decimal,
338 #[serde(deserialize_with = "deserialize_decimal")]
340 pub best_bid_amount: Decimal,
341 #[serde(deserialize_with = "deserialize_decimal")]
343 pub best_ask_price: Decimal,
344 #[serde(deserialize_with = "deserialize_decimal")]
346 pub best_ask_amount: Decimal,
347}
348
349#[derive(Debug, Clone, Deserialize)]
354pub struct DeribitInstrumentStateMsg {
355 pub instrument_name: Ustr,
357 pub state: DeribitInstrumentState,
359 pub timestamp: u64,
361}
362
363#[derive(Debug, Clone, Deserialize)]
369pub struct DeribitPerpetualMsg {
370 #[serde(deserialize_with = "deserialize_decimal")]
372 pub index_price: Decimal,
373 #[serde(deserialize_with = "deserialize_decimal")]
375 pub interest: Decimal,
376 pub timestamp: u64,
378}
379
380#[derive(Debug, Clone, Deserialize)]
382pub struct DeribitVolatilityIndexMsg {
383 pub timestamp: u64,
385 pub volatility: f64,
387 pub index_name: String,
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
396#[serde(rename_all = "lowercase")]
397pub enum DeribitChartStatus {
398 #[default]
400 Ok,
401 Imputed,
403}
404
405#[derive(Debug, Clone, Deserialize)]
407pub struct DeribitChartMsg {
408 pub tick: u64,
410 pub open: f64,
412 pub high: f64,
414 pub low: f64,
416 pub close: f64,
418 pub volume: f64,
420 pub cost: f64,
422 #[serde(default)]
424 pub status: DeribitChartStatus,
425}
426
427#[derive(Debug, Clone, Serialize)]
432pub struct DeribitOrderParams {
433 pub instrument_name: String,
435 #[serde(with = "rust_decimal::serde::float")]
437 pub amount: Decimal,
438 #[serde(rename = "type")]
440 pub order_type: String,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub label: Option<String>,
444 #[serde(
446 skip_serializing_if = "Option::is_none",
447 with = "rust_decimal::serde::float_option"
448 )]
449 pub price: Option<Decimal>,
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub time_in_force: Option<String>,
453 #[serde(skip_serializing_if = "Option::is_none")]
456 pub post_only: Option<bool>,
457 #[serde(skip_serializing_if = "Option::is_none")]
460 pub reject_post_only: Option<bool>,
461 #[serde(skip_serializing_if = "Option::is_none")]
463 pub reduce_only: Option<bool>,
464 #[serde(
466 skip_serializing_if = "Option::is_none",
467 with = "rust_decimal::serde::float_option"
468 )]
469 pub trigger_price: Option<Decimal>,
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub trigger: Option<String>,
473 #[serde(
475 skip_serializing_if = "Option::is_none",
476 with = "rust_decimal::serde::float_option"
477 )]
478 pub max_show: Option<Decimal>,
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub valid_until: Option<u64>,
482}
483
484#[derive(Debug, Clone, Serialize)]
486pub struct DeribitCancelParams {
487 pub order_id: String,
489}
490
491#[derive(Debug, Clone, Serialize)]
493pub struct DeribitCancelAllByInstrumentParams {
494 pub instrument_name: String,
496 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
498 pub order_type: Option<String>,
499}
500
501#[derive(Debug, Clone, Serialize)]
506pub struct DeribitEditParams {
507 pub order_id: String,
509 #[serde(with = "rust_decimal::serde::float")]
511 pub amount: Decimal,
512 #[serde(
514 skip_serializing_if = "Option::is_none",
515 with = "rust_decimal::serde::float_option"
516 )]
517 pub price: Option<Decimal>,
518 #[serde(
520 skip_serializing_if = "Option::is_none",
521 with = "rust_decimal::serde::float_option"
522 )]
523 pub trigger_price: Option<Decimal>,
524 #[serde(skip_serializing_if = "Option::is_none")]
527 pub post_only: Option<bool>,
528 #[serde(skip_serializing_if = "Option::is_none")]
531 pub reject_post_only: Option<bool>,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub reduce_only: Option<bool>,
535}
536
537#[derive(Debug, Clone, Serialize)]
539pub struct DeribitGetOrderStateParams {
540 pub order_id: String,
542}
543
544fn deserialize_optional_decimal_or_market<'de, D>(
549 deserializer: D,
550) -> Result<Option<Decimal>, D::Error>
551where
552 D: Deserializer<'de>,
553{
554 struct Visitor;
555
556 impl<'de> de::Visitor<'de> for Visitor {
557 type Value = Option<Decimal>;
558
559 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
560 formatter.write_str(
561 "null, a decimal as string/integer/float, or the literal \"market_price\"",
562 )
563 }
564
565 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
566 if v.is_empty() || v == "market_price" {
567 return Ok(None);
568 }
569
570 if v.contains('e') || v.contains('E') {
571 Decimal::from_scientific(v).map(Some).map_err(E::custom)
572 } else {
573 Decimal::from_str(v).map(Some).map_err(E::custom)
574 }
575 }
576
577 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
578 self.visit_str(&v)
579 }
580
581 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
582 Ok(Some(Decimal::from(v)))
583 }
584
585 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
586 Ok(Some(Decimal::from(v)))
587 }
588
589 fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
590 Ok(Some(Decimal::from(v)))
591 }
592
593 fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
594 Ok(Some(Decimal::from(v)))
595 }
596
597 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Self::Value, E> {
598 if v.is_nan() || v.is_infinite() {
599 return Err(E::invalid_value(de::Unexpected::Float(v), &self));
600 }
601 Decimal::try_from(v).map(Some).map_err(E::custom)
602 }
603
604 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
605 Ok(None)
606 }
607
608 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
609 Ok(None)
610 }
611 }
612
613 deserializer.deserialize_any(Visitor)
614}
615
616#[derive(Debug, Clone, Deserialize)]
620pub struct DeribitOrderResponse {
621 pub order: DeribitOrderMsg,
623 #[serde(default)]
625 pub trades: Vec<DeribitUserTradeMsg>,
626}
627
628#[derive(Debug, Clone, Deserialize)]
632pub struct DeribitOrderMsg {
633 pub order_id: String,
635 pub label: Option<String>,
637 pub instrument_name: Ustr,
639 pub direction: String,
641 pub order_type: String,
643 pub order_state: String,
645 #[serde(default)]
647 pub replaced: bool,
648 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_market")]
651 pub price: Option<Decimal>,
652 #[serde(deserialize_with = "nautilus_core::serialization::deserialize_decimal")]
654 pub amount: Decimal,
655 #[serde(default, deserialize_with = "deserialize_decimal")]
658 pub filled_amount: Decimal,
659 #[serde(
661 default,
662 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
663 )]
664 pub average_price: Option<Decimal>,
665 pub creation_timestamp: u64,
667 pub last_update_timestamp: u64,
669 pub time_in_force: String,
671 #[serde(
673 default,
674 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
675 )]
676 pub commission: Decimal,
677 #[serde(default)]
679 pub post_only: bool,
680 #[serde(default)]
682 pub reduce_only: bool,
683 #[serde(
685 default,
686 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
687 )]
688 pub trigger_price: Option<Decimal>,
689 pub trigger: Option<String>,
691 #[serde(
693 default,
694 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
695 )]
696 pub max_show: Option<Decimal>,
697 #[serde(default)]
699 pub api: bool,
700 pub reject_reason: Option<String>,
702 pub cancel_reason: Option<String>,
704}
705
706#[derive(Debug, Clone, Serialize, Deserialize)]
710pub struct DeribitUserTradeMsg {
711 pub trade_id: String,
713 pub order_id: String,
715 pub instrument_name: Ustr,
717 pub direction: String,
719 #[serde(
721 serialize_with = "nautilus_core::serialization::serialize_decimal",
722 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
723 )]
724 pub price: Decimal,
725 #[serde(
727 serialize_with = "nautilus_core::serialization::serialize_decimal",
728 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
729 )]
730 pub amount: Decimal,
731 #[serde(
733 serialize_with = "nautilus_core::serialization::serialize_decimal",
734 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
735 )]
736 pub fee: Decimal,
737 pub fee_currency: String,
739 pub timestamp: u64,
741 pub trade_seq: u64,
743 pub liquidity: String,
745 pub order_type: String,
747 #[serde(
749 serialize_with = "nautilus_core::serialization::serialize_decimal",
750 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
751 )]
752 pub index_price: Decimal,
753 #[serde(
755 serialize_with = "nautilus_core::serialization::serialize_decimal",
756 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
757 )]
758 pub mark_price: Decimal,
759 pub tick_direction: i8,
761 pub state: String,
763 pub label: Option<String>,
765 #[serde(default)]
767 pub reduce_only: bool,
768 #[serde(default)]
770 pub post_only: bool,
771 #[serde(default)]
773 pub liquidation: Option<String>,
774 #[serde(
776 default,
777 serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
778 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
779 )]
780 pub profit_loss: Option<Decimal>,
781}
782
783#[derive(Debug, Clone, Deserialize)]
785pub struct DeribitPortfolioMsg {
786 pub currency: String,
788 #[serde(with = "rust_decimal::serde::float")]
790 pub equity: Decimal,
791 #[serde(with = "rust_decimal::serde::float")]
793 pub balance: Decimal,
794 #[serde(with = "rust_decimal::serde::float")]
796 pub available_funds: Decimal,
797 #[serde(with = "rust_decimal::serde::float")]
799 pub margin_balance: Decimal,
800 #[serde(with = "rust_decimal::serde::float")]
802 pub initial_margin: Decimal,
803 #[serde(with = "rust_decimal::serde::float")]
805 pub maintenance_margin: Decimal,
806 #[serde(default)]
808 pub margin_model: Option<String>,
809 #[serde(default)]
811 pub cross_collateral_enabled: Option<bool>,
812 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
814 pub available_withdrawal_funds: Option<Decimal>,
815}
816
817#[derive(Debug, Clone)]
819pub enum DeribitWsMessage {
820 Response(DeribitJsonRpcResponse<serde_json::Value>),
822 Notification(DeribitSubscriptionNotification<serde_json::Value>),
824 Heartbeat(DeribitHeartbeatData),
826 Error(DeribitJsonRpcError),
828 Reconnected,
830}
831
832#[derive(Debug, Clone, Serialize, Deserialize)]
834pub struct DeribitWebSocketError {
835 pub code: i64,
837 pub message: String,
839 pub timestamp: u64,
841}
842
843impl From<DeribitJsonRpcError> for DeribitWebSocketError {
844 fn from(err: DeribitJsonRpcError) -> Self {
845 Self {
846 code: err.code,
847 message: err.message,
848 timestamp: 0,
849 }
850 }
851}
852
853#[derive(Debug, Clone)]
855pub enum NautilusWsMessage {
856 Data(Vec<Data>),
858 Deltas(OrderBookDeltas),
860 Instrument(Box<InstrumentAny>),
862 FundingRates(Vec<FundingRateUpdate>),
864 OptionGreeks(OptionGreeks),
866 OrderStatusReports(Vec<OrderStatusReport>),
868 FillReports(Vec<FillReport>),
870 OrderFilled(OrderFilled),
872 OrderAccepted(OrderAccepted),
874 OrderCanceled(OrderCanceled),
876 OrderExpired(OrderExpired),
878 OrderRejected(OrderRejected),
880 OrderCancelRejected(OrderCancelRejected),
882 OrderModifyRejected(OrderModifyRejected),
884 OrderUpdated(OrderUpdated),
886 AccountState(AccountState),
888 InstrumentStatus(InstrumentStatus),
890 Error(DeribitWsError),
892 Raw(serde_json::Value),
894 Reconnected,
896 Authenticated(Box<DeribitAuthResult>),
898 AuthenticationFailed(String),
900}
901
902pub fn parse_raw_message(text: &str) -> Result<DeribitWsMessage, DeribitWsError> {
908 let value: serde_json::Value =
909 serde_json::from_str(text).map_err(|e| DeribitWsError::Json(e.to_string()))?;
910
911 if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
913 if method == "subscription" {
914 let notification: DeribitSubscriptionNotification<serde_json::Value> =
915 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
916 return Ok(DeribitWsMessage::Notification(notification));
917 }
918 if method == "heartbeat"
920 && let Some(params) = value.get("params")
921 {
922 let heartbeat: DeribitHeartbeatData = serde_json::from_value(params.clone())
923 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
924 return Ok(DeribitWsMessage::Heartbeat(heartbeat));
925 }
926 }
927
928 if value.get("id").is_some() {
933 let response: DeribitJsonRpcResponse<serde_json::Value> =
934 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
935 return Ok(DeribitWsMessage::Response(response));
936 }
937
938 let response: DeribitJsonRpcResponse<serde_json::Value> =
940 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
941 Ok(DeribitWsMessage::Response(response))
942}
943
944pub fn extract_instrument_from_channel(channel: &str) -> Option<&str> {
948 let parts: Vec<&str> = channel.split('.').collect();
949 if parts.len() >= 2 {
950 Some(parts[1])
951 } else {
952 None
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 use rstest::rstest;
959
960 use super::*;
961
962 #[rstest]
963 fn test_parse_subscription_notification() {
964 let json = r#"{
965 "jsonrpc": "2.0",
966 "method": "subscription",
967 "params": {
968 "channel": "trades.BTC-PERPETUAL.raw",
969 "data": [{"trade_id": "123", "price": 50000.0}]
970 }
971 }"#;
972
973 let msg = parse_raw_message(json).unwrap();
974 assert!(matches!(msg, DeribitWsMessage::Notification(_)));
975 }
976
977 #[rstest]
978 fn test_parse_response() {
979 let json = r#"{
980 "jsonrpc": "2.0",
981 "id": 1,
982 "result": ["trades.BTC-PERPETUAL.raw"],
983 "testnet": true,
984 "usIn": 1234567890,
985 "usOut": 1234567891,
986 "usDiff": 1
987 }"#;
988
989 let msg = parse_raw_message(json).unwrap();
990 assert!(matches!(msg, DeribitWsMessage::Response(_)));
991 }
992
993 #[rstest]
994 fn test_parse_error_response() {
995 let json = r#"{
998 "jsonrpc": "2.0",
999 "id": 1,
1000 "error": {
1001 "code": 10028,
1002 "message": "too_many_requests"
1003 }
1004 }"#;
1005
1006 let msg = parse_raw_message(json).unwrap();
1007 match msg {
1008 DeribitWsMessage::Response(resp) => {
1009 assert!(resp.error.is_some());
1010 let error = resp.error.unwrap();
1011 assert_eq!(error.code, 10028);
1012 assert_eq!(error.message, "too_many_requests");
1013 }
1014 _ => panic!("Expected Response with error, was {msg:?}"),
1015 }
1016 }
1017
1018 #[rstest]
1019 fn test_extract_instrument_from_channel() {
1020 assert_eq!(
1021 extract_instrument_from_channel("trades.BTC-PERPETUAL.raw"),
1022 Some("BTC-PERPETUAL")
1023 );
1024 assert_eq!(
1025 extract_instrument_from_channel("book.ETH-25DEC25.raw"),
1026 Some("ETH-25DEC25")
1027 );
1028 assert_eq!(extract_instrument_from_channel("platform_state"), None);
1029 }
1030
1031 #[rstest]
1032 fn test_parse_volatility_index_payload() {
1033 let value = serde_json::json!({
1034 "timestamp": 1619777946007_u64,
1035 "volatility": 129.36_f64,
1036 "index_name": "btc_usd",
1037 });
1038
1039 let payload: DeribitVolatilityIndexMsg = serde_json::from_value(value).unwrap();
1040 assert_eq!(payload.index_name, "btc_usd");
1041 assert_eq!(payload.volatility, 129.36);
1042 assert_eq!(payload.timestamp, 1619777946007_u64);
1043 }
1044}