nautilus_deribit/websocket/
messages.rs1use nautilus_model::{
19 data::{Data, OrderBookDeltas},
20 instruments::InstrumentAny,
21};
22use serde::{Deserialize, Serialize};
23use ustr::Ustr;
24
25use super::enums::{DeribitBookAction, DeribitBookMsgType, DeribitHeartbeatType};
26pub use crate::common::rpc::{DeribitJsonRpcError, DeribitJsonRpcRequest, DeribitJsonRpcResponse};
27use crate::websocket::error::DeribitWsError;
28
29#[derive(Debug, Clone, Deserialize)]
31pub struct DeribitSubscriptionNotification<T> {
32 pub jsonrpc: String,
34 pub method: String,
36 pub params: DeribitSubscriptionParams<T>,
38}
39
40#[derive(Debug, Clone, Deserialize)]
42pub struct DeribitSubscriptionParams<T> {
43 pub channel: String,
45 pub data: T,
47}
48
49#[derive(Debug, Clone, Serialize)]
51pub struct DeribitAuthParams {
52 pub grant_type: String,
54 pub client_id: String,
56 pub timestamp: u64,
58 pub signature: String,
60 pub nonce: String,
62 pub data: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
68 pub scope: Option<String>,
69}
70
71#[derive(Debug, Clone, Serialize)]
73pub struct DeribitRefreshTokenParams {
74 pub grant_type: String,
76 pub refresh_token: String,
78}
79
80#[derive(Debug, Clone, Deserialize)]
82pub struct DeribitAuthResult {
83 pub access_token: String,
85 pub expires_in: u64,
87 pub refresh_token: String,
89 pub scope: String,
91 pub token_type: String,
93 #[serde(default)]
95 pub enabled_features: Vec<String>,
96}
97
98#[derive(Debug, Clone, Serialize)]
100pub struct DeribitSubscribeParams {
101 pub channels: Vec<String>,
103}
104
105#[derive(Debug, Clone, Deserialize)]
107pub struct DeribitSubscribeResult(pub Vec<String>);
108
109#[derive(Debug, Clone, Serialize)]
111pub struct DeribitHeartbeatParams {
112 pub interval: u64,
114}
115
116#[derive(Debug, Clone, Deserialize)]
118pub struct DeribitHeartbeatData {
119 #[serde(rename = "type")]
121 pub heartbeat_type: DeribitHeartbeatType,
122}
123
124#[derive(Debug, Clone, Deserialize)]
126pub struct DeribitTradeMsg {
127 pub trade_id: String,
129 pub instrument_name: Ustr,
131 pub price: f64,
133 pub amount: f64,
135 pub direction: String,
137 pub timestamp: u64,
139 pub trade_seq: u64,
141 pub tick_direction: i8,
143 pub index_price: f64,
145 pub mark_price: f64,
147 pub iv: Option<f64>,
149 pub liquidation: Option<String>,
151 pub combo_trade_id: Option<i64>,
153 pub block_trade_id: Option<String>,
155 pub combo_id: Option<String>,
157}
158
159#[derive(Debug, Clone, Deserialize)]
161pub struct DeribitBookMsg {
162 #[serde(rename = "type")]
164 pub msg_type: DeribitBookMsgType,
165 pub instrument_name: Ustr,
167 pub timestamp: u64,
169 pub change_id: u64,
171 pub prev_change_id: Option<u64>,
173 pub bids: Vec<Vec<serde_json::Value>>,
175 pub asks: Vec<Vec<serde_json::Value>>,
177}
178
179#[derive(Debug, Clone)]
181pub struct DeribitBookLevel {
182 pub price: f64,
184 pub amount: f64,
186 pub action: Option<DeribitBookAction>,
188}
189
190#[derive(Debug, Clone, Deserialize)]
192pub struct DeribitTickerMsg {
193 pub instrument_name: Ustr,
195 pub timestamp: u64,
197 pub best_bid_price: Option<f64>,
199 pub best_bid_amount: Option<f64>,
201 pub best_ask_price: Option<f64>,
203 pub best_ask_amount: Option<f64>,
205 pub last_price: Option<f64>,
207 pub mark_price: f64,
209 pub index_price: f64,
211 pub open_interest: f64,
213 pub current_funding: Option<f64>,
215 pub funding_8h: Option<f64>,
217 pub settlement_price: Option<f64>,
219 pub volume: Option<f64>,
221 pub volume_usd: Option<f64>,
223 pub high: Option<f64>,
225 pub low: Option<f64>,
227 pub price_change: Option<f64>,
229 pub state: String,
231 pub greeks: Option<DeribitGreeks>,
234 pub underlying_price: Option<f64>,
236 pub underlying_index: Option<String>,
238}
239
240#[derive(Debug, Clone, Deserialize)]
242pub struct DeribitGreeks {
243 pub delta: f64,
244 pub gamma: f64,
245 pub vega: f64,
246 pub theta: f64,
247 pub rho: f64,
248}
249
250#[derive(Debug, Clone, Deserialize)]
252pub struct DeribitQuoteMsg {
253 pub instrument_name: Ustr,
255 pub timestamp: u64,
257 pub best_bid_price: f64,
259 pub best_bid_amount: f64,
261 pub best_ask_price: f64,
263 pub best_ask_amount: f64,
265}
266
267#[derive(Debug, Clone)]
269pub enum DeribitWsMessage {
270 Response(DeribitJsonRpcResponse<serde_json::Value>),
272 Notification(DeribitSubscriptionNotification<serde_json::Value>),
274 Heartbeat(DeribitHeartbeatData),
276 Error(DeribitJsonRpcError),
278 Reconnected,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct DeribitWebSocketError {
285 pub code: i64,
287 pub message: String,
289 pub timestamp: u64,
291}
292
293impl From<DeribitJsonRpcError> for DeribitWebSocketError {
294 fn from(err: DeribitJsonRpcError) -> Self {
295 Self {
296 code: err.code,
297 message: err.message,
298 timestamp: 0,
299 }
300 }
301}
302
303#[derive(Debug, Clone)]
305pub enum NautilusWsMessage {
306 Data(Vec<Data>),
308 Deltas(OrderBookDeltas),
310 Instrument(Box<InstrumentAny>),
312 Error(DeribitWsError),
314 Raw(serde_json::Value),
316 Reconnected,
318 Authenticated(Box<DeribitAuthResult>),
320}
321
322pub fn parse_raw_message(text: &str) -> Result<DeribitWsMessage, DeribitWsError> {
328 let value: serde_json::Value =
329 serde_json::from_str(text).map_err(|e| DeribitWsError::Json(e.to_string()))?;
330
331 if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
333 if method == "subscription" {
334 let notification: DeribitSubscriptionNotification<serde_json::Value> =
335 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
336 return Ok(DeribitWsMessage::Notification(notification));
337 }
338 if method == "heartbeat"
340 && let Some(params) = value.get("params")
341 {
342 let heartbeat: DeribitHeartbeatData = serde_json::from_value(params.clone())
343 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
344 return Ok(DeribitWsMessage::Heartbeat(heartbeat));
345 }
346 }
347
348 if value.get("id").is_some() {
350 if value.get("error").is_some() {
352 let response: DeribitJsonRpcResponse<serde_json::Value> =
353 serde_json::from_value(value.clone())
354 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
355 if let Some(err) = response.error {
356 return Ok(DeribitWsMessage::Error(err));
357 }
358 }
359 let response: DeribitJsonRpcResponse<serde_json::Value> =
361 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
362 return Ok(DeribitWsMessage::Response(response));
363 }
364
365 let response: DeribitJsonRpcResponse<serde_json::Value> =
367 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
368 Ok(DeribitWsMessage::Response(response))
369}
370
371pub fn extract_instrument_from_channel(channel: &str) -> Option<&str> {
375 let parts: Vec<&str> = channel.split('.').collect();
376 if parts.len() >= 2 {
377 Some(parts[1])
378 } else {
379 None
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use rstest::rstest;
386
387 use super::*;
388
389 #[rstest]
390 fn test_parse_subscription_notification() {
391 let json = r#"{
392 "jsonrpc": "2.0",
393 "method": "subscription",
394 "params": {
395 "channel": "trades.BTC-PERPETUAL.raw",
396 "data": [{"trade_id": "123", "price": 50000.0}]
397 }
398 }"#;
399
400 let msg = parse_raw_message(json).unwrap();
401 assert!(matches!(msg, DeribitWsMessage::Notification(_)));
402 }
403
404 #[rstest]
405 fn test_parse_response() {
406 let json = r#"{
407 "jsonrpc": "2.0",
408 "id": 1,
409 "result": ["trades.BTC-PERPETUAL.raw"],
410 "testnet": true,
411 "usIn": 1234567890,
412 "usOut": 1234567891,
413 "usDiff": 1
414 }"#;
415
416 let msg = parse_raw_message(json).unwrap();
417 assert!(matches!(msg, DeribitWsMessage::Response(_)));
418 }
419
420 #[rstest]
421 fn test_parse_error_response() {
422 let json = r#"{
423 "jsonrpc": "2.0",
424 "id": 1,
425 "error": {
426 "code": 10028,
427 "message": "too_many_requests"
428 }
429 }"#;
430
431 let msg = parse_raw_message(json).unwrap();
432 assert!(matches!(msg, DeribitWsMessage::Error(_)));
433 }
434
435 #[rstest]
436 fn test_extract_instrument_from_channel() {
437 assert_eq!(
438 extract_instrument_from_channel("trades.BTC-PERPETUAL.raw"),
439 Some("BTC-PERPETUAL")
440 );
441 assert_eq!(
442 extract_instrument_from_channel("book.ETH-25DEC25.raw"),
443 Some("ETH-25DEC25")
444 );
445 assert_eq!(extract_instrument_from_channel("platform_state"), None);
446 }
447}