1use std::fmt::Display;
19
20use nautilus_model::{
21 data::{Data, FundingRateUpdate, OrderBookDeltas},
22 instruments::InstrumentAny,
23};
24use serde::{Deserialize, Serialize};
25use ustr::Ustr;
26
27use super::enums::{DeribitBookAction, DeribitBookMsgType, DeribitHeartbeatType};
28pub use crate::common::rpc::{DeribitJsonRpcError, DeribitJsonRpcRequest, DeribitJsonRpcResponse};
29use crate::websocket::error::DeribitWsError;
30
31#[derive(Debug, Clone, Deserialize)]
33pub struct DeribitSubscriptionNotification<T> {
34 pub jsonrpc: String,
36 pub method: String,
38 pub params: DeribitSubscriptionParams<T>,
40}
41
42#[derive(Debug, Clone, Deserialize)]
44pub struct DeribitSubscriptionParams<T> {
45 pub channel: String,
47 pub data: T,
49}
50
51#[derive(Debug, Clone, Serialize)]
53pub struct DeribitAuthParams {
54 pub grant_type: String,
56 pub client_id: String,
58 pub timestamp: u64,
60 pub signature: String,
62 pub nonce: String,
64 pub data: String,
66 #[serde(skip_serializing_if = "Option::is_none")]
70 pub scope: Option<String>,
71}
72
73#[derive(Debug, Clone, Serialize)]
75pub struct DeribitRefreshTokenParams {
76 pub grant_type: String,
78 pub refresh_token: String,
80}
81
82#[derive(Debug, Clone, Deserialize)]
84pub struct DeribitAuthResult {
85 pub access_token: String,
87 pub expires_in: u64,
89 pub refresh_token: String,
91 pub scope: String,
93 pub token_type: String,
95 #[serde(default)]
97 pub enabled_features: Vec<String>,
98}
99
100#[derive(Debug, Clone, Serialize)]
102pub struct DeribitSubscribeParams {
103 pub channels: Vec<String>,
105}
106
107#[derive(Debug, Clone, Deserialize)]
109pub struct DeribitSubscribeResult(pub Vec<String>);
110
111#[derive(Debug, Clone, Serialize)]
113pub struct DeribitHeartbeatParams {
114 pub interval: u64,
116}
117
118#[derive(Debug, Clone, Deserialize)]
120pub struct DeribitHeartbeatData {
121 #[serde(rename = "type")]
123 pub heartbeat_type: DeribitHeartbeatType,
124}
125
126#[derive(Debug, Clone, Deserialize)]
128pub struct DeribitTradeMsg {
129 pub trade_id: String,
131 pub instrument_name: Ustr,
133 pub price: f64,
135 pub amount: f64,
137 pub direction: String,
139 pub timestamp: u64,
141 pub trade_seq: u64,
143 pub tick_direction: i8,
145 pub index_price: f64,
147 pub mark_price: f64,
149 pub iv: Option<f64>,
151 pub liquidation: Option<String>,
153 pub combo_trade_id: Option<i64>,
155 pub block_trade_id: Option<String>,
157 pub combo_id: Option<String>,
159}
160
161#[derive(Debug, Clone, Deserialize)]
163pub struct DeribitBookMsg {
164 #[serde(rename = "type")]
166 pub msg_type: DeribitBookMsgType,
167 pub instrument_name: Ustr,
169 pub timestamp: u64,
171 pub change_id: u64,
173 pub prev_change_id: Option<u64>,
175 pub bids: Vec<Vec<serde_json::Value>>,
177 pub asks: Vec<Vec<serde_json::Value>>,
179}
180
181#[derive(Debug, Clone)]
183pub struct DeribitBookLevel {
184 pub price: f64,
186 pub amount: f64,
188 pub action: Option<DeribitBookAction>,
190}
191
192#[derive(Debug, Clone, Deserialize)]
194pub struct DeribitTickerMsg {
195 pub instrument_name: Ustr,
197 pub timestamp: u64,
199 pub best_bid_price: Option<f64>,
201 pub best_bid_amount: Option<f64>,
203 pub best_ask_price: Option<f64>,
205 pub best_ask_amount: Option<f64>,
207 pub last_price: Option<f64>,
209 pub mark_price: f64,
211 pub index_price: f64,
213 pub open_interest: f64,
215 pub current_funding: Option<f64>,
217 pub funding_8h: Option<f64>,
219 pub settlement_price: Option<f64>,
221 pub volume: Option<f64>,
223 pub volume_usd: Option<f64>,
225 pub high: Option<f64>,
227 pub low: Option<f64>,
229 pub price_change: Option<f64>,
231 pub state: String,
233 pub greeks: Option<DeribitGreeks>,
236 pub underlying_price: Option<f64>,
238 pub underlying_index: Option<String>,
240}
241
242#[derive(Debug, Clone, Deserialize)]
244pub struct DeribitGreeks {
245 pub delta: f64,
246 pub gamma: f64,
247 pub vega: f64,
248 pub theta: f64,
249 pub rho: f64,
250}
251
252#[derive(Debug, Clone, Deserialize)]
254pub struct DeribitQuoteMsg {
255 pub instrument_name: Ustr,
257 pub timestamp: u64,
259 pub best_bid_price: f64,
261 pub best_bid_amount: f64,
263 pub best_ask_price: f64,
265 pub best_ask_amount: f64,
267}
268
269#[derive(
273 Debug,
274 Clone,
275 Copy,
276 PartialEq,
277 Eq,
278 Hash,
279 Serialize,
280 Deserialize,
281 strum::AsRefStr,
282 strum::EnumIter,
283 strum::EnumString,
284)]
285#[serde(rename_all = "snake_case")]
286#[strum(serialize_all = "snake_case")]
287#[cfg_attr(
288 feature = "python",
289 pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.deribit")
290)]
291pub enum DeribitInstrumentState {
292 Created,
294 Started,
296 Settled,
298 Closed,
300 Terminated,
302}
303
304impl Display for DeribitInstrumentState {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 match self {
307 Self::Created => write!(f, "created"),
308 Self::Started => write!(f, "started"),
309 Self::Settled => write!(f, "settled"),
310 Self::Closed => write!(f, "closed"),
311 Self::Terminated => write!(f, "terminated"),
312 }
313 }
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 pub index_price: f64,
339 pub interest: f64,
341 pub timestamp: u64,
343}
344
345#[derive(Debug, Clone, Deserialize)]
350pub struct DeribitChartMsg {
351 pub tick: u64,
353 pub open: f64,
355 pub high: f64,
357 pub low: f64,
359 pub close: f64,
361 pub volume: f64,
363 pub cost: f64,
365}
366
367#[derive(Debug, Clone)]
369pub enum DeribitWsMessage {
370 Response(DeribitJsonRpcResponse<serde_json::Value>),
372 Notification(DeribitSubscriptionNotification<serde_json::Value>),
374 Heartbeat(DeribitHeartbeatData),
376 Error(DeribitJsonRpcError),
378 Reconnected,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct DeribitWebSocketError {
385 pub code: i64,
387 pub message: String,
389 pub timestamp: u64,
391}
392
393impl From<DeribitJsonRpcError> for DeribitWebSocketError {
394 fn from(err: DeribitJsonRpcError) -> Self {
395 Self {
396 code: err.code,
397 message: err.message,
398 timestamp: 0,
399 }
400 }
401}
402
403#[derive(Debug, Clone)]
405pub enum NautilusWsMessage {
406 Data(Vec<Data>),
408 Deltas(OrderBookDeltas),
410 Instrument(Box<InstrumentAny>),
412 FundingRates(Vec<FundingRateUpdate>),
414 Error(DeribitWsError),
416 Raw(serde_json::Value),
418 Reconnected,
420 Authenticated(Box<DeribitAuthResult>),
422}
423
424pub fn parse_raw_message(text: &str) -> Result<DeribitWsMessage, DeribitWsError> {
430 let value: serde_json::Value =
431 serde_json::from_str(text).map_err(|e| DeribitWsError::Json(e.to_string()))?;
432
433 if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
435 if method == "subscription" {
436 let notification: DeribitSubscriptionNotification<serde_json::Value> =
437 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
438 return Ok(DeribitWsMessage::Notification(notification));
439 }
440 if method == "heartbeat"
442 && let Some(params) = value.get("params")
443 {
444 let heartbeat: DeribitHeartbeatData = serde_json::from_value(params.clone())
445 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
446 return Ok(DeribitWsMessage::Heartbeat(heartbeat));
447 }
448 }
449
450 if value.get("id").is_some() {
452 if value.get("error").is_some() {
454 let response: DeribitJsonRpcResponse<serde_json::Value> =
455 serde_json::from_value(value.clone())
456 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
457 if let Some(err) = response.error {
458 return Ok(DeribitWsMessage::Error(err));
459 }
460 }
461 let response: DeribitJsonRpcResponse<serde_json::Value> =
463 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
464 return Ok(DeribitWsMessage::Response(response));
465 }
466
467 let response: DeribitJsonRpcResponse<serde_json::Value> =
469 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
470 Ok(DeribitWsMessage::Response(response))
471}
472
473pub fn extract_instrument_from_channel(channel: &str) -> Option<&str> {
477 let parts: Vec<&str> = channel.split('.').collect();
478 if parts.len() >= 2 {
479 Some(parts[1])
480 } else {
481 None
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use rstest::rstest;
488
489 use super::*;
490
491 #[rstest]
492 fn test_parse_subscription_notification() {
493 let json = r#"{
494 "jsonrpc": "2.0",
495 "method": "subscription",
496 "params": {
497 "channel": "trades.BTC-PERPETUAL.raw",
498 "data": [{"trade_id": "123", "price": 50000.0}]
499 }
500 }"#;
501
502 let msg = parse_raw_message(json).unwrap();
503 assert!(matches!(msg, DeribitWsMessage::Notification(_)));
504 }
505
506 #[rstest]
507 fn test_parse_response() {
508 let json = r#"{
509 "jsonrpc": "2.0",
510 "id": 1,
511 "result": ["trades.BTC-PERPETUAL.raw"],
512 "testnet": true,
513 "usIn": 1234567890,
514 "usOut": 1234567891,
515 "usDiff": 1
516 }"#;
517
518 let msg = parse_raw_message(json).unwrap();
519 assert!(matches!(msg, DeribitWsMessage::Response(_)));
520 }
521
522 #[rstest]
523 fn test_parse_error_response() {
524 let json = r#"{
525 "jsonrpc": "2.0",
526 "id": 1,
527 "error": {
528 "code": 10028,
529 "message": "too_many_requests"
530 }
531 }"#;
532
533 let msg = parse_raw_message(json).unwrap();
534 assert!(matches!(msg, DeribitWsMessage::Error(_)));
535 }
536
537 #[rstest]
538 fn test_extract_instrument_from_channel() {
539 assert_eq!(
540 extract_instrument_from_channel("trades.BTC-PERPETUAL.raw"),
541 Some("BTC-PERPETUAL")
542 );
543 assert_eq!(
544 extract_instrument_from_channel("book.ETH-25DEC25.raw"),
545 Some("ETH-25DEC25")
546 );
547 assert_eq!(extract_instrument_from_channel("platform_state"), None);
548 }
549}