nautilus_deribit/websocket/
enums.rs

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