Skip to main content

nautilus_deribit/websocket/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Enumerations for Deribit WebSocket channels and operations.
17
18use std::fmt::Display;
19
20use nautilus_model::enums::BookAction;
21use serde::{Deserialize, Serialize};
22use strum::{AsRefStr, Display, EnumIter, EnumString};
23
24/// Deribit data stream update intervals.
25///
26/// Controls how frequently updates are sent for subscribed channels.
27/// Raw updates require authentication while aggregated updates are public.
28#[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)]
53pub enum DeribitUpdateInterval {
54    /// Raw updates - immediate delivery of each event.
55    /// Requires authentication.
56    #[strum(serialize = "raw", serialize = "Raw")]
57    Raw,
58    /// Aggregated updates every 100 milliseconds (default).
59    #[default]
60    #[strum(serialize = "100ms", serialize = "Ms100")]
61    Ms100,
62    /// Aggregated updates every 2 ticks.
63    #[strum(serialize = "agg2", serialize = "Agg2")]
64    Agg2,
65}
66
67impl DeribitUpdateInterval {
68    /// Returns the string representation for Deribit channel subscription.
69    #[must_use]
70    pub const fn as_str(&self) -> &'static str {
71        match self {
72            Self::Raw => "raw",
73            Self::Ms100 => "100ms",
74            Self::Agg2 => "agg2",
75        }
76    }
77
78    /// Returns whether this interval requires authentication.
79    #[must_use]
80    pub const fn requires_auth(&self) -> bool {
81        matches!(self, Self::Raw)
82    }
83}
84
85impl Display for DeribitUpdateInterval {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        write!(f, "{}", self.as_str())
88    }
89}
90
91/// Deribit WebSocket public data channels.
92///
93/// Channels follow the format: `{channel_type}.{instrument_or_currency}.{interval}`
94#[derive(
95    Clone,
96    Copy,
97    Debug,
98    Display,
99    PartialEq,
100    Eq,
101    Hash,
102    AsRefStr,
103    EnumIter,
104    EnumString,
105    Serialize,
106    Deserialize,
107)]
108#[cfg_attr(
109    feature = "python",
110    pyo3::pyclass(
111        eq,
112        eq_int,
113        module = "nautilus_trader.core.nautilus_pyo3.deribit",
114        from_py_object
115    )
116)]
117pub enum DeribitWsChannel {
118    // Public Market Data Channels
119    /// Raw trade stream: `trades.{instrument}.raw`
120    Trades,
121    /// Order book updates: `book.{instrument}.{group}.{depth}.{interval}`
122    Book,
123    /// Ticker updates: `ticker.{instrument}.{interval}`
124    Ticker,
125    /// Quote updates (best bid/ask): `quote.{instrument}`
126    Quote,
127    /// Index price: `deribit_price_index.{currency}`
128    PriceIndex,
129    /// Price ranking: `deribit_price_ranking.{currency}`
130    PriceRanking,
131    /// Volatility index: `deribit_volatility_index.{currency}`
132    VolatilityIndex,
133    /// Estimated expiration price: `estimated_expiration_price.{currency}`
134    EstimatedExpirationPrice,
135    /// Perpetual interest rate: `perpetual.{instrument}.{interval}`
136    Perpetual,
137    /// Mark price options: `markprice.options.{currency}`
138    MarkPriceOptions,
139    /// Platform state: `platform_state`
140    PlatformState,
141    /// Announcements: `announcements`
142    Announcements,
143    /// Chart trades: `chart.trades.{instrument}.{resolution}`
144    ChartTrades,
145    /// Instrument state changes: `instrument.state.{kind}.{currency}`
146    /// Used for instrument lifecycle notifications (created, started, settled, closed, terminated)
147    InstrumentState,
148
149    // Private User Channels (for future execution support)
150    /// User orders: `user.orders.{instrument}.{interval}`
151    UserOrders,
152    /// User trades/fills: `user.trades.{instrument}.{interval}`
153    UserTrades,
154    /// User portfolio: `user.portfolio.{currency}`
155    UserPortfolio,
156    /// User changes (combined orders/trades/positions): `user.changes.{instrument}.{interval}`
157    UserChanges,
158    /// User access log: `user.access_log`
159    UserAccessLog,
160}
161
162impl DeribitWsChannel {
163    /// Formats the channel name for subscription with the given instrument or currency.
164    ///
165    /// Returns the full channel string for Deribit subscription.
166    ///
167    /// # Arguments
168    ///
169    /// * `instrument_or_currency` - The instrument name (e.g., "BTC-PERPETUAL") or currency (e.g., "BTC")
170    /// * `interval` - Optional update interval. Defaults to `Ms100` (100ms) if not specified.
171    ///
172    /// # Panics
173    ///
174    /// Panics if called on `InstrumentState` variant. Use `format_instrument_state_channel()` instead.
175    ///
176    /// # Note
177    ///
178    /// `Raw` subscriptions require authentication. Use `Ms100` for public/unauthenticated access.
179    #[must_use]
180    pub fn format_channel(
181        &self,
182        instrument_or_currency: &str,
183        interval: Option<DeribitUpdateInterval>,
184    ) -> String {
185        let interval_str = interval.unwrap_or_default().as_str();
186        match self {
187            Self::Trades => format!("trades.{instrument_or_currency}.{interval_str}"),
188            Self::Book => format!("book.{instrument_or_currency}.{interval_str}"),
189            Self::Ticker => format!("ticker.{instrument_or_currency}.{interval_str}"),
190            Self::Quote => format!("quote.{instrument_or_currency}"),
191            Self::PriceIndex => format!("deribit_price_index.{instrument_or_currency}"),
192            Self::PriceRanking => format!("deribit_price_ranking.{instrument_or_currency}"),
193            Self::VolatilityIndex => format!("deribit_volatility_index.{instrument_or_currency}"),
194            Self::EstimatedExpirationPrice => {
195                format!("estimated_expiration_price.{instrument_or_currency}")
196            }
197            Self::Perpetual => format!("perpetual.{instrument_or_currency}.{interval_str}"),
198            Self::MarkPriceOptions => format!("markprice.options.{instrument_or_currency}"),
199            Self::PlatformState => "platform_state".to_string(),
200            Self::Announcements => "announcements".to_string(),
201            Self::ChartTrades => format!("chart.trades.{instrument_or_currency}.{interval_str}"),
202            Self::UserOrders => format!("user.orders.{instrument_or_currency}.{interval_str}"),
203            Self::UserTrades => format!("user.trades.{instrument_or_currency}.{interval_str}"),
204            Self::UserPortfolio => format!("user.portfolio.{instrument_or_currency}"),
205            Self::UserChanges => format!("user.changes.{instrument_or_currency}.{interval_str}"),
206            Self::UserAccessLog => "user.access_log".to_string(),
207            Self::InstrumentState => {
208                // InstrumentState requires kind and currency, use format_instrument_state_channel() instead
209                panic!(
210                    "InstrumentState channel requires kind and currency parameters, use format_instrument_state_channel() instead"
211                )
212            }
213        }
214    }
215
216    /// Formats the instrument state channel for subscription.
217    ///
218    /// Returns the full channel string: `instrument.state.{kind}.{currency}`
219    ///
220    /// # Arguments
221    ///
222    /// * `kind` - Instrument kind: "future", "option", "spot", "future_combo", "option_combo", or "any"
223    /// * `currency` - Currency: "BTC", "ETH", "USDC", "USDT", "EURR", or "any"
224    #[must_use]
225    pub fn format_instrument_state_channel(kind: &str, currency: &str) -> String {
226        format!("instrument.state.{kind}.{currency}")
227    }
228
229    /// Parses a channel string to extract the channel type.
230    ///
231    /// Returns the channel enum variant if recognized.
232    #[must_use]
233    pub fn from_channel_string(channel: &str) -> Option<Self> {
234        if channel.starts_with("trades.") {
235            Some(Self::Trades)
236        } else if channel.starts_with("book.") {
237            Some(Self::Book)
238        } else if channel.starts_with("ticker.") {
239            Some(Self::Ticker)
240        } else if channel.starts_with("quote.") {
241            Some(Self::Quote)
242        } else if channel.starts_with("deribit_price_index.") {
243            Some(Self::PriceIndex)
244        } else if channel.starts_with("deribit_price_ranking.") {
245            Some(Self::PriceRanking)
246        } else if channel.starts_with("deribit_volatility_index.") {
247            Some(Self::VolatilityIndex)
248        } else if channel.starts_with("estimated_expiration_price.") {
249            Some(Self::EstimatedExpirationPrice)
250        } else if channel.starts_with("perpetual.") {
251            Some(Self::Perpetual)
252        } else if channel.starts_with("markprice.options.") {
253            Some(Self::MarkPriceOptions)
254        } else if channel == "platform_state" {
255            Some(Self::PlatformState)
256        } else if channel == "announcements" {
257            Some(Self::Announcements)
258        } else if channel.starts_with("chart.trades.") {
259            Some(Self::ChartTrades)
260        } else if channel.starts_with("user.orders.") {
261            Some(Self::UserOrders)
262        } else if channel.starts_with("user.trades.") {
263            Some(Self::UserTrades)
264        } else if channel.starts_with("user.portfolio.") {
265            Some(Self::UserPortfolio)
266        } else if channel.starts_with("user.changes.") {
267            Some(Self::UserChanges)
268        } else if channel == "user.access_log" {
269            Some(Self::UserAccessLog)
270        } else if channel.starts_with("instrument.state.") {
271            Some(Self::InstrumentState)
272        } else {
273            None
274        }
275    }
276
277    /// Returns whether this is a private (authenticated) channel.
278    #[must_use]
279    pub const fn is_private(&self) -> bool {
280        matches!(
281            self,
282            Self::UserOrders
283                | Self::UserTrades
284                | Self::UserPortfolio
285                | Self::UserChanges
286                | Self::UserAccessLog
287        )
288    }
289}
290
291/// Deribit JSON-RPC WebSocket methods.
292#[derive(
293    Clone,
294    Debug,
295    Display,
296    PartialEq,
297    Eq,
298    Hash,
299    AsRefStr,
300    EnumIter,
301    EnumString,
302    Serialize,
303    Deserialize,
304)]
305pub enum DeribitWsMethod {
306    // Public methods
307    /// Subscribe to public channels.
308    #[serde(rename = "public/subscribe")]
309    #[strum(serialize = "public/subscribe")]
310    PublicSubscribe,
311    /// Unsubscribe from public channels.
312    #[serde(rename = "public/unsubscribe")]
313    #[strum(serialize = "public/unsubscribe")]
314    PublicUnsubscribe,
315    /// Authenticate with API credentials.
316    #[serde(rename = "public/auth")]
317    #[strum(serialize = "public/auth")]
318    PublicAuth,
319    /// Enable heartbeat mechanism.
320    #[serde(rename = "public/set_heartbeat")]
321    #[strum(serialize = "public/set_heartbeat")]
322    SetHeartbeat,
323    /// Disable heartbeat mechanism.
324    #[serde(rename = "public/disable_heartbeat")]
325    #[strum(serialize = "public/disable_heartbeat")]
326    DisableHeartbeat,
327    /// Test connectivity (used for heartbeat response).
328    #[serde(rename = "public/test")]
329    #[strum(serialize = "public/test")]
330    Test,
331    /// Hello/handshake message.
332    #[serde(rename = "public/hello")]
333    #[strum(serialize = "public/hello")]
334    Hello,
335    /// Get server time.
336    #[serde(rename = "public/get_time")]
337    #[strum(serialize = "public/get_time")]
338    GetTime,
339
340    // Private methods (for future execution support)
341    /// Subscribe to private channels.
342    #[serde(rename = "private/subscribe")]
343    #[strum(serialize = "private/subscribe")]
344    PrivateSubscribe,
345    /// Unsubscribe from private channels.
346    #[serde(rename = "private/unsubscribe")]
347    #[strum(serialize = "private/unsubscribe")]
348    PrivateUnsubscribe,
349    /// Logout and close session.
350    #[serde(rename = "private/logout")]
351    #[strum(serialize = "private/logout")]
352    Logout,
353}
354
355impl DeribitWsMethod {
356    /// Returns the JSON-RPC method string.
357    #[must_use]
358    pub fn as_method_str(&self) -> &'static str {
359        match self {
360            Self::PublicSubscribe => "public/subscribe",
361            Self::PublicUnsubscribe => "public/unsubscribe",
362            Self::PublicAuth => "public/auth",
363            Self::SetHeartbeat => "public/set_heartbeat",
364            Self::DisableHeartbeat => "public/disable_heartbeat",
365            Self::Test => "public/test",
366            Self::Hello => "public/hello",
367            Self::GetTime => "public/get_time",
368            Self::PrivateSubscribe => "private/subscribe",
369            Self::PrivateUnsubscribe => "private/unsubscribe",
370            Self::Logout => "private/logout",
371        }
372    }
373}
374
375/// Deribit order book update action types.
376#[derive(
377    Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
378)]
379#[serde(rename_all = "snake_case")]
380#[strum(serialize_all = "snake_case")]
381pub enum DeribitBookAction {
382    /// New price level added.
383    #[serde(rename = "new")]
384    New,
385    /// Existing price level changed.
386    #[serde(rename = "change")]
387    Change,
388    /// Price level removed.
389    #[serde(rename = "delete")]
390    Delete,
391}
392
393impl From<DeribitBookAction> for BookAction {
394    fn from(action: DeribitBookAction) -> Self {
395        match action {
396            DeribitBookAction::New => Self::Add,
397            DeribitBookAction::Change => Self::Update,
398            DeribitBookAction::Delete => Self::Delete,
399        }
400    }
401}
402
403/// Deribit order book message type.
404#[derive(
405    Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
406)]
407#[serde(rename_all = "snake_case")]
408pub enum DeribitBookMsgType {
409    /// Full order book snapshot.
410    #[serde(rename = "snapshot")]
411    Snapshot,
412    /// Incremental update.
413    #[serde(rename = "change")]
414    Change,
415}
416
417/// Deribit heartbeat types.
418#[derive(
419    Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumString, Serialize, Deserialize,
420)]
421#[serde(rename_all = "snake_case")]
422pub enum DeribitHeartbeatType {
423    /// Server heartbeat notification.
424    #[serde(rename = "heartbeat")]
425    Heartbeat,
426    /// Server requesting client response.
427    #[serde(rename = "test_request")]
428    TestRequest,
429}