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