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/// Book summary data from `/public/get_book_summary_by_currency` endpoint.
611///
612/// Each entry represents a single instrument's book summary including the
613/// forward/underlying price used for ATM determination.
614#[derive(Clone, Debug, Serialize, Deserialize)]
615pub struct DeribitBookSummary {
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    /// The time when the instrument was created (milliseconds since UNIX epoch)
628    pub creation_timestamp: i64,
629}
630
631/// Ticker data from `/public/ticker` endpoint.
632///
633/// Only the fields needed for forward price extraction are included;
634/// serde will ignore the many additional fields returned by the API.
635#[derive(Clone, Debug, Serialize, Deserialize)]
636pub struct DeribitTicker {
637    /// Unique instrument identifier (e.g., "BTC-28FEB26-65000-C")
638    pub instrument_name: String,
639    /// Underlying price for implied volatility calculations (options only)
640    #[serde(default, deserialize_with = "deserialize_optional_decimal")]
641    pub underlying_price: Option<Decimal>,
642    /// Name of the underlying future or index (e.g., "BTC-28MAR25" or "SYN.BTC-28MAR25")
643    #[serde(default)]
644    pub underlying_index: Option<String>,
645}
646
647/// Position data from `/private/get_positions` endpoint.
648///
649/// Contains information about a single position in a specific instrument.
650#[derive(Debug, Clone, Serialize, Deserialize)]
651pub struct DeribitPosition {
652    /// Unique instrument identifier
653    pub instrument_name: Ustr,
654    /// Position direction: "buy" (long), "sell" (short), or "zero" (flat)
655    pub direction: String,
656    /// Position size in contracts (positive = long, negative = short)
657    #[serde(
658        serialize_with = "nautilus_core::serialization::serialize_decimal",
659        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
660    )]
661    pub size: Decimal,
662    /// Average entry price
663    #[serde(
664        serialize_with = "nautilus_core::serialization::serialize_decimal",
665        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
666    )]
667    pub average_price: Decimal,
668    /// Current mark price
669    #[serde(
670        serialize_with = "nautilus_core::serialization::serialize_decimal",
671        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
672    )]
673    pub mark_price: Decimal,
674    /// Current index price
675    #[serde(
676        default,
677        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
678        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
679    )]
680    pub index_price: Option<Decimal>,
681    /// Maintenance margin
682    #[serde(
683        serialize_with = "nautilus_core::serialization::serialize_decimal",
684        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
685    )]
686    pub maintenance_margin: Decimal,
687    /// Initial margin
688    #[serde(
689        serialize_with = "nautilus_core::serialization::serialize_decimal",
690        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
691    )]
692    pub initial_margin: Decimal,
693    /// Leverage used for the position
694    #[serde(default)]
695    pub leverage: Option<i64>,
696    /// Current unrealized profit/loss
697    #[serde(
698        serialize_with = "nautilus_core::serialization::serialize_decimal",
699        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
700    )]
701    pub floating_profit_loss: Decimal,
702    /// Realized profit/loss for this position
703    #[serde(
704        serialize_with = "nautilus_core::serialization::serialize_decimal",
705        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
706    )]
707    pub realized_profit_loss: Decimal,
708    /// Total profit/loss (floating + realized)
709    #[serde(
710        serialize_with = "nautilus_core::serialization::serialize_decimal",
711        deserialize_with = "nautilus_core::serialization::deserialize_decimal"
712    )]
713    pub total_profit_loss: Decimal,
714    /// Product type: future, option, spot, etc.
715    pub kind: DeribitProductType,
716    /// Position size in currency units (for currency-quoted positions)
717    #[serde(
718        default,
719        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
720        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
721    )]
722    pub size_currency: Option<Decimal>,
723    /// Estimated liquidation price
724    #[serde(
725        default,
726        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
727        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
728    )]
729    pub estimated_liquidation_price: Option<Decimal>,
730    /// Position delta (for options)
731    #[serde(
732        default,
733        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
734        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
735    )]
736    pub delta: Option<Decimal>,
737    /// Position gamma (for options)
738    #[serde(
739        default,
740        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
741        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
742    )]
743    pub gamma: Option<Decimal>,
744    /// Position vega (for options)
745    #[serde(
746        default,
747        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
748        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
749    )]
750    pub vega: Option<Decimal>,
751    /// Position theta (for options)
752    #[serde(
753        default,
754        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
755        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
756    )]
757    pub theta: Option<Decimal>,
758    /// Settlement price (if settled)
759    #[serde(
760        default,
761        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
762        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
763    )]
764    pub settlement_price: Option<Decimal>,
765    /// Open orders margin for this position
766    #[serde(
767        default,
768        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
769        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
770    )]
771    pub open_orders_margin: Option<Decimal>,
772    /// Average price in USD (for currency-margined contracts)
773    #[serde(
774        default,
775        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
776        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
777    )]
778    pub average_price_usd: Option<Decimal>,
779    /// Realized profit loss (session)
780    #[serde(
781        default,
782        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
783        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
784    )]
785    pub realized_profit_loss_session: Option<Decimal>,
786    /// Floating profit loss in USD
787    #[serde(
788        default,
789        serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
790        deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
791    )]
792    pub floating_profit_loss_usd: Option<Decimal>,
793}
794
795/// Response wrapper for user trades endpoints.
796///
797/// Contains the trades array and pagination information.
798/// Used by `/private/get_user_trades_by_*` endpoints.
799#[derive(Clone, Debug, Serialize, Deserialize)]
800pub struct DeribitUserTradesResponse {
801    /// Whether there are more trades available.
802    pub has_more: bool,
803    /// Array of user trade objects.
804    pub trades: Vec<crate::websocket::messages::DeribitUserTradeMsg>,
805}
806
807#[cfg(test)]
808mod tests {
809    use rstest::rstest;
810    use rust_decimal_macros::dec;
811
812    use super::*;
813
814    #[rstest]
815    fn test_deserialize_public_trade_with_empty_mark_and_index_price() {
816        let json = r#"{
817            "amount": 1.0,
818            "direction": "sell",
819            "index_price": "",
820            "instrument_name": "ETH-PERPETUAL",
821            "mark_price": "",
822            "price": 2968.3,
823            "tick_direction": 0,
824            "timestamp": 1766332040636,
825            "trade_id": "ETH-123",
826            "trade_seq": 1
827        }"#;
828
829        let trade: DeribitPublicTrade = serde_json::from_str(json).unwrap();
830        assert_eq!(trade.index_price, None);
831        assert_eq!(trade.mark_price, None);
832        assert_eq!(trade.price, dec!(2968.3));
833    }
834
835    #[rstest]
836    fn test_deserialize_public_trade_with_missing_mark_and_index_price() {
837        let json = r#"{
838            "amount": 1.0,
839            "direction": "sell",
840            "instrument_name": "ETH-PERPETUAL",
841            "price": 2968.3,
842            "tick_direction": 0,
843            "timestamp": 1766332040636,
844            "trade_id": "ETH-123",
845            "trade_seq": 1
846        }"#;
847
848        let trade: DeribitPublicTrade = serde_json::from_str(json).unwrap();
849        assert_eq!(trade.index_price, None);
850        assert_eq!(trade.mark_price, None);
851    }
852
853    #[rstest]
854    fn test_deserialize_public_trade_with_present_mark_and_index_price() {
855        let json = r#"{
856            "amount": 1.0,
857            "direction": "sell",
858            "index_price": 2967.73,
859            "instrument_name": "ETH-PERPETUAL",
860            "mark_price": 2968.01,
861            "price": 2968.3,
862            "tick_direction": 0,
863            "timestamp": 1766332040636,
864            "trade_id": "ETH-123",
865            "trade_seq": 1
866        }"#;
867
868        let trade: DeribitPublicTrade = serde_json::from_str(json).unwrap();
869        assert_eq!(trade.index_price, Some(dec!(2967.73)));
870        assert_eq!(trade.mark_price, Some(dec!(2968.01)));
871    }
872
873    #[rstest]
874    fn test_deserialize_expirations_currency_keyed_response() {
875        let json = r#"{
876            "btc": {
877                "option": ["20MAY26", "26JUN26"],
878                "future": ["20MAY26", "PERPETUAL"]
879            }
880        }"#;
881
882        let response: DeribitExpirationsResponse = serde_json::from_str(json).unwrap();
883        let expirations = response.expirations_for_currency("BTC").unwrap();
884        assert_eq!(expirations.option, vec!["20MAY26", "26JUN26"]);
885        assert_eq!(expirations.future, vec!["20MAY26", "PERPETUAL"]);
886    }
887
888    #[rstest]
889    fn test_deserialize_expirations_direct_response() {
890        let json = r#"{
891            "option": ["20MAY26", "26JUN26"],
892            "future": ["20MAY26", "PERPETUAL"]
893        }"#;
894
895        let response: DeribitExpirationsResponse = serde_json::from_str(json).unwrap();
896        let expirations = response.expirations_for_currency("any").unwrap();
897        assert_eq!(expirations.option, vec!["20MAY26", "26JUN26"]);
898        assert_eq!(expirations.future, vec!["20MAY26", "PERPETUAL"]);
899    }
900}