Skip to main content

nautilus_deribit/http/
models.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//! Deribit HTTP API models and types.
17
18use ahash::AHashMap;
19use nautilus_core::serialization::{deserialize_decimal, deserialize_optional_decimal};
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use ustr::Ustr;
23
24pub use crate::common::{
25    enums::{DeribitCurrency, DeribitOptionType, DeribitProductType},
26    models::DeribitTradeLeg,
27    rpc::{DeribitJsonRpcError, DeribitJsonRpcRequest, DeribitJsonRpcResponse},
28};
29
30/// JSON-RPC 2.0 response payload (either success or error).
31#[derive(Debug, Serialize, Deserialize)]
32#[serde(untagged)]
33pub enum DeribitResponsePayload<T> {
34    /// Successful response with result data
35    Success { result: T },
36    /// Error response with error details
37    Error { error: DeribitJsonRpcError },
38}
39
40/// Deribit instrument definition.
41#[derive(Clone, Debug, Serialize, Deserialize)]
42pub struct DeribitInstrument {
43    /// The underlying currency being traded
44    pub base_currency: Ustr,
45    /// Block trade commission for instrument
46    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
47    pub block_trade_commission: Option<Decimal>,
48    /// Minimum amount for block trading
49    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
50    pub block_trade_min_trade_amount: Option<Decimal>,
51    /// Specifies minimal price change for block trading
52    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
53    pub block_trade_tick_size: Option<Decimal>,
54    /// Contract size for instrument
55    #[serde(deserialize_with = "deserialize_decimal")]
56    pub contract_size: Decimal,
57    /// Counter currency for the instrument
58    pub counter_currency: Option<Ustr>,
59    /// The time when the instrument was first created (milliseconds since UNIX epoch)
60    pub creation_timestamp: i64,
61    /// The time when the instrument will expire (milliseconds since UNIX epoch)
62    pub expiration_timestamp: Option<i64>,
63    /// Future type (deprecated, use instrument_type instead)
64    pub future_type: Option<String>,
65    /// Instrument ID
66    pub instrument_id: i64,
67    /// Unique instrument identifier (e.g., "BTC-PERPETUAL")
68    pub instrument_name: Ustr,
69    /// Type of the instrument: "linear" or "reversed"
70    pub instrument_type: Option<String>,
71    /// Indicates if the instrument can currently be traded
72    pub is_active: bool,
73    /// Product type: "future", "option", "spot", "future_combo", "option_combo"
74    pub kind: DeribitProductType,
75    /// Maker commission for instrument
76    #[serde(deserialize_with = "deserialize_decimal")]
77    pub maker_commission: Decimal,
78    /// Maximal leverage for instrument (only for futures)
79    pub max_leverage: Option<i64>,
80    /// Maximal liquidation trade commission for instrument (only for futures)
81    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
82    pub max_liquidation_commission: Option<Decimal>,
83    /// Minimum amount for trading
84    #[serde(deserialize_with = "deserialize_decimal")]
85    pub min_trade_amount: Decimal,
86    /// The option type (only for options)
87    pub option_type: Option<DeribitOptionType>,
88    /// Name of price index that is used for this instrument
89    pub price_index: Option<String>,
90    /// The currency in which the instrument prices are quoted
91    pub quote_currency: Ustr,
92    /// Settlement currency for the instrument (not present for spot)
93    pub settlement_currency: Option<Ustr>,
94    /// The settlement period (not present for spot)
95    pub settlement_period: Option<String>,
96    /// The strike value (only for options)
97    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
98    pub strike: Option<Decimal>,
99    /// Taker commission for instrument
100    #[serde(deserialize_with = "deserialize_decimal")]
101    pub taker_commission: Decimal,
102    /// Specifies minimal price change and number of decimal places for instrument prices
103    #[serde(deserialize_with = "deserialize_decimal")]
104    pub tick_size: Decimal,
105    /// Tick size steps for different price ranges
106    pub tick_size_steps: Option<Vec<DeribitTickSizeStep>>,
107}
108
109/// Tick size step definition for price-dependent tick sizes.
110#[derive(Clone, Debug, Serialize, Deserialize)]
111pub struct DeribitTickSizeStep {
112    /// The price from which the increased tick size applies
113    #[serde(deserialize_with = "deserialize_decimal")]
114    pub above_price: Decimal,
115    /// Tick size to be used above the price
116    #[serde(deserialize_with = "deserialize_decimal")]
117    pub tick_size: Decimal,
118}
119
120/// Deribit combo definition from `public/get_combos`.
121#[derive(Clone, Debug, Serialize, Deserialize)]
122pub struct DeribitCombo {
123    /// Unique combo identifier, matching the combo instrument name.
124    pub id: Ustr,
125    /// Combo state, e.g. `active`.
126    pub state: String,
127    /// Combo leg makeup.
128    pub legs: Vec<DeribitComboLeg>,
129    /// Combo creation timestamp in milliseconds since UNIX epoch.
130    pub creation_timestamp: i64,
131    /// Numeric Deribit instrument ID.
132    pub instrument_id: i64,
133    /// State timestamp in milliseconds since UNIX epoch.
134    pub state_timestamp: i64,
135}
136
137/// A single leg entry of a Deribit combo definition.
138#[derive(Clone, Debug, Serialize, Deserialize)]
139pub struct DeribitComboLeg {
140    /// Signed leg amount. Positive is long, negative is short.
141    #[serde(deserialize_with = "deserialize_decimal")]
142    pub amount: Decimal,
143    /// Leg instrument name.
144    pub instrument_name: Ustr,
145}
146
147/// Wrapper for the account summaries response.
148///
149/// The API returns an object with a `summaries` field containing the array of account summaries,
150/// plus account-level metadata.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct DeribitAccountSummariesResponse {
153    /// Array of per-currency account summaries
154    pub summaries: Vec<DeribitAccountSummary>,
155    /// Account ID
156    #[serde(default)]
157    pub id: Option<i64>,
158    /// Account email
159    #[serde(default)]
160    pub email: Option<String>,
161    /// System name
162    #[serde(default)]
163    pub system_name: Option<String>,
164    /// Account username
165    #[serde(default)]
166    pub username: Option<String>,
167    /// Account type (e.g., "main", "subaccount")
168    #[serde(rename = "type", default)]
169    pub account_type: Option<String>,
170    /// Account creation timestamp (milliseconds since UNIX epoch)
171    #[serde(default)]
172    pub creation_timestamp: Option<i64>,
173    /// Referrer ID (affiliation program)
174    #[serde(default)]
175    pub referrer_id: Option<String>,
176    /// Whether login is enabled for this account
177    #[serde(default)]
178    pub login_enabled: Option<bool>,
179    /// Whether security keys are enabled
180    #[serde(default)]
181    pub security_keys_enabled: Option<bool>,
182    /// Whether MMP (Market Maker Protection) is enabled
183    #[serde(default)]
184    pub mmp_enabled: Option<bool>,
185    /// Whether inter-user transfers are enabled
186    #[serde(default)]
187    pub interuser_transfers_enabled: Option<bool>,
188    /// Self-trading reject mode
189    #[serde(default)]
190    pub self_trading_reject_mode: Option<String>,
191    /// Whether self-trading is extended to subaccounts
192    #[serde(default)]
193    pub self_trading_extended_to_subaccounts: Option<bool>,
194    /// Block RFQ self match prevention
195    #[serde(default)]
196    pub block_rfq_self_match_prevention: Option<bool>,
197}
198
199/// Account summary for a single currency.
200///
201/// Contains balance, equity, margin information, and profit/loss data.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct DeribitAccountSummary {
204    /// Currency code (e.g., "BTC", "ETH")
205    pub currency: Ustr,
206    /// Account equity (balance + unrealized PnL)
207    #[serde(deserialize_with = "deserialize_decimal")]
208    pub equity: Decimal,
209    /// Account balance
210    #[serde(deserialize_with = "deserialize_decimal")]
211    pub balance: Decimal,
212    /// Available funds for trading
213    #[serde(deserialize_with = "deserialize_decimal")]
214    pub available_funds: Decimal,
215    /// Margin balance (for derivatives)
216    #[serde(deserialize_with = "deserialize_decimal")]
217    pub margin_balance: Decimal,
218    /// Initial margin (required for current positions)
219    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
220    pub initial_margin: Option<Decimal>,
221    /// Maintenance margin
222    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
223    pub maintenance_margin: Option<Decimal>,
224    /// Total profit/loss
225    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
226    pub total_pl: Option<Decimal>,
227    /// Session unrealized profit/loss
228    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
229    pub session_upl: Option<Decimal>,
230    /// Session realized profit/loss
231    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
232    pub session_rpl: Option<Decimal>,
233    /// Portfolio margining enabled
234    #[serde(default)]
235    pub portfolio_margining_enabled: Option<bool>,
236    /// Margin model (e.g., "segregated_sm", "cross_sm", "cross_pm")
237    #[serde(default)]
238    pub margin_model: Option<String>,
239    /// Whether cross-collateral is enabled for this currency
240    #[serde(default)]
241    pub cross_collateral_enabled: Option<bool>,
242    /// Available withdrawal funds (per-currency withdrawable amount)
243    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
244    pub available_withdrawal_funds: Option<Decimal>,
245}
246
247/// Extended account summary with additional account details.
248///
249/// Returned by `private/get_account_summary` with `extended=true`.
250/// Contains all fields from [`DeribitAccountSummary`] plus account metadata,
251/// position Greeks, detailed margins, and fee structures.
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct DeribitAccountSummaryExtended {
254    /// Currency code (e.g., "BTC", "ETH")
255    pub currency: Ustr,
256    /// Account equity (balance + unrealized PnL)
257    #[serde(deserialize_with = "deserialize_decimal")]
258    pub equity: Decimal,
259    /// Account balance
260    #[serde(deserialize_with = "deserialize_decimal")]
261    pub balance: Decimal,
262    /// Available funds for trading
263    #[serde(deserialize_with = "deserialize_decimal")]
264    pub available_funds: Decimal,
265    /// Margin balance (for derivatives)
266    #[serde(deserialize_with = "deserialize_decimal")]
267    pub margin_balance: Decimal,
268    /// Initial margin (required for current positions)
269    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
270    pub initial_margin: Option<Decimal>,
271    /// Maintenance margin
272    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
273    pub maintenance_margin: Option<Decimal>,
274    /// Total profit/loss
275    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
276    pub total_pl: Option<Decimal>,
277    /// Session unrealized profit/loss
278    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
279    pub session_upl: Option<Decimal>,
280    /// Session realized profit/loss
281    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
282    pub session_rpl: Option<Decimal>,
283    /// Portfolio margining enabled
284    #[serde(default)]
285    pub portfolio_margining_enabled: Option<bool>,
286    // Extended fields below
287    /// Account ID
288    #[serde(default)]
289    pub id: Option<i64>,
290    /// Account email
291    #[serde(default)]
292    pub email: Option<String>,
293    /// Account username
294    #[serde(default)]
295    pub username: Option<String>,
296    /// System name
297    #[serde(default)]
298    pub system_name: Option<String>,
299    /// Account type (e.g., "main", "subaccount")
300    #[serde(rename = "type", default)]
301    pub account_type: Option<String>,
302    /// Futures session unrealized P&L
303    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
304    pub futures_session_upl: Option<Decimal>,
305    /// Futures session realized P&L
306    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
307    pub futures_session_rpl: Option<Decimal>,
308    /// Options session unrealized P&L
309    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
310    pub options_session_upl: Option<Decimal>,
311    /// Options session realized P&L
312    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
313    pub options_session_rpl: Option<Decimal>,
314    /// Futures profit/loss
315    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
316    pub futures_pl: Option<Decimal>,
317    /// Options profit/loss
318    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
319    pub options_pl: Option<Decimal>,
320    /// Options delta
321    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
322    pub options_delta: Option<Decimal>,
323    /// Options gamma
324    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
325    pub options_gamma: Option<Decimal>,
326    /// Options vega
327    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
328    pub options_vega: Option<Decimal>,
329    /// Options theta
330    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
331    pub options_theta: Option<Decimal>,
332    /// Options value
333    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
334    pub options_value: Option<Decimal>,
335    /// Total delta across all positions
336    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
337    pub delta_total: Option<Decimal>,
338    /// Projected delta total
339    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
340    pub projected_delta_total: Option<Decimal>,
341    /// Projected initial margin
342    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
343    pub projected_initial_margin: Option<Decimal>,
344    /// Projected maintenance margin
345    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
346    pub projected_maintenance_margin: Option<Decimal>,
347    /// Estimated liquidation ratio
348    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
349    pub estimated_liquidation_ratio: Option<Decimal>,
350    /// Available withdrawal funds
351    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
352    pub available_withdrawal_funds: Option<Decimal>,
353    /// Spot reserve
354    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
355    pub spot_reserve: Option<Decimal>,
356    /// Fee balance
357    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
358    pub fee_balance: Option<Decimal>,
359    /// Margin model (e.g., "segregated_sm", "cross_pm")
360    #[serde(default)]
361    pub margin_model: Option<String>,
362    /// Cross collateral enabled
363    #[serde(default)]
364    pub cross_collateral_enabled: Option<bool>,
365    /// Account creation timestamp (milliseconds since UNIX epoch)
366    #[serde(default)]
367    pub creation_timestamp: Option<i64>,
368    /// Whether login is enabled for this account
369    #[serde(default)]
370    pub login_enabled: Option<bool>,
371    /// Whether security keys are enabled
372    #[serde(default)]
373    pub security_keys_enabled: Option<bool>,
374    /// Whether MMP (Market Maker Protection) is enabled
375    #[serde(default)]
376    pub mmp_enabled: Option<bool>,
377    /// Whether inter-user transfers are enabled
378    #[serde(default)]
379    pub interuser_transfers_enabled: Option<bool>,
380    /// Self-trading reject mode
381    #[serde(default)]
382    pub self_trading_reject_mode: Option<String>,
383    /// Whether self-trading is extended to subaccounts
384    #[serde(default)]
385    pub self_trading_extended_to_subaccounts: Option<bool>,
386    /// Referrer ID (affiliation program)
387    #[serde(default)]
388    pub referrer_id: Option<String>,
389    /// Block RFQ self match prevention
390    #[serde(default)]
391    pub block_rfq_self_match_prevention: Option<bool>,
392}
393
394/// Deribit public trade data from the market data API.
395///
396/// Represents a single trade returned by `/public/get_last_trades_by_instrument_and_time`
397/// and other trade-related endpoints.
398#[derive(Clone, Debug, Serialize, Deserialize)]
399pub struct DeribitPublicTrade {
400    /// Trade amount. For perpetual and inverse futures the amount is in USD units.
401    /// For options and linear futures it is the underlying base currency coin.
402    #[serde(deserialize_with = "deserialize_decimal")]
403    pub amount: Decimal,
404    /// Trade size in contract units (optional, may be absent in historical trades).
405    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
406    pub contracts: Option<Decimal>,
407    /// Direction of the trade: "buy" or "sell"
408    pub direction: String,
409    /// Index Price at the moment of trade (can be empty for some trade types).
410    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
411    pub index_price: Option<Decimal>,
412    /// Unique instrument identifier.
413    pub instrument_name: String,
414    /// Option implied volatility for the price (Option only).
415    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
416    pub iv: Option<Decimal>,
417    /// Optional field (only for trades caused by liquidation).
418    #[serde(default)]
419    pub liquidation: Option<String>,
420    /// Mark Price at the moment of trade (can be empty for some trade types).
421    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
422    pub mark_price: Option<Decimal>,
423    /// Price in base currency.
424    #[serde(deserialize_with = "deserialize_decimal")]
425    pub price: Decimal,
426    /// Direction of the "tick" (0 = Plus Tick, 1 = Zero-Plus Tick, 2 = Minus Tick, 3 = Zero-Minus Tick).
427    pub tick_direction: i32,
428    /// The timestamp of the trade (milliseconds since the UNIX epoch).
429    pub timestamp: i64,
430    /// Unique (per currency) trade identifier.
431    pub trade_id: String,
432    /// The sequence number of the trade within instrument.
433    pub trade_seq: i64,
434    /// Block trade id - when trade was part of a block trade.
435    #[serde(default)]
436    pub block_trade_id: Option<String>,
437    /// Block trade leg count - when trade was part of a block trade.
438    #[serde(default)]
439    pub block_trade_leg_count: Option<i32>,
440    /// ID of the Block RFQ - when trade was part of the Block RFQ.
441    #[serde(default)]
442    pub block_rfq_id: Option<i64>,
443    /// Optional field containing combo instrument name if the trade is a combo trade.
444    #[serde(default)]
445    pub combo_id: Option<String>,
446    /// Optional field containing combo trade identifier if the trade is a combo trade.
447    #[serde(default)]
448    pub combo_trade_id: Option<String>,
449    /// Per-leg trades when this is the parent combo trade.
450    #[serde(default)]
451    pub legs: Option<Vec<DeribitTradeLeg>>,
452}
453
454/// Response wrapper for trades endpoints.
455///
456/// Contains the trades array and pagination information.
457#[derive(Clone, Debug, Serialize, Deserialize)]
458pub struct DeribitTradesResponse {
459    /// Whether there are more trades available.
460    pub has_more: bool,
461    /// Array of trade objects.
462    pub trades: Vec<DeribitPublicTrade>,
463}
464
465/// Expiration strings grouped by instrument kind.
466#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
467pub struct DeribitExpirationsByKind {
468    /// Future expirations, including `PERPETUAL` when present.
469    #[serde(default)]
470    pub future: Vec<String>,
471    /// Option expirations.
472    #[serde(default)]
473    pub option: Vec<String>,
474    /// Future combo expirations.
475    #[serde(default)]
476    pub future_combo: Vec<String>,
477    /// Option combo expirations.
478    #[serde(default)]
479    pub option_combo: Vec<String>,
480}
481
482/// Response from `public/get_expirations` endpoint.
483///
484/// Deribit returns a currency-keyed object for concrete or `grouped` currencies,
485/// and a direct kind-keyed object for `currency=any`.
486#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
487#[serde(untagged)]
488pub enum DeribitExpirationsResponse {
489    /// Expirations keyed by lowercase currency code.
490    ByCurrency(AHashMap<String, DeribitExpirationsByKind>),
491    /// Expirations returned without currency grouping.
492    Expirations(DeribitExpirationsByKind),
493}
494
495impl DeribitExpirationsResponse {
496    /// Returns expirations for a currency code, or the direct result for `currency=any`.
497    #[must_use]
498    pub fn expirations_for_currency(&self, currency: &str) -> Option<&DeribitExpirationsByKind> {
499        match self {
500            Self::ByCurrency(expirations) => {
501                let key = currency.to_ascii_lowercase();
502                expirations.get(&key)
503            }
504            Self::Expirations(expirations) => Some(expirations),
505        }
506    }
507}
508
509/// Response from `public/get_tradingview_chart_data` endpoint.
510///
511/// Contains OHLCV data in array format where each array element corresponds
512/// to a single candle at the index in the `ticks` array.
513#[derive(Clone, Debug, Serialize, Deserialize)]
514pub struct DeribitTradingViewChartData {
515    /// List of prices at close (one per candle)
516    pub close: Vec<f64>,
517    /// List of cost bars (volume in quote currency, one per candle)
518    pub cost: Vec<f64>,
519    /// List of highest price levels (one per candle)
520    pub high: Vec<f64>,
521    /// List of lowest price levels (one per candle)
522    pub low: Vec<f64>,
523    /// List of prices at open (one per candle)
524    pub open: Vec<f64>,
525    /// Status of the query: "ok" or "no_data"
526    pub status: String,
527    /// Values of the time axis given in milliseconds since UNIX epoch
528    pub ticks: Vec<i64>,
529    /// List of volume bars (in base currency, one per candle)
530    pub volume: Vec<f64>,
531}
532
533/// Response from `public/get_order_book` endpoint.
534///
535/// Contains the current order book state with bids, asks, and market data.
536#[derive(Clone, Debug, Serialize, Deserialize)]
537pub struct DeribitOrderBook {
538    /// The timestamp of the order book (milliseconds since UNIX epoch)
539    pub timestamp: i64,
540    /// Unique instrument identifier
541    pub instrument_name: String,
542    /// List of bids as [price, amount] pairs (kept as f64 for performance)
543    pub bids: Vec<[f64; 2]>,
544    /// List of asks as [price, amount] pairs (kept as f64 for performance)
545    pub asks: Vec<[f64; 2]>,
546    /// The state of the order book: "open" or "closed"
547    pub state: String,
548    /// The current best bid price (null if there aren't any bids)
549    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
550    pub best_bid_price: Option<Decimal>,
551    /// The current best ask price (null if there aren't any asks)
552    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
553    pub best_ask_price: Option<Decimal>,
554    /// The order size of all best bids
555    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
556    pub best_bid_amount: Option<Decimal>,
557    /// The order size of all best asks
558    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
559    pub best_ask_amount: Option<Decimal>,
560    /// The mark price for the instrument
561    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
562    pub mark_price: Option<Decimal>,
563    /// The price for the last trade
564    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
565    pub last_price: Option<Decimal>,
566    /// Current index price
567    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
568    pub index_price: Option<Decimal>,
569    /// The total amount of outstanding contracts
570    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
571    pub open_interest: Option<Decimal>,
572    /// The maximum price for the future
573    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
574    pub max_price: Option<Decimal>,
575    /// The minimum price for the future
576    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
577    pub min_price: Option<Decimal>,
578    /// Current funding (perpetual only)
579    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
580    pub current_funding: Option<Decimal>,
581    /// Funding 8h (perpetual only)
582    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
583    pub funding_8h: Option<Decimal>,
584    /// The settlement price for the instrument (when state = open)
585    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
586    pub settlement_price: Option<Decimal>,
587    /// The settlement/delivery price for the instrument (when state = closed)
588    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
589    pub delivery_price: Option<Decimal>,
590    /// (Only for option) implied volatility for best bid
591    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
592    pub bid_iv: Option<Decimal>,
593    /// (Only for option) implied volatility for best ask
594    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
595    pub ask_iv: Option<Decimal>,
596    /// (Only for option) implied volatility for mark price
597    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
598    pub mark_iv: Option<Decimal>,
599    /// Underlying price for implied volatility calculations (options only)
600    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
601    pub underlying_price: Option<Decimal>,
602    /// Name of the underlying future, or index_price (options only)
603    #[serde(default)]
604    pub underlying_index: Option<serde_json::Value>,
605    /// Interest rate used in implied volatility calculations (options only)
606    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
607    pub interest_rate: Option<Decimal>,
608}
609
610/// Wire DTO for `/public/get_book_summary_by_currency` entries.
611///
612/// Pure venue payload (no engine timestamps). Convert to the domain
613/// [`crate::data_types::DeribitBookSummary`] before emitting custom data.
614#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
615pub struct DeribitBookSummaryRaw {
616    /// Unique instrument identifier (e.g. "BTC-28MAR25-90000-C")
617    pub instrument_name: String,
618    /// The forward/underlying price for implied volatility calculations
619    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
620    pub underlying_price: Option<Decimal>,
621    /// Name of the underlying future or index (e.g. "BTC-28MAR25" or "SYN.BTC-28MAR25")
622    #[serde(default)]
623    pub underlying_index: Option<String>,
624    /// Mark price for the instrument
625    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
626    pub mark_price: Option<Decimal>,
627    /// Mid price
628    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
629    pub mid_price: Option<Decimal>,
630    /// Best bid price
631    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
632    pub bid_price: Option<Decimal>,
633    /// Best ask price
634    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
635    pub ask_price: Option<Decimal>,
636    /// Last traded price (`last` on the wire)
637    #[serde(
638        default,
639        rename = "last",
640        deserialize_with = "deserialize_optional_decimal"
641    )]
642    pub last_price: Option<Decimal>,
643    /// Mark implied volatility (options; may be absent on some product kinds)
644    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
645    pub mark_iv: Option<Decimal>,
646    /// Bid implied volatility (options)
647    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
648    pub bid_iv: Option<Decimal>,
649    /// Ask implied volatility (options)
650    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
651    pub ask_iv: Option<Decimal>,
652    /// Interest rate used in IV calculations (options)
653    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
654    pub interest_rate: Option<Decimal>,
655    /// Open interest
656    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
657    pub open_interest: Option<Decimal>,
658    /// Open interest value when provided by the venue
659    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
660    pub open_interest_value: Option<Decimal>,
661    /// 24h volume in contracts / base units
662    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
663    pub volume: Option<Decimal>,
664    /// 24h volume in USD
665    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
666    pub volume_usd: Option<Decimal>,
667    /// 24h notional volume
668    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
669    pub volume_notional: Option<Decimal>,
670    /// 24h volume in BTC (when provided)
671    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
672    pub volume_btc: Option<Decimal>,
673    /// 24h high price
674    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
675    pub high: Option<Decimal>,
676    /// 24h low price
677    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
678    pub low: Option<Decimal>,
679    /// 24h price change (`price_change` on the wire)
680    #[serde(
681        default,
682        rename = "price_change",
683        deserialize_with = "deserialize_optional_decimal"
684    )]
685    pub price_change: Option<Decimal>,
686    /// Estimated delivery price
687    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
688    pub estimated_delivery_price: Option<Decimal>,
689    /// Settlement/delivery price when present
690    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
691    pub delivery_price: Option<Decimal>,
692    /// Base currency
693    #[serde(default)]
694    pub base_currency: Option<String>,
695    /// Quote currency
696    #[serde(default)]
697    pub quote_currency: Option<String>,
698    /// Instrument creation time (milliseconds since UNIX epoch)
699    #[serde(default)]
700    pub creation_timestamp: i64,
701}
702
703#[cfg(test)]
704mod book_summary_tests {
705    use rstest::rstest;
706    use rust_decimal_macros::dec;
707
708    use super::*;
709
710    #[rstest]
711    fn test_book_summary_raw_deserializes_full_option_payload() {
712        let json = r#"{
713            "instrument_name": "BTC-28MAR25-90000-C",
714            "underlying_price": 95000.5,
715            "underlying_index": "SYN.BTC-28MAR25",
716            "mark_price": 0.042,
717            "mid_price": 0.041,
718            "bid_price": 0.040,
719            "ask_price": 0.042,
720            "last": 0.0415,
721            "mark_iv": 55.2,
722            "bid_iv": 54.0,
723            "ask_iv": 56.1,
724            "interest_rate": 0.0,
725            "open_interest": 123.5,
726            "volume": 10.0,
727            "volume_usd": 950000.0,
728            "volume_notional": 10.0,
729            "high": 0.05,
730            "low": 0.03,
731            "price_change": 0.01,
732            "estimated_delivery_price": 94900.0,
733            "base_currency": "BTC",
734            "quote_currency": "USD",
735            "creation_timestamp": 1710000000000
736        }"#;
737
738        let summary: DeribitBookSummaryRaw = serde_json::from_str(json).unwrap();
739        assert_eq!(summary.instrument_name, "BTC-28MAR25-90000-C");
740        assert_eq!(summary.underlying_price, Some(dec!(95000.5)));
741        assert_eq!(summary.underlying_index.as_deref(), Some("SYN.BTC-28MAR25"));
742        assert_eq!(summary.mark_price, Some(dec!(0.042)));
743        assert_eq!(summary.last_price, Some(dec!(0.0415)));
744        assert_eq!(summary.mark_iv, Some(dec!(55.2)));
745        assert_eq!(summary.bid_price, Some(dec!(0.040)));
746        assert_eq!(summary.ask_price, Some(dec!(0.042)));
747        assert_eq!(summary.open_interest, Some(dec!(123.5)));
748        assert_eq!(summary.volume_usd, Some(dec!(950000.0)));
749        assert_eq!(summary.price_change, Some(dec!(0.01)));
750        assert_eq!(summary.creation_timestamp, 1_710_000_000_000);
751    }
752
753    #[rstest]
754    fn test_book_summary_raw_deserializes_minimal_payload() {
755        let json = r#"{
756            "instrument_name": "BTC-28MAR25-90000-C",
757            "creation_timestamp": 1710000000000
758        }"#;
759
760        let summary: DeribitBookSummaryRaw = serde_json::from_str(json).unwrap();
761        assert_eq!(summary.instrument_name, "BTC-28MAR25-90000-C");
762        assert!(summary.mark_iv.is_none());
763        assert!(summary.open_interest.is_none());
764        assert_eq!(summary.creation_timestamp, 1_710_000_000_000);
765    }
766}
767
768/// Ticker data from `/public/ticker` endpoint.
769///
770/// Only the fields needed for forward price extraction are included;
771/// serde will ignore the many additional fields returned by the API.
772#[derive(Clone, Debug, Serialize, Deserialize)]
773pub struct DeribitTicker {
774    /// Unique instrument identifier (e.g., "BTC-28FEB26-65000-C")
775    pub instrument_name: String,
776    /// Underlying price for implied volatility calculations (options only)
777    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
778    pub underlying_price: Option<Decimal>,
779    /// Name of the underlying future or index (e.g., "BTC-28MAR25" or "SYN.BTC-28MAR25")
780    #[serde(default)]
781    pub underlying_index: Option<String>,
782}
783
784/// Position data from `/private/get_positions` endpoint.
785///
786/// Contains information about a single position in a specific instrument.
787#[derive(Debug, Clone, Serialize, Deserialize)]
788pub struct DeribitPosition {
789    /// Unique instrument identifier
790    pub instrument_name: Ustr,
791    /// Position direction: "buy" (long), "sell" (short), or "zero" (flat)
792    pub direction: String,
793    /// Position size in contracts (positive = long, negative = short)
794    #[serde(
795        serialize_with = "nautilus_core::serialization::serialize_decimal",
796        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
797    )]
798    pub size: Decimal,
799    /// Average entry price
800    #[serde(
801        serialize_with = "nautilus_core::serialization::serialize_decimal",
802        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
803    )]
804    pub average_price: Decimal,
805    /// Current mark price
806    #[serde(
807        serialize_with = "nautilus_core::serialization::serialize_decimal",
808        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
809    )]
810    pub mark_price: Decimal,
811    /// Current index price
812    #[serde(
813        default,
814        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
815        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
816    )]
817    pub index_price: Option<Decimal>,
818    /// Maintenance margin
819    #[serde(
820        serialize_with = "nautilus_core::serialization::serialize_decimal",
821        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
822    )]
823    pub maintenance_margin: Decimal,
824    /// Initial margin
825    #[serde(
826        serialize_with = "nautilus_core::serialization::serialize_decimal",
827        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
828    )]
829    pub initial_margin: Decimal,
830    /// Leverage used for the position
831    #[serde(default)]
832    pub leverage: Option<i64>,
833    /// Current unrealized profit/loss
834    #[serde(
835        serialize_with = "nautilus_core::serialization::serialize_decimal",
836        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
837    )]
838    pub floating_profit_loss: Decimal,
839    /// Realized profit/loss for this position
840    #[serde(
841        serialize_with = "nautilus_core::serialization::serialize_decimal",
842        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
843    )]
844    pub realized_profit_loss: Decimal,
845    /// Total profit/loss (floating + realized)
846    #[serde(
847        serialize_with = "nautilus_core::serialization::serialize_decimal",
848        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
849    )]
850    pub total_profit_loss: Decimal,
851    /// Product type: future, option, spot, etc.
852    pub kind: DeribitProductType,
853    /// Position size in currency units (for currency-quoted positions)
854    #[serde(
855        default,
856        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
857        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
858    )]
859    pub size_currency: Option<Decimal>,
860    /// Estimated liquidation price
861    #[serde(
862        default,
863        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
864        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
865    )]
866    pub estimated_liquidation_price: Option<Decimal>,
867    /// Position delta (for options)
868    #[serde(
869        default,
870        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
871        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
872    )]
873    pub delta: Option<Decimal>,
874    /// Position gamma (for options)
875    #[serde(
876        default,
877        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
878        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
879    )]
880    pub gamma: Option<Decimal>,
881    /// Position vega (for options)
882    #[serde(
883        default,
884        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
885        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
886    )]
887    pub vega: Option<Decimal>,
888    /// Position theta (for options)
889    #[serde(
890        default,
891        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
892        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
893    )]
894    pub theta: Option<Decimal>,
895    /// Settlement price (if settled)
896    #[serde(
897        default,
898        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
899        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
900    )]
901    pub settlement_price: Option<Decimal>,
902    /// Open orders margin for this position
903    #[serde(
904        default,
905        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
906        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
907    )]
908    pub open_orders_margin: Option<Decimal>,
909    /// Average price in USD (for currency-margined contracts)
910    #[serde(
911        default,
912        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
913        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
914    )]
915    pub average_price_usd: Option<Decimal>,
916    /// Realized profit loss (session)
917    #[serde(
918        default,
919        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
920        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
921    )]
922    pub realized_profit_loss_session: Option<Decimal>,
923    /// Floating profit loss in USD
924    #[serde(
925        default,
926        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
927        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
928    )]
929    pub floating_profit_loss_usd: Option<Decimal>,
930}
931
932/// Response wrapper for user trades endpoints.
933///
934/// Contains the trades array and pagination information.
935/// Used by `/private/get_user_trades_by_*` endpoints.
936#[derive(Clone, Debug, Serialize, Deserialize)]
937pub struct DeribitUserTradesResponse {
938    /// Whether there are more trades available.
939    pub has_more: bool,
940    /// Array of user trade objects.
941    pub trades: Vec<crate::websocket::messages::DeribitUserTradeMsg>,
942}
943
944#[cfg(test)]
945mod tests {
946    use rstest::rstest;
947    use rust_decimal_macros::dec;
948
949    use super::*;
950
951    #[rstest]
952    fn test_deserialize_public_trade_with_empty_mark_and_index_price() {
953        let json = r#"{
954            "amount": 1.0,
955            "direction": "sell",
956            "index_price": "",
957            "instrument_name": "ETH-PERPETUAL",
958            "mark_price": "",
959            "price": 2968.3,
960            "tick_direction": 0,
961            "timestamp": 1766332040636,
962            "trade_id": "ETH-123",
963            "trade_seq": 1
964        }"#;
965
966        let trade: DeribitPublicTrade = serde_json::from_str(json).unwrap();
967        assert_eq!(trade.index_price, None);
968        assert_eq!(trade.mark_price, None);
969        assert_eq!(trade.price, dec!(2968.3));
970    }
971
972    #[rstest]
973    fn test_deserialize_public_trade_with_missing_mark_and_index_price() {
974        let json = r#"{
975            "amount": 1.0,
976            "direction": "sell",
977            "instrument_name": "ETH-PERPETUAL",
978            "price": 2968.3,
979            "tick_direction": 0,
980            "timestamp": 1766332040636,
981            "trade_id": "ETH-123",
982            "trade_seq": 1
983        }"#;
984
985        let trade: DeribitPublicTrade = serde_json::from_str(json).unwrap();
986        assert_eq!(trade.index_price, None);
987        assert_eq!(trade.mark_price, None);
988    }
989
990    #[rstest]
991    fn test_deserialize_public_trade_with_present_mark_and_index_price() {
992        let json = r#"{
993            "amount": 1.0,
994            "direction": "sell",
995            "index_price": 2967.73,
996            "instrument_name": "ETH-PERPETUAL",
997            "mark_price": 2968.01,
998            "price": 2968.3,
999            "tick_direction": 0,
1000            "timestamp": 1766332040636,
1001            "trade_id": "ETH-123",
1002            "trade_seq": 1
1003        }"#;
1004
1005        let trade: DeribitPublicTrade = serde_json::from_str(json).unwrap();
1006        assert_eq!(trade.index_price, Some(dec!(2967.73)));
1007        assert_eq!(trade.mark_price, Some(dec!(2968.01)));
1008    }
1009
1010    #[rstest]
1011    fn test_deserialize_expirations_currency_keyed_response() {
1012        let json = r#"{
1013            "btc": {
1014                "option": ["20MAY26", "26JUN26"],
1015                "future": ["20MAY26", "PERPETUAL"]
1016            }
1017        }"#;
1018
1019        let response: DeribitExpirationsResponse = serde_json::from_str(json).unwrap();
1020        let expirations = response.expirations_for_currency("BTC").unwrap();
1021        assert_eq!(expirations.option, vec!["20MAY26", "26JUN26"]);
1022        assert_eq!(expirations.future, vec!["20MAY26", "PERPETUAL"]);
1023    }
1024
1025    #[rstest]
1026    fn test_deserialize_expirations_direct_response() {
1027        let json = r#"{
1028            "option": ["20MAY26", "26JUN26"],
1029            "future": ["20MAY26", "PERPETUAL"]
1030        }"#;
1031
1032        let response: DeribitExpirationsResponse = serde_json::from_str(json).unwrap();
1033        let expirations = response.expirations_for_currency("any").unwrap();
1034        assert_eq!(expirations.option, vec!["20MAY26", "26JUN26"]);
1035        assert_eq!(expirations.future, vec!["20MAY26", "PERPETUAL"]);
1036    }
1037}