1use nautilus_core::serialization::{deserialize_decimal, deserialize_optional_decimal};
19use nautilus_model::{
20 data::{Data, FundingRateUpdate, OrderBookDeltas},
21 events::{
22 AccountState, OrderAccepted, OrderCancelRejected, OrderCanceled, OrderExpired,
23 OrderModifyRejected, OrderRejected, OrderUpdated,
24 },
25 instruments::InstrumentAny,
26 reports::{FillReport, OrderStatusReport},
27};
28use rust_decimal::Decimal;
29use serde::{Deserialize, Serialize};
30use ustr::Ustr;
31
32use super::enums::{DeribitBookAction, DeribitBookMsgType, DeribitHeartbeatType};
33pub use crate::common::{
34 enums::DeribitInstrumentState,
35 rpc::{DeribitJsonRpcError, DeribitJsonRpcRequest, DeribitJsonRpcResponse},
36};
37use crate::websocket::error::DeribitWsError;
38
39#[derive(Debug, Clone, Deserialize)]
41pub struct DeribitSubscriptionNotification<T> {
42 pub jsonrpc: String,
44 pub method: String,
46 pub params: DeribitSubscriptionParams<T>,
48}
49
50#[derive(Debug, Clone, Deserialize)]
52pub struct DeribitSubscriptionParams<T> {
53 pub channel: String,
55 pub data: T,
57}
58
59#[derive(Debug, Clone, Serialize)]
61pub struct DeribitAuthParams {
62 pub grant_type: String,
64 pub client_id: String,
66 pub timestamp: u64,
68 pub signature: String,
70 pub nonce: String,
72 pub data: String,
74 #[serde(skip_serializing_if = "Option::is_none")]
78 pub scope: Option<String>,
79}
80
81#[derive(Debug, Clone, Serialize)]
83pub struct DeribitRefreshTokenParams {
84 pub grant_type: String,
86 pub refresh_token: String,
88}
89
90#[derive(Debug, Clone, Deserialize)]
92pub struct DeribitAuthResult {
93 pub access_token: String,
95 pub expires_in: u64,
97 pub refresh_token: String,
99 pub scope: String,
101 pub token_type: String,
103 #[serde(default)]
105 pub enabled_features: Vec<String>,
106}
107
108#[derive(Debug, Clone, Serialize)]
110pub struct DeribitSubscribeParams {
111 pub channels: Vec<String>,
113}
114
115#[derive(Debug, Clone, Deserialize)]
117pub struct DeribitSubscribeResult(pub Vec<String>);
118
119#[derive(Debug, Clone, Serialize)]
121pub struct DeribitHeartbeatParams {
122 pub interval: u64,
124}
125
126#[derive(Debug, Clone, Deserialize)]
128pub struct DeribitHeartbeatData {
129 #[serde(rename = "type")]
131 pub heartbeat_type: DeribitHeartbeatType,
132}
133
134#[derive(Debug, Clone, Deserialize)]
136pub struct DeribitTradeMsg {
137 pub trade_id: String,
139 pub instrument_name: Ustr,
141 #[serde(deserialize_with = "deserialize_decimal")]
143 pub price: Decimal,
144 #[serde(deserialize_with = "deserialize_decimal")]
146 pub amount: Decimal,
147 pub direction: String,
149 pub timestamp: u64,
151 pub trade_seq: u64,
153 pub tick_direction: i8,
155 #[serde(deserialize_with = "deserialize_decimal")]
157 pub index_price: Decimal,
158 #[serde(deserialize_with = "deserialize_decimal")]
160 pub mark_price: Decimal,
161 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
163 pub iv: Option<Decimal>,
164 pub liquidation: Option<String>,
166 pub combo_trade_id: Option<String>,
168 pub block_trade_id: Option<String>,
170 pub combo_id: Option<String>,
172}
173
174#[derive(Debug, Clone, Deserialize)]
179pub struct DeribitBookMsg {
180 #[serde(rename = "type", default = "default_book_msg_type")]
182 pub msg_type: DeribitBookMsgType,
183 pub instrument_name: Ustr,
185 pub timestamp: u64,
187 pub change_id: u64,
189 pub prev_change_id: Option<u64>,
191 pub bids: Vec<Vec<serde_json::Value>>,
193 pub asks: Vec<Vec<serde_json::Value>>,
195}
196
197fn default_book_msg_type() -> DeribitBookMsgType {
199 DeribitBookMsgType::Snapshot
200}
201
202#[derive(Debug, Clone)]
204pub struct DeribitBookLevel {
205 pub price: Decimal,
207 pub amount: Decimal,
209 pub action: Option<DeribitBookAction>,
211}
212
213#[derive(Debug, Clone, Deserialize)]
215pub struct DeribitTickerMsg {
216 pub instrument_name: Ustr,
218 pub timestamp: u64,
220 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
222 pub best_bid_price: Option<Decimal>,
223 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
225 pub best_bid_amount: Option<Decimal>,
226 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
228 pub best_ask_price: Option<Decimal>,
229 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
231 pub best_ask_amount: Option<Decimal>,
232 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
234 pub last_price: Option<Decimal>,
235 #[serde(deserialize_with = "deserialize_decimal")]
237 pub mark_price: Decimal,
238 #[serde(deserialize_with = "deserialize_decimal")]
240 pub index_price: Decimal,
241 #[serde(deserialize_with = "deserialize_decimal")]
243 pub open_interest: Decimal,
244 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
246 pub current_funding: Option<Decimal>,
247 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
249 pub funding_8h: Option<Decimal>,
250 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
252 pub settlement_price: Option<Decimal>,
253 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
255 pub volume: Option<Decimal>,
256 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
258 pub volume_usd: Option<Decimal>,
259 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
261 pub high: Option<Decimal>,
262 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
264 pub low: Option<Decimal>,
265 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
267 pub price_change: Option<Decimal>,
268 pub state: String,
270 pub greeks: Option<DeribitGreeks>,
273 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
275 pub underlying_price: Option<Decimal>,
276 pub underlying_index: Option<String>,
278}
279
280#[derive(Debug, Clone, Deserialize)]
282pub struct DeribitGreeks {
283 #[serde(deserialize_with = "deserialize_decimal")]
284 pub delta: Decimal,
285 #[serde(deserialize_with = "deserialize_decimal")]
286 pub gamma: Decimal,
287 #[serde(deserialize_with = "deserialize_decimal")]
288 pub vega: Decimal,
289 #[serde(deserialize_with = "deserialize_decimal")]
290 pub theta: Decimal,
291 #[serde(deserialize_with = "deserialize_decimal")]
292 pub rho: Decimal,
293}
294
295#[derive(Debug, Clone, Deserialize)]
297pub struct DeribitQuoteMsg {
298 pub instrument_name: Ustr,
300 pub timestamp: u64,
302 #[serde(deserialize_with = "deserialize_decimal")]
304 pub best_bid_price: Decimal,
305 #[serde(deserialize_with = "deserialize_decimal")]
307 pub best_bid_amount: Decimal,
308 #[serde(deserialize_with = "deserialize_decimal")]
310 pub best_ask_price: Decimal,
311 #[serde(deserialize_with = "deserialize_decimal")]
313 pub best_ask_amount: Decimal,
314}
315
316#[derive(Debug, Clone, Deserialize)]
321pub struct DeribitInstrumentStateMsg {
322 pub instrument_name: Ustr,
324 pub state: DeribitInstrumentState,
326 pub timestamp: u64,
328}
329
330#[derive(Debug, Clone, Deserialize)]
336pub struct DeribitPerpetualMsg {
337 #[serde(deserialize_with = "deserialize_decimal")]
339 pub index_price: Decimal,
340 #[serde(deserialize_with = "deserialize_decimal")]
342 pub interest: Decimal,
343 pub timestamp: u64,
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
352#[serde(rename_all = "lowercase")]
353pub enum DeribitChartStatus {
354 #[default]
356 Ok,
357 Imputed,
359}
360
361#[derive(Debug, Clone, Deserialize)]
363pub struct DeribitChartMsg {
364 pub tick: u64,
366 pub open: f64,
368 pub high: f64,
370 pub low: f64,
372 pub close: f64,
374 pub volume: f64,
376 pub cost: f64,
378 #[serde(default)]
380 pub status: DeribitChartStatus,
381}
382
383#[derive(Debug, Clone, Serialize)]
388pub struct DeribitOrderParams {
389 pub instrument_name: String,
391 #[serde(with = "rust_decimal::serde::float")]
393 pub amount: Decimal,
394 #[serde(rename = "type")]
396 pub order_type: String,
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub label: Option<String>,
400 #[serde(
402 skip_serializing_if = "Option::is_none",
403 with = "rust_decimal::serde::float_option"
404 )]
405 pub price: Option<Decimal>,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub time_in_force: Option<String>,
409 #[serde(skip_serializing_if = "Option::is_none")]
412 pub post_only: Option<bool>,
413 #[serde(skip_serializing_if = "Option::is_none")]
416 pub reject_post_only: Option<bool>,
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub reduce_only: Option<bool>,
420 #[serde(
422 skip_serializing_if = "Option::is_none",
423 with = "rust_decimal::serde::float_option"
424 )]
425 pub trigger_price: Option<Decimal>,
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub trigger: Option<String>,
429 #[serde(
431 skip_serializing_if = "Option::is_none",
432 with = "rust_decimal::serde::float_option"
433 )]
434 pub max_show: Option<Decimal>,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub valid_until: Option<u64>,
438}
439
440#[derive(Debug, Clone, Serialize)]
442pub struct DeribitCancelParams {
443 pub order_id: String,
445}
446
447#[derive(Debug, Clone, Serialize)]
449pub struct DeribitCancelAllByInstrumentParams {
450 pub instrument_name: String,
452 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
454 pub order_type: Option<String>,
455}
456
457#[derive(Debug, Clone, Serialize)]
462pub struct DeribitEditParams {
463 pub order_id: String,
465 #[serde(with = "rust_decimal::serde::float")]
467 pub amount: Decimal,
468 #[serde(
470 skip_serializing_if = "Option::is_none",
471 with = "rust_decimal::serde::float_option"
472 )]
473 pub price: Option<Decimal>,
474 #[serde(
476 skip_serializing_if = "Option::is_none",
477 with = "rust_decimal::serde::float_option"
478 )]
479 pub trigger_price: Option<Decimal>,
480 #[serde(skip_serializing_if = "Option::is_none")]
483 pub post_only: Option<bool>,
484 #[serde(skip_serializing_if = "Option::is_none")]
487 pub reject_post_only: Option<bool>,
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub reduce_only: Option<bool>,
491}
492
493#[derive(Debug, Clone, Serialize)]
495pub struct DeribitGetOrderStateParams {
496 pub order_id: String,
498}
499
500#[derive(Debug, Clone, Deserialize)]
504pub struct DeribitOrderResponse {
505 pub order: DeribitOrderMsg,
507 #[serde(default)]
509 pub trades: Vec<DeribitUserTradeMsg>,
510}
511
512#[derive(Debug, Clone, Deserialize)]
516pub struct DeribitOrderMsg {
517 pub order_id: String,
519 pub label: Option<String>,
521 pub instrument_name: Ustr,
523 pub direction: String,
525 pub order_type: String,
527 pub order_state: String,
529 #[serde(
531 default,
532 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
533 )]
534 pub price: Option<Decimal>,
535 #[serde(deserialize_with = "nautilus_core::serialization::deserialize_decimal")]
537 pub amount: Decimal,
538 #[serde(deserialize_with = "nautilus_core::serialization::deserialize_decimal")]
540 pub filled_amount: Decimal,
541 #[serde(
543 default,
544 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
545 )]
546 pub average_price: Option<Decimal>,
547 pub creation_timestamp: u64,
549 pub last_update_timestamp: u64,
551 pub time_in_force: String,
553 #[serde(
555 default,
556 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
557 )]
558 pub commission: Decimal,
559 #[serde(default)]
561 pub post_only: bool,
562 #[serde(default)]
564 pub reduce_only: bool,
565 #[serde(
567 default,
568 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
569 )]
570 pub trigger_price: Option<Decimal>,
571 pub trigger: Option<String>,
573 #[serde(
575 default,
576 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
577 )]
578 pub max_show: Option<Decimal>,
579 #[serde(default)]
581 pub api: bool,
582 pub reject_reason: Option<String>,
584 pub cancel_reason: Option<String>,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
592pub struct DeribitUserTradeMsg {
593 pub trade_id: String,
595 pub order_id: String,
597 pub instrument_name: Ustr,
599 pub direction: String,
601 #[serde(
603 serialize_with = "nautilus_core::serialization::serialize_decimal",
604 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
605 )]
606 pub price: Decimal,
607 #[serde(
609 serialize_with = "nautilus_core::serialization::serialize_decimal",
610 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
611 )]
612 pub amount: Decimal,
613 #[serde(
615 serialize_with = "nautilus_core::serialization::serialize_decimal",
616 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
617 )]
618 pub fee: Decimal,
619 pub fee_currency: String,
621 pub timestamp: u64,
623 pub trade_seq: u64,
625 pub liquidity: String,
627 pub order_type: String,
629 #[serde(
631 serialize_with = "nautilus_core::serialization::serialize_decimal",
632 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
633 )]
634 pub index_price: Decimal,
635 #[serde(
637 serialize_with = "nautilus_core::serialization::serialize_decimal",
638 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
639 )]
640 pub mark_price: Decimal,
641 pub tick_direction: i8,
643 pub state: String,
645 pub label: Option<String>,
647 #[serde(default)]
649 pub reduce_only: bool,
650 #[serde(default)]
652 pub post_only: bool,
653 #[serde(
655 default,
656 serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
657 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
658 )]
659 pub profit_loss: Option<Decimal>,
660}
661
662#[derive(Debug, Clone, Deserialize)]
664pub struct DeribitPortfolioMsg {
665 pub currency: String,
667 #[serde(with = "rust_decimal::serde::float")]
669 pub equity: Decimal,
670 #[serde(with = "rust_decimal::serde::float")]
672 pub balance: Decimal,
673 #[serde(with = "rust_decimal::serde::float")]
675 pub available_funds: Decimal,
676 #[serde(with = "rust_decimal::serde::float")]
678 pub margin_balance: Decimal,
679 #[serde(with = "rust_decimal::serde::float")]
681 pub initial_margin: Decimal,
682 #[serde(with = "rust_decimal::serde::float")]
684 pub maintenance_margin: Decimal,
685}
686
687#[derive(Debug, Clone)]
689pub enum DeribitWsMessage {
690 Response(DeribitJsonRpcResponse<serde_json::Value>),
692 Notification(DeribitSubscriptionNotification<serde_json::Value>),
694 Heartbeat(DeribitHeartbeatData),
696 Error(DeribitJsonRpcError),
698 Reconnected,
700}
701
702#[derive(Debug, Clone, Serialize, Deserialize)]
704pub struct DeribitWebSocketError {
705 pub code: i64,
707 pub message: String,
709 pub timestamp: u64,
711}
712
713impl From<DeribitJsonRpcError> for DeribitWebSocketError {
714 fn from(err: DeribitJsonRpcError) -> Self {
715 Self {
716 code: err.code,
717 message: err.message,
718 timestamp: 0,
719 }
720 }
721}
722
723#[derive(Debug, Clone)]
725pub enum NautilusWsMessage {
726 Data(Vec<Data>),
728 Deltas(OrderBookDeltas),
730 Instrument(Box<InstrumentAny>),
732 FundingRates(Vec<FundingRateUpdate>),
734 OrderStatusReports(Vec<OrderStatusReport>),
736 FillReports(Vec<FillReport>),
738 OrderAccepted(OrderAccepted),
740 OrderCanceled(OrderCanceled),
742 OrderExpired(OrderExpired),
744 OrderRejected(OrderRejected),
746 OrderCancelRejected(OrderCancelRejected),
748 OrderModifyRejected(OrderModifyRejected),
750 OrderUpdated(OrderUpdated),
752 AccountState(AccountState),
754 Error(DeribitWsError),
756 Raw(serde_json::Value),
758 Reconnected,
760 Authenticated(Box<DeribitAuthResult>),
762}
763
764pub fn parse_raw_message(text: &str) -> Result<DeribitWsMessage, DeribitWsError> {
770 let value: serde_json::Value =
771 serde_json::from_str(text).map_err(|e| DeribitWsError::Json(e.to_string()))?;
772
773 if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
775 if method == "subscription" {
776 let notification: DeribitSubscriptionNotification<serde_json::Value> =
777 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
778 return Ok(DeribitWsMessage::Notification(notification));
779 }
780 if method == "heartbeat"
782 && let Some(params) = value.get("params")
783 {
784 let heartbeat: DeribitHeartbeatData = serde_json::from_value(params.clone())
785 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
786 return Ok(DeribitWsMessage::Heartbeat(heartbeat));
787 }
788 }
789
790 if value.get("id").is_some() {
795 let response: DeribitJsonRpcResponse<serde_json::Value> =
796 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
797 return Ok(DeribitWsMessage::Response(response));
798 }
799
800 let response: DeribitJsonRpcResponse<serde_json::Value> =
802 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
803 Ok(DeribitWsMessage::Response(response))
804}
805
806pub fn extract_instrument_from_channel(channel: &str) -> Option<&str> {
810 let parts: Vec<&str> = channel.split('.').collect();
811 if parts.len() >= 2 {
812 Some(parts[1])
813 } else {
814 None
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use rstest::rstest;
821
822 use super::*;
823
824 #[rstest]
825 fn test_parse_subscription_notification() {
826 let json = r#"{
827 "jsonrpc": "2.0",
828 "method": "subscription",
829 "params": {
830 "channel": "trades.BTC-PERPETUAL.raw",
831 "data": [{"trade_id": "123", "price": 50000.0}]
832 }
833 }"#;
834
835 let msg = parse_raw_message(json).unwrap();
836 assert!(matches!(msg, DeribitWsMessage::Notification(_)));
837 }
838
839 #[rstest]
840 fn test_parse_response() {
841 let json = r#"{
842 "jsonrpc": "2.0",
843 "id": 1,
844 "result": ["trades.BTC-PERPETUAL.raw"],
845 "testnet": true,
846 "usIn": 1234567890,
847 "usOut": 1234567891,
848 "usDiff": 1
849 }"#;
850
851 let msg = parse_raw_message(json).unwrap();
852 assert!(matches!(msg, DeribitWsMessage::Response(_)));
853 }
854
855 #[rstest]
856 fn test_parse_error_response() {
857 let json = r#"{
860 "jsonrpc": "2.0",
861 "id": 1,
862 "error": {
863 "code": 10028,
864 "message": "too_many_requests"
865 }
866 }"#;
867
868 let msg = parse_raw_message(json).unwrap();
869 match msg {
870 DeribitWsMessage::Response(resp) => {
871 assert!(resp.error.is_some());
872 let error = resp.error.unwrap();
873 assert_eq!(error.code, 10028);
874 assert_eq!(error.message, "too_many_requests");
875 }
876 _ => panic!("Expected Response with error, was {msg:?}"),
877 }
878 }
879
880 #[rstest]
881 fn test_extract_instrument_from_channel() {
882 assert_eq!(
883 extract_instrument_from_channel("trades.BTC-PERPETUAL.raw"),
884 Some("BTC-PERPETUAL")
885 );
886 assert_eq!(
887 extract_instrument_from_channel("book.ETH-25DEC25.raw"),
888 Some("ETH-25DEC25")
889 );
890 assert_eq!(extract_instrument_from_channel("platform_state"), None);
891 }
892}