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,
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, deserialize_with = "deserialize_optional_decimal_or_market")]
648 pub price: Option<Decimal>,
649 #[serde(deserialize_with = "nautilus_core::serialization::deserialize_decimal")]
651 pub amount: Decimal,
652 #[serde(default, deserialize_with = "deserialize_decimal")]
655 pub filled_amount: Decimal,
656 #[serde(
658 default,
659 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
660 )]
661 pub average_price: Option<Decimal>,
662 pub creation_timestamp: u64,
664 pub last_update_timestamp: u64,
666 pub time_in_force: String,
668 #[serde(
670 default,
671 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
672 )]
673 pub commission: Decimal,
674 #[serde(default)]
676 pub post_only: bool,
677 #[serde(default)]
679 pub reduce_only: bool,
680 #[serde(
682 default,
683 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
684 )]
685 pub trigger_price: Option<Decimal>,
686 pub trigger: Option<String>,
688 #[serde(
690 default,
691 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
692 )]
693 pub max_show: Option<Decimal>,
694 #[serde(default)]
696 pub api: bool,
697 pub reject_reason: Option<String>,
699 pub cancel_reason: Option<String>,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize)]
707pub struct DeribitUserTradeMsg {
708 pub trade_id: String,
710 pub order_id: String,
712 pub instrument_name: Ustr,
714 pub direction: String,
716 #[serde(
718 serialize_with = "nautilus_core::serialization::serialize_decimal",
719 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
720 )]
721 pub price: Decimal,
722 #[serde(
724 serialize_with = "nautilus_core::serialization::serialize_decimal",
725 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
726 )]
727 pub amount: Decimal,
728 #[serde(
730 serialize_with = "nautilus_core::serialization::serialize_decimal",
731 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
732 )]
733 pub fee: Decimal,
734 pub fee_currency: String,
736 pub timestamp: u64,
738 pub trade_seq: u64,
740 pub liquidity: String,
742 pub order_type: String,
744 #[serde(
746 serialize_with = "nautilus_core::serialization::serialize_decimal",
747 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
748 )]
749 pub index_price: Decimal,
750 #[serde(
752 serialize_with = "nautilus_core::serialization::serialize_decimal",
753 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
754 )]
755 pub mark_price: Decimal,
756 pub tick_direction: i8,
758 pub state: String,
760 pub label: Option<String>,
762 #[serde(default)]
764 pub reduce_only: bool,
765 #[serde(default)]
767 pub post_only: bool,
768 #[serde(default)]
770 pub liquidation: Option<String>,
771 #[serde(
773 default,
774 serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
775 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
776 )]
777 pub profit_loss: Option<Decimal>,
778}
779
780#[derive(Debug, Clone, Deserialize)]
782pub struct DeribitPortfolioMsg {
783 pub currency: String,
785 #[serde(with = "rust_decimal::serde::float")]
787 pub equity: Decimal,
788 #[serde(with = "rust_decimal::serde::float")]
790 pub balance: Decimal,
791 #[serde(with = "rust_decimal::serde::float")]
793 pub available_funds: Decimal,
794 #[serde(with = "rust_decimal::serde::float")]
796 pub margin_balance: Decimal,
797 #[serde(with = "rust_decimal::serde::float")]
799 pub initial_margin: Decimal,
800 #[serde(with = "rust_decimal::serde::float")]
802 pub maintenance_margin: Decimal,
803 #[serde(default)]
805 pub margin_model: Option<String>,
806 #[serde(default)]
808 pub cross_collateral_enabled: Option<bool>,
809 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
811 pub available_withdrawal_funds: Option<Decimal>,
812}
813
814#[derive(Debug, Clone)]
816pub enum DeribitWsMessage {
817 Response(DeribitJsonRpcResponse<serde_json::Value>),
819 Notification(DeribitSubscriptionNotification<serde_json::Value>),
821 Heartbeat(DeribitHeartbeatData),
823 Error(DeribitJsonRpcError),
825 Reconnected,
827}
828
829#[derive(Debug, Clone, Serialize, Deserialize)]
831pub struct DeribitWebSocketError {
832 pub code: i64,
834 pub message: String,
836 pub timestamp: u64,
838}
839
840impl From<DeribitJsonRpcError> for DeribitWebSocketError {
841 fn from(err: DeribitJsonRpcError) -> Self {
842 Self {
843 code: err.code,
844 message: err.message,
845 timestamp: 0,
846 }
847 }
848}
849
850#[derive(Debug, Clone)]
852pub enum NautilusWsMessage {
853 Data(Vec<Data>),
855 Deltas(OrderBookDeltas),
857 Instrument(Box<InstrumentAny>),
859 FundingRates(Vec<FundingRateUpdate>),
861 OptionGreeks(OptionGreeks),
863 OrderStatusReports(Vec<OrderStatusReport>),
865 FillReports(Vec<FillReport>),
867 OrderAccepted(OrderAccepted),
869 OrderCanceled(OrderCanceled),
871 OrderExpired(OrderExpired),
873 OrderRejected(OrderRejected),
875 OrderCancelRejected(OrderCancelRejected),
877 OrderModifyRejected(OrderModifyRejected),
879 OrderUpdated(OrderUpdated),
881 AccountState(AccountState),
883 InstrumentStatus(InstrumentStatus),
885 Error(DeribitWsError),
887 Raw(serde_json::Value),
889 Reconnected,
891 Authenticated(Box<DeribitAuthResult>),
893 AuthenticationFailed(String),
895}
896
897pub fn parse_raw_message(text: &str) -> Result<DeribitWsMessage, DeribitWsError> {
903 let value: serde_json::Value =
904 serde_json::from_str(text).map_err(|e| DeribitWsError::Json(e.to_string()))?;
905
906 if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
908 if method == "subscription" {
909 let notification: DeribitSubscriptionNotification<serde_json::Value> =
910 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
911 return Ok(DeribitWsMessage::Notification(notification));
912 }
913 if method == "heartbeat"
915 && let Some(params) = value.get("params")
916 {
917 let heartbeat: DeribitHeartbeatData = serde_json::from_value(params.clone())
918 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
919 return Ok(DeribitWsMessage::Heartbeat(heartbeat));
920 }
921 }
922
923 if value.get("id").is_some() {
928 let response: DeribitJsonRpcResponse<serde_json::Value> =
929 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
930 return Ok(DeribitWsMessage::Response(response));
931 }
932
933 let response: DeribitJsonRpcResponse<serde_json::Value> =
935 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
936 Ok(DeribitWsMessage::Response(response))
937}
938
939pub fn extract_instrument_from_channel(channel: &str) -> Option<&str> {
943 let parts: Vec<&str> = channel.split('.').collect();
944 if parts.len() >= 2 {
945 Some(parts[1])
946 } else {
947 None
948 }
949}
950
951#[cfg(test)]
952mod tests {
953 use rstest::rstest;
954
955 use super::*;
956
957 #[rstest]
958 fn test_parse_subscription_notification() {
959 let json = r#"{
960 "jsonrpc": "2.0",
961 "method": "subscription",
962 "params": {
963 "channel": "trades.BTC-PERPETUAL.raw",
964 "data": [{"trade_id": "123", "price": 50000.0}]
965 }
966 }"#;
967
968 let msg = parse_raw_message(json).unwrap();
969 assert!(matches!(msg, DeribitWsMessage::Notification(_)));
970 }
971
972 #[rstest]
973 fn test_parse_response() {
974 let json = r#"{
975 "jsonrpc": "2.0",
976 "id": 1,
977 "result": ["trades.BTC-PERPETUAL.raw"],
978 "testnet": true,
979 "usIn": 1234567890,
980 "usOut": 1234567891,
981 "usDiff": 1
982 }"#;
983
984 let msg = parse_raw_message(json).unwrap();
985 assert!(matches!(msg, DeribitWsMessage::Response(_)));
986 }
987
988 #[rstest]
989 fn test_parse_error_response() {
990 let json = r#"{
993 "jsonrpc": "2.0",
994 "id": 1,
995 "error": {
996 "code": 10028,
997 "message": "too_many_requests"
998 }
999 }"#;
1000
1001 let msg = parse_raw_message(json).unwrap();
1002 match msg {
1003 DeribitWsMessage::Response(resp) => {
1004 assert!(resp.error.is_some());
1005 let error = resp.error.unwrap();
1006 assert_eq!(error.code, 10028);
1007 assert_eq!(error.message, "too_many_requests");
1008 }
1009 _ => panic!("Expected Response with error, was {msg:?}"),
1010 }
1011 }
1012
1013 #[rstest]
1014 fn test_extract_instrument_from_channel() {
1015 assert_eq!(
1016 extract_instrument_from_channel("trades.BTC-PERPETUAL.raw"),
1017 Some("BTC-PERPETUAL")
1018 );
1019 assert_eq!(
1020 extract_instrument_from_channel("book.ETH-25DEC25.raw"),
1021 Some("ETH-25DEC25")
1022 );
1023 assert_eq!(extract_instrument_from_channel("platform_state"), None);
1024 }
1025
1026 #[rstest]
1027 fn test_parse_volatility_index_payload() {
1028 let value = serde_json::json!({
1029 "timestamp": 1619777946007_u64,
1030 "volatility": 129.36_f64,
1031 "index_name": "btc_usd",
1032 });
1033
1034 let payload: DeribitVolatilityIndexMsg = serde_json::from_value(value).unwrap();
1035 assert_eq!(payload.index_name, "btc_usd");
1036 assert_eq!(payload.volatility, 129.36);
1037 assert_eq!(payload.timestamp, 1619777946007_u64);
1038 }
1039}