nautilus_deribit/websocket/
enums.rs1use std::fmt::Display;
19
20use nautilus_model::enums::BookAction;
21use serde::{Deserialize, Serialize};
22use strum::{AsRefStr, Display, EnumIter, EnumString};
23
24#[derive(
29 Clone,
30 Copy,
31 Debug,
32 Default,
33 PartialEq,
34 Eq,
35 Hash,
36 AsRefStr,
37 EnumIter,
38 EnumString,
39 Serialize,
40 Deserialize,
41)]
42#[serde(rename_all = "snake_case")]
43pub enum DeribitUpdateInterval {
44 #[strum(serialize = "raw", serialize = "Raw")]
47 Raw,
48 #[default]
50 #[strum(serialize = "100ms", serialize = "Ms100")]
51 Ms100,
52 #[strum(serialize = "agg2", serialize = "Agg2")]
54 Agg2,
55}
56
57impl DeribitUpdateInterval {
58 #[must_use]
60 pub const fn as_str(&self) -> &'static str {
61 match self {
62 Self::Raw => "raw",
63 Self::Ms100 => "100ms",
64 Self::Agg2 => "agg2",
65 }
66 }
67
68 #[must_use]
70 pub const fn requires_auth(&self) -> bool {
71 matches!(self, Self::Raw)
72 }
73}
74
75impl Display for DeribitUpdateInterval {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(f, "{}", self.as_str())
78 }
79}
80
81#[derive(
85 Clone,
86 Copy,
87 Debug,
88 Display,
89 PartialEq,
90 Eq,
91 Hash,
92 AsRefStr,
93 EnumIter,
94 EnumString,
95 Serialize,
96 Deserialize,
97)]
98pub enum DeribitWsChannel {
99 Trades,
102 Book,
104 Ticker,
106 Quote,
108 PriceIndex,
110 PriceRanking,
112 VolatilityIndex,
114 EstimatedExpirationPrice,
116 Perpetual,
118 MarkPriceOptions,
120 PlatformState,
122 Announcements,
124 ChartTrades,
126 InstrumentState,
129
130 UserOrders,
133 UserTrades,
135 UserPortfolio,
137 UserChanges,
139 UserAccessLog,
141}
142
143impl DeribitWsChannel {
144 #[must_use]
161 pub fn format_channel(
162 &self,
163 instrument_or_currency: &str,
164 interval: Option<DeribitUpdateInterval>,
165 ) -> String {
166 let interval_str = interval.unwrap_or_default().as_str();
167 match self {
168 Self::Trades => format!("trades.{instrument_or_currency}.{interval_str}"),
169 Self::Book => format!("book.{instrument_or_currency}.{interval_str}"),
170 Self::Ticker => format!("ticker.{instrument_or_currency}.{interval_str}"),
171 Self::Quote => format!("quote.{instrument_or_currency}"),
172 Self::PriceIndex => format!("deribit_price_index.{instrument_or_currency}"),
173 Self::PriceRanking => format!("deribit_price_ranking.{instrument_or_currency}"),
174 Self::VolatilityIndex => format!("deribit_volatility_index.{instrument_or_currency}"),
175 Self::EstimatedExpirationPrice => {
176 format!("estimated_expiration_price.{instrument_or_currency}")
177 }
178 Self::Perpetual => format!("perpetual.{instrument_or_currency}.{interval_str}"),
179 Self::MarkPriceOptions => format!("markprice.options.{instrument_or_currency}"),
180 Self::PlatformState => "platform_state".to_string(),
181 Self::Announcements => "announcements".to_string(),
182 Self::ChartTrades => format!("chart.trades.{instrument_or_currency}.{interval_str}"),
183 Self::UserOrders => format!("user.orders.{instrument_or_currency}.{interval_str}"),
184 Self::UserTrades => format!("user.trades.{instrument_or_currency}.{interval_str}"),
185 Self::UserPortfolio => format!("user.portfolio.{instrument_or_currency}"),
186 Self::UserChanges => format!("user.changes.{instrument_or_currency}.{interval_str}"),
187 Self::UserAccessLog => "user.access_log".to_string(),
188 Self::InstrumentState => {
189 panic!(
191 "InstrumentState channel requires kind and currency parameters, use format_instrument_state_channel() instead"
192 )
193 }
194 }
195 }
196
197 #[must_use]
206 pub fn format_instrument_state_channel(kind: &str, currency: &str) -> String {
207 format!("instrument.state.{kind}.{currency}")
208 }
209
210 #[must_use]
214 pub fn from_channel_string(channel: &str) -> Option<Self> {
215 if channel.starts_with("trades.") {
216 Some(Self::Trades)
217 } else if channel.starts_with("book.") {
218 Some(Self::Book)
219 } else if channel.starts_with("ticker.") {
220 Some(Self::Ticker)
221 } else if channel.starts_with("quote.") {
222 Some(Self::Quote)
223 } else if channel.starts_with("deribit_price_index.") {
224 Some(Self::PriceIndex)
225 } else if channel.starts_with("deribit_price_ranking.") {
226 Some(Self::PriceRanking)
227 } else if channel.starts_with("deribit_volatility_index.") {
228 Some(Self::VolatilityIndex)
229 } else if channel.starts_with("estimated_expiration_price.") {
230 Some(Self::EstimatedExpirationPrice)
231 } else if channel.starts_with("perpetual.") {
232 Some(Self::Perpetual)
233 } else if channel.starts_with("markprice.options.") {
234 Some(Self::MarkPriceOptions)
235 } else if channel == "platform_state" {
236 Some(Self::PlatformState)
237 } else if channel == "announcements" {
238 Some(Self::Announcements)
239 } else if channel.starts_with("chart.trades.") {
240 Some(Self::ChartTrades)
241 } else if channel.starts_with("user.orders.") {
242 Some(Self::UserOrders)
243 } else if channel.starts_with("user.trades.") {
244 Some(Self::UserTrades)
245 } else if channel.starts_with("user.portfolio.") {
246 Some(Self::UserPortfolio)
247 } else if channel.starts_with("user.changes.") {
248 Some(Self::UserChanges)
249 } else if channel == "user.access_log" {
250 Some(Self::UserAccessLog)
251 } else if channel.starts_with("instrument.state.") {
252 Some(Self::InstrumentState)
253 } else {
254 None
255 }
256 }
257
258 #[must_use]
260 pub const fn is_private(&self) -> bool {
261 matches!(
262 self,
263 Self::UserOrders
264 | Self::UserTrades
265 | Self::UserPortfolio
266 | Self::UserChanges
267 | Self::UserAccessLog
268 )
269 }
270
271 #[must_use]
277 pub fn requires_auth(channel: &str) -> bool {
278 match Self::from_channel_string(channel) {
279 Some(ch) if ch.is_private() => true,
280 Some(_) => channel.ends_with(".raw"),
281 None => false,
282 }
283 }
284}
285
286#[derive(
288 Clone,
289 Debug,
290 Display,
291 PartialEq,
292 Eq,
293 Hash,
294 AsRefStr,
295 EnumIter,
296 EnumString,
297 Serialize,
298 Deserialize,
299)]
300pub enum DeribitWsMethod {
301 #[serde(rename = "public/subscribe")]
304 #[strum(serialize = "public/subscribe")]
305 PublicSubscribe,
306 #[serde(rename = "public/unsubscribe")]
308 #[strum(serialize = "public/unsubscribe")]
309 PublicUnsubscribe,
310 #[serde(rename = "public/auth")]
312 #[strum(serialize = "public/auth")]
313 PublicAuth,
314 #[serde(rename = "public/set_heartbeat")]
316 #[strum(serialize = "public/set_heartbeat")]
317 SetHeartbeat,
318 #[serde(rename = "public/disable_heartbeat")]
320 #[strum(serialize = "public/disable_heartbeat")]
321 DisableHeartbeat,
322 #[serde(rename = "public/test")]
324 #[strum(serialize = "public/test")]
325 Test,
326 #[serde(rename = "public/hello")]
328 #[strum(serialize = "public/hello")]
329 Hello,
330 #[serde(rename = "public/get_time")]
332 #[strum(serialize = "public/get_time")]
333 GetTime,
334
335 #[serde(rename = "private/subscribe")]
337 #[strum(serialize = "private/subscribe")]
338 PrivateSubscribe,
339 #[serde(rename = "private/unsubscribe")]
341 #[strum(serialize = "private/unsubscribe")]
342 PrivateUnsubscribe,
343 #[serde(rename = "private/logout")]
345 #[strum(serialize = "private/logout")]
346 Logout,
347 #[serde(rename = "private/buy")]
349 #[strum(serialize = "private/buy")]
350 Buy,
351 #[serde(rename = "private/sell")]
353 #[strum(serialize = "private/sell")]
354 Sell,
355 #[serde(rename = "private/edit")]
357 #[strum(serialize = "private/edit")]
358 Edit,
359 #[serde(rename = "private/cancel")]
361 #[strum(serialize = "private/cancel")]
362 Cancel,
363 #[serde(rename = "private/cancel_all_by_instrument")]
365 #[strum(serialize = "private/cancel_all_by_instrument")]
366 CancelAllByInstrument,
367 #[serde(rename = "private/get_order_state")]
369 #[strum(serialize = "private/get_order_state")]
370 GetOrderState,
371}
372
373impl DeribitWsMethod {
374 #[must_use]
376 pub fn as_method_str(&self) -> &str {
377 self.as_ref()
378 }
379}
380
381#[derive(
383 Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
384)]
385#[serde(rename_all = "snake_case")]
386#[strum(serialize_all = "snake_case")]
387pub enum DeribitBookAction {
388 #[serde(rename = "new")]
390 New,
391 #[serde(rename = "change")]
393 Change,
394 #[serde(rename = "delete")]
396 Delete,
397}
398
399impl From<DeribitBookAction> for BookAction {
400 fn from(action: DeribitBookAction) -> Self {
401 match action {
402 DeribitBookAction::New => Self::Add,
403 DeribitBookAction::Change => Self::Update,
404 DeribitBookAction::Delete => Self::Delete,
405 }
406 }
407}
408
409#[derive(
411 Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
412)]
413#[serde(rename_all = "snake_case")]
414pub enum DeribitBookMsgType {
415 #[serde(rename = "snapshot")]
417 Snapshot,
418 #[serde(rename = "change")]
420 Change,
421}
422
423#[cfg(test)]
424mod tests {
425 use rstest::rstest;
426
427 use super::*;
428
429 #[rstest]
430 fn test_requires_auth_user_channels() {
431 assert!(DeribitWsChannel::requires_auth("user.orders.any.any.raw"));
432 assert!(DeribitWsChannel::requires_auth("user.trades.any.any.raw"));
433 assert!(DeribitWsChannel::requires_auth("user.portfolio.any"));
434 assert!(DeribitWsChannel::requires_auth("user.changes.any.any.raw"));
435 assert!(DeribitWsChannel::requires_auth("user.access_log"));
436 }
437
438 #[rstest]
439 fn test_requires_auth_raw_channels() {
440 assert!(DeribitWsChannel::requires_auth("book.BTC-PERPETUAL.raw"));
441 assert!(DeribitWsChannel::requires_auth("book.ETH-25DEC25.raw"));
442 assert!(DeribitWsChannel::requires_auth("trades.BTC-PERPETUAL.raw"));
443 assert!(DeribitWsChannel::requires_auth("ticker.BTC-PERPETUAL.raw"));
444 }
445
446 #[rstest]
447 fn test_requires_auth_public_channels() {
448 assert!(!DeribitWsChannel::requires_auth(
449 "book.BTC-PERPETUAL.none.10.100ms"
450 ));
451 assert!(!DeribitWsChannel::requires_auth(
452 "book.BTC-PERPETUAL.none.20.agg2"
453 ));
454 assert!(!DeribitWsChannel::requires_auth(
455 "trades.BTC-PERPETUAL.100ms"
456 ));
457 assert!(!DeribitWsChannel::requires_auth(
458 "ticker.BTC-PERPETUAL.100ms"
459 ));
460 assert!(!DeribitWsChannel::requires_auth("quote.BTC-PERPETUAL"));
461 assert!(!DeribitWsChannel::requires_auth("deribit_price_index.btc"));
462 assert!(!DeribitWsChannel::requires_auth("platform_state"));
463 assert!(!DeribitWsChannel::requires_auth("announcements"));
464 }
465}
466
467#[derive(
469 Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
470)]
471#[serde(rename_all = "snake_case")]
472pub enum DeribitHeartbeatType {
473 #[serde(rename = "heartbeat")]
475 Heartbeat,
476 #[serde(rename = "test_request")]
478 TestRequest,
479}