1use 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")]
43#[cfg_attr(
44 feature = "python",
45 pyo3::pyclass(
46 eq,
47 eq_int,
48 module = "nautilus_trader.core.nautilus_pyo3.deribit",
49 from_py_object,
50 rename_all = "SCREAMING_SNAKE_CASE",
51 )
52)]
53#[cfg_attr(
54 feature = "python",
55 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.deribit")
56)]
57pub enum DeribitUpdateInterval {
58 #[strum(serialize = "raw", serialize = "Raw")]
61 Raw,
62 #[default]
64 #[strum(serialize = "100ms", serialize = "Ms100")]
65 Ms100,
66 #[strum(serialize = "agg2", serialize = "Agg2")]
68 Agg2,
69}
70
71impl DeribitUpdateInterval {
72 #[must_use]
74 pub const fn as_str(&self) -> &'static str {
75 match self {
76 Self::Raw => "raw",
77 Self::Ms100 => "100ms",
78 Self::Agg2 => "agg2",
79 }
80 }
81
82 #[must_use]
84 pub const fn requires_auth(&self) -> bool {
85 matches!(self, Self::Raw)
86 }
87}
88
89impl Display for DeribitUpdateInterval {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 write!(f, "{}", self.as_str())
92 }
93}
94
95#[derive(
99 Clone,
100 Copy,
101 Debug,
102 Display,
103 PartialEq,
104 Eq,
105 Hash,
106 AsRefStr,
107 EnumIter,
108 EnumString,
109 Serialize,
110 Deserialize,
111)]
112#[cfg_attr(
113 feature = "python",
114 pyo3::pyclass(
115 eq,
116 eq_int,
117 module = "nautilus_trader.core.nautilus_pyo3.deribit",
118 from_py_object
119 )
120)]
121#[cfg_attr(
122 feature = "python",
123 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.deribit")
124)]
125pub enum DeribitWsChannel {
126 Trades,
129 Book,
131 Ticker,
133 Quote,
135 PriceIndex,
137 PriceRanking,
139 VolatilityIndex,
141 EstimatedExpirationPrice,
143 Perpetual,
145 MarkPriceOptions,
147 PlatformState,
149 Announcements,
151 ChartTrades,
153 InstrumentState,
156
157 UserOrders,
160 UserTrades,
162 UserPortfolio,
164 UserChanges,
166 UserAccessLog,
168}
169
170impl DeribitWsChannel {
171 #[must_use]
188 pub fn format_channel(
189 &self,
190 instrument_or_currency: &str,
191 interval: Option<DeribitUpdateInterval>,
192 ) -> String {
193 let interval_str = interval.unwrap_or_default().as_str();
194 match self {
195 Self::Trades => format!("trades.{instrument_or_currency}.{interval_str}"),
196 Self::Book => format!("book.{instrument_or_currency}.{interval_str}"),
197 Self::Ticker => format!("ticker.{instrument_or_currency}.{interval_str}"),
198 Self::Quote => format!("quote.{instrument_or_currency}"),
199 Self::PriceIndex => format!("deribit_price_index.{instrument_or_currency}"),
200 Self::PriceRanking => format!("deribit_price_ranking.{instrument_or_currency}"),
201 Self::VolatilityIndex => format!("deribit_volatility_index.{instrument_or_currency}"),
202 Self::EstimatedExpirationPrice => {
203 format!("estimated_expiration_price.{instrument_or_currency}")
204 }
205 Self::Perpetual => format!("perpetual.{instrument_or_currency}.{interval_str}"),
206 Self::MarkPriceOptions => format!("markprice.options.{instrument_or_currency}"),
207 Self::PlatformState => "platform_state".to_string(),
208 Self::Announcements => "announcements".to_string(),
209 Self::ChartTrades => format!("chart.trades.{instrument_or_currency}.{interval_str}"),
210 Self::UserOrders => format!("user.orders.{instrument_or_currency}.{interval_str}"),
211 Self::UserTrades => format!("user.trades.{instrument_or_currency}.{interval_str}"),
212 Self::UserPortfolio => format!("user.portfolio.{instrument_or_currency}"),
213 Self::UserChanges => format!("user.changes.{instrument_or_currency}.{interval_str}"),
214 Self::UserAccessLog => "user.access_log".to_string(),
215 Self::InstrumentState => {
216 panic!(
218 "InstrumentState channel requires kind and currency parameters, use format_instrument_state_channel() instead"
219 )
220 }
221 }
222 }
223
224 #[must_use]
233 pub fn format_instrument_state_channel(kind: &str, currency: &str) -> String {
234 format!("instrument.state.{kind}.{currency}")
235 }
236
237 #[must_use]
241 pub fn from_channel_string(channel: &str) -> Option<Self> {
242 if channel.starts_with("trades.") {
243 Some(Self::Trades)
244 } else if channel.starts_with("book.") {
245 Some(Self::Book)
246 } else if channel.starts_with("ticker.") {
247 Some(Self::Ticker)
248 } else if channel.starts_with("quote.") {
249 Some(Self::Quote)
250 } else if channel.starts_with("deribit_price_index.") {
251 Some(Self::PriceIndex)
252 } else if channel.starts_with("deribit_price_ranking.") {
253 Some(Self::PriceRanking)
254 } else if channel.starts_with("deribit_volatility_index.") {
255 Some(Self::VolatilityIndex)
256 } else if channel.starts_with("estimated_expiration_price.") {
257 Some(Self::EstimatedExpirationPrice)
258 } else if channel.starts_with("perpetual.") {
259 Some(Self::Perpetual)
260 } else if channel.starts_with("markprice.options.") {
261 Some(Self::MarkPriceOptions)
262 } else if channel == "platform_state" {
263 Some(Self::PlatformState)
264 } else if channel == "announcements" {
265 Some(Self::Announcements)
266 } else if channel.starts_with("chart.trades.") {
267 Some(Self::ChartTrades)
268 } else if channel.starts_with("user.orders.") {
269 Some(Self::UserOrders)
270 } else if channel.starts_with("user.trades.") {
271 Some(Self::UserTrades)
272 } else if channel.starts_with("user.portfolio.") {
273 Some(Self::UserPortfolio)
274 } else if channel.starts_with("user.changes.") {
275 Some(Self::UserChanges)
276 } else if channel == "user.access_log" {
277 Some(Self::UserAccessLog)
278 } else if channel.starts_with("instrument.state.") {
279 Some(Self::InstrumentState)
280 } else {
281 None
282 }
283 }
284
285 #[must_use]
287 pub const fn is_private(&self) -> bool {
288 matches!(
289 self,
290 Self::UserOrders
291 | Self::UserTrades
292 | Self::UserPortfolio
293 | Self::UserChanges
294 | Self::UserAccessLog
295 )
296 }
297
298 #[must_use]
304 pub fn requires_auth(channel: &str) -> bool {
305 match Self::from_channel_string(channel) {
306 Some(ch) if ch.is_private() => true,
307 Some(_) => channel.ends_with(".raw"),
308 None => false,
309 }
310 }
311}
312
313#[derive(
315 Clone,
316 Debug,
317 Display,
318 PartialEq,
319 Eq,
320 Hash,
321 AsRefStr,
322 EnumIter,
323 EnumString,
324 Serialize,
325 Deserialize,
326)]
327pub enum DeribitWsMethod {
328 #[serde(rename = "public/subscribe")]
331 #[strum(serialize = "public/subscribe")]
332 PublicSubscribe,
333 #[serde(rename = "public/unsubscribe")]
335 #[strum(serialize = "public/unsubscribe")]
336 PublicUnsubscribe,
337 #[serde(rename = "public/auth")]
339 #[strum(serialize = "public/auth")]
340 PublicAuth,
341 #[serde(rename = "public/set_heartbeat")]
343 #[strum(serialize = "public/set_heartbeat")]
344 SetHeartbeat,
345 #[serde(rename = "public/disable_heartbeat")]
347 #[strum(serialize = "public/disable_heartbeat")]
348 DisableHeartbeat,
349 #[serde(rename = "public/test")]
351 #[strum(serialize = "public/test")]
352 Test,
353 #[serde(rename = "public/hello")]
355 #[strum(serialize = "public/hello")]
356 Hello,
357 #[serde(rename = "public/get_time")]
359 #[strum(serialize = "public/get_time")]
360 GetTime,
361
362 #[serde(rename = "private/subscribe")]
364 #[strum(serialize = "private/subscribe")]
365 PrivateSubscribe,
366 #[serde(rename = "private/unsubscribe")]
368 #[strum(serialize = "private/unsubscribe")]
369 PrivateUnsubscribe,
370 #[serde(rename = "private/logout")]
372 #[strum(serialize = "private/logout")]
373 Logout,
374 #[serde(rename = "private/buy")]
376 #[strum(serialize = "private/buy")]
377 Buy,
378 #[serde(rename = "private/sell")]
380 #[strum(serialize = "private/sell")]
381 Sell,
382 #[serde(rename = "private/edit")]
384 #[strum(serialize = "private/edit")]
385 Edit,
386 #[serde(rename = "private/cancel")]
388 #[strum(serialize = "private/cancel")]
389 Cancel,
390 #[serde(rename = "private/cancel_all_by_instrument")]
392 #[strum(serialize = "private/cancel_all_by_instrument")]
393 CancelAllByInstrument,
394 #[serde(rename = "private/get_order_state")]
396 #[strum(serialize = "private/get_order_state")]
397 GetOrderState,
398}
399
400impl DeribitWsMethod {
401 #[must_use]
403 pub fn as_method_str(&self) -> &str {
404 self.as_ref()
405 }
406}
407
408#[derive(
410 Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
411)]
412#[serde(rename_all = "snake_case")]
413#[strum(serialize_all = "snake_case")]
414pub enum DeribitBookAction {
415 #[serde(rename = "new")]
417 New,
418 #[serde(rename = "change")]
420 Change,
421 #[serde(rename = "delete")]
423 Delete,
424}
425
426impl From<DeribitBookAction> for BookAction {
427 fn from(action: DeribitBookAction) -> Self {
428 match action {
429 DeribitBookAction::New => Self::Add,
430 DeribitBookAction::Change => Self::Update,
431 DeribitBookAction::Delete => Self::Delete,
432 }
433 }
434}
435
436#[derive(
438 Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
439)]
440#[serde(rename_all = "snake_case")]
441pub enum DeribitBookMsgType {
442 #[serde(rename = "snapshot")]
444 Snapshot,
445 #[serde(rename = "change")]
447 Change,
448}
449
450#[cfg(test)]
451mod tests {
452 use rstest::rstest;
453
454 use super::*;
455
456 #[rstest]
457 fn test_requires_auth_user_channels() {
458 assert!(DeribitWsChannel::requires_auth("user.orders.any.any.raw"));
459 assert!(DeribitWsChannel::requires_auth("user.trades.any.any.raw"));
460 assert!(DeribitWsChannel::requires_auth("user.portfolio.any"));
461 assert!(DeribitWsChannel::requires_auth("user.changes.any.any.raw"));
462 assert!(DeribitWsChannel::requires_auth("user.access_log"));
463 }
464
465 #[rstest]
466 fn test_requires_auth_raw_channels() {
467 assert!(DeribitWsChannel::requires_auth("book.BTC-PERPETUAL.raw"));
468 assert!(DeribitWsChannel::requires_auth("book.ETH-25DEC25.raw"));
469 assert!(DeribitWsChannel::requires_auth("trades.BTC-PERPETUAL.raw"));
470 assert!(DeribitWsChannel::requires_auth("ticker.BTC-PERPETUAL.raw"));
471 }
472
473 #[rstest]
474 fn test_requires_auth_public_channels() {
475 assert!(!DeribitWsChannel::requires_auth(
476 "book.BTC-PERPETUAL.none.10.100ms"
477 ));
478 assert!(!DeribitWsChannel::requires_auth(
479 "book.BTC-PERPETUAL.none.20.agg2"
480 ));
481 assert!(!DeribitWsChannel::requires_auth(
482 "trades.BTC-PERPETUAL.100ms"
483 ));
484 assert!(!DeribitWsChannel::requires_auth(
485 "ticker.BTC-PERPETUAL.100ms"
486 ));
487 assert!(!DeribitWsChannel::requires_auth("quote.BTC-PERPETUAL"));
488 assert!(!DeribitWsChannel::requires_auth("deribit_price_index.btc"));
489 assert!(!DeribitWsChannel::requires_auth("platform_state"));
490 assert!(!DeribitWsChannel::requires_auth("announcements"));
491 }
492}
493
494#[derive(
496 Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
497)]
498#[serde(rename_all = "snake_case")]
499pub enum DeribitHeartbeatType {
500 #[serde(rename = "heartbeat")]
502 Heartbeat,
503 #[serde(rename = "test_request")]
505 TestRequest,
506}