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