Skip to main content

nautilus_deribit/http/
query.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 query parameter builders.
17
18use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20
21use super::models::{DeribitCurrency, DeribitProductType};
22
23/// Instrument kind filter for `/public/get_expirations` endpoint.
24#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
25#[serde(rename_all = "snake_case")]
26pub enum DeribitExpirationKind {
27    /// Future contract expirations.
28    Future,
29    /// Option contract expirations.
30    Option,
31    /// All supported instrument kinds.
32    Any,
33    /// Future combo expirations.
34    FutureCombo,
35    /// Option combo expirations.
36    OptionCombo,
37}
38
39/// Query parameters for `/public/get_expirations` endpoint.
40#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
41#[builder(setter(into, strip_option))]
42pub struct GetExpirationsParams {
43    /// Settlement currency, `any`, or `grouped`.
44    pub currency: String,
45    /// Instrument kind filter.
46    pub kind: DeribitExpirationKind,
47    /// Optional currency pair filter (e.g., "btc_usd").
48    #[serde(skip_serializing_if = "Option::is_none")]
49    #[builder(default)]
50    pub currency_pair: Option<String>,
51}
52
53impl GetExpirationsParams {
54    /// Creates a new builder for [`GetExpirationsParams`].
55    #[must_use]
56    pub fn builder() -> GetExpirationsParamsBuilder {
57        GetExpirationsParamsBuilder::default()
58    }
59
60    /// Creates parameters for a settlement currency and product kind.
61    #[must_use]
62    pub fn new(currency: impl Into<String>, kind: DeribitExpirationKind) -> Self {
63        Self {
64            currency: currency.into(),
65            kind,
66            currency_pair: None,
67        }
68    }
69}
70
71/// Query parameters for `/public/get_instruments` endpoint.
72#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
73#[builder(setter(into, strip_option))]
74pub struct GetInstrumentsParams {
75    /// Currency filter
76    pub currency: DeribitCurrency,
77    /// Optional product type filter
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[builder(default)]
80    pub kind: Option<DeribitProductType>,
81    /// Whether to include expired instruments
82    #[serde(skip_serializing_if = "Option::is_none")]
83    #[builder(default)]
84    pub expired: Option<bool>,
85}
86
87impl GetInstrumentsParams {
88    /// Creates a new builder for [`GetInstrumentsParams`].
89    #[must_use]
90    pub fn builder() -> GetInstrumentsParamsBuilder {
91        GetInstrumentsParamsBuilder::default()
92    }
93
94    /// Creates parameters for a specific currency.
95    #[must_use]
96    pub fn new(currency: DeribitCurrency) -> Self {
97        Self {
98            currency,
99            kind: None,
100            expired: None,
101        }
102    }
103
104    /// Creates parameters for a specific currency and product type.
105    #[must_use]
106    pub fn with_kind(currency: DeribitCurrency, kind: DeribitProductType) -> Self {
107        Self {
108            currency,
109            kind: Some(kind),
110            expired: None,
111        }
112    }
113}
114
115/// Query parameters for `/public/get_instrument` endpoint.
116#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
117pub struct GetInstrumentParams {
118    /// Instrument name (e.g., "BTC-PERPETUAL", "ETH-25MAR23-2000-C")
119    pub instrument_name: String,
120}
121
122/// Query parameters for `/public/get_combos` endpoint.
123#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
124#[builder(setter(into, strip_option))]
125pub struct GetCombosParams {
126    /// Currency to query.
127    pub currency: DeribitCurrency,
128}
129
130impl GetCombosParams {
131    /// Creates a new builder for [`GetCombosParams`].
132    #[must_use]
133    pub fn builder() -> GetCombosParamsBuilder {
134        GetCombosParamsBuilder::default()
135    }
136
137    /// Creates parameters for a specific currency.
138    #[must_use]
139    pub fn new(currency: DeribitCurrency) -> Self {
140        Self { currency }
141    }
142}
143
144/// Query parameters for `/private/get_account_summaries` endpoint.
145#[derive(Clone, Debug, Default, Deserialize, Serialize)]
146pub struct GetAccountSummariesParams {
147    /// The user id for the subaccount.
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub subaccount_id: Option<String>,
150    /// Include extended fields
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub extended: Option<bool>,
153}
154
155impl GetAccountSummariesParams {
156    /// Creates a new instance with both subaccount ID and extended flag.
157    #[must_use]
158    pub fn new(subaccount_id: String, extended: bool) -> Self {
159        Self {
160            subaccount_id: Some(subaccount_id),
161            extended: Some(extended),
162        }
163    }
164}
165
166/// Query parameters for `/public/get_last_trades_by_instrument_and_time` endpoint.
167#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
168#[builder(setter(into, strip_option))]
169pub struct GetLastTradesByInstrumentAndTimeParams {
170    /// Instrument name (e.g., "BTC-PERPETUAL")
171    pub instrument_name: String,
172    /// The earliest timestamp to return result from (milliseconds since the UNIX epoch)
173    pub start_timestamp: i64,
174    /// The most recent timestamp to return result from (milliseconds since the UNIX epoch)
175    pub end_timestamp: i64,
176    /// Number of requested items, default - 10, maximum - 1000
177    #[serde(skip_serializing_if = "Option::is_none")]
178    #[builder(default)]
179    pub count: Option<u32>,
180    /// Direction of results sorting: "asc", "desc", or "default"
181    #[serde(skip_serializing_if = "Option::is_none")]
182    #[builder(default)]
183    pub sorting: Option<String>,
184}
185
186impl GetLastTradesByInstrumentAndTimeParams {
187    /// Creates a new instance with the required parameters.
188    #[must_use]
189    pub fn new(
190        instrument_name: impl Into<String>,
191        start_timestamp: i64,
192        end_timestamp: i64,
193        count: Option<u32>,
194        sorting: Option<String>,
195    ) -> Self {
196        Self {
197            instrument_name: instrument_name.into(),
198            start_timestamp,
199            end_timestamp,
200            count,
201            sorting,
202        }
203    }
204}
205
206/// Query parameters for `/public/get_last_trades_by_currency` endpoint.
207///
208/// Mirrors the per-instrument variant but selects trades by currency and
209/// (optionally) product kind. Required to backfill combo trades, which are
210/// not accessible via the instrument-scoped endpoint.
211#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
212#[builder(setter(into, strip_option))]
213pub struct GetLastTradesByCurrencyParams {
214    /// Currency to query.
215    pub currency: DeribitCurrency,
216    /// Optional product kind filter (e.g., `option_combo`, `future_combo`).
217    #[serde(skip_serializing_if = "Option::is_none")]
218    #[builder(default)]
219    pub kind: Option<DeribitProductType>,
220    /// First trade ID (inclusive) of the range to fetch.
221    #[serde(skip_serializing_if = "Option::is_none")]
222    #[builder(default)]
223    pub start_id: Option<String>,
224    /// Last trade ID (inclusive) of the range to fetch.
225    #[serde(skip_serializing_if = "Option::is_none")]
226    #[builder(default)]
227    pub end_id: Option<String>,
228    /// Maximum number of trades to return (default 10, max 1000).
229    #[serde(skip_serializing_if = "Option::is_none")]
230    #[builder(default)]
231    pub count: Option<u32>,
232    /// Whether to include expired-instrument trades.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    #[builder(default)]
235    pub include_old: Option<bool>,
236    /// Direction of results sorting: `asc`, `desc`, or `default`.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    #[builder(default)]
239    pub sorting: Option<String>,
240}
241
242impl GetLastTradesByCurrencyParams {
243    /// Creates a new builder for [`GetLastTradesByCurrencyParams`].
244    #[must_use]
245    pub fn builder() -> GetLastTradesByCurrencyParamsBuilder {
246        GetLastTradesByCurrencyParamsBuilder::default()
247    }
248
249    /// Creates parameters for a specific currency.
250    #[must_use]
251    pub fn new(currency: DeribitCurrency) -> Self {
252        Self {
253            currency,
254            kind: None,
255            start_id: None,
256            end_id: None,
257            count: None,
258            include_old: None,
259            sorting: None,
260        }
261    }
262
263    /// Creates parameters for a specific currency and product kind.
264    #[must_use]
265    pub fn with_kind(currency: DeribitCurrency, kind: DeribitProductType) -> Self {
266        Self {
267            currency,
268            kind: Some(kind),
269            start_id: None,
270            end_id: None,
271            count: None,
272            include_old: None,
273            sorting: None,
274        }
275    }
276}
277
278/// Query parameters for `/public/get_tradingview_chart_data` endpoint.
279#[derive(Clone, Debug, Deserialize, Serialize)]
280pub struct GetTradingViewChartDataParams {
281    /// Instrument name (e.g., "BTC-PERPETUAL")
282    pub instrument_name: String,
283    /// The earliest timestamp to return result from (milliseconds since UNIX epoch)
284    pub start_timestamp: i64,
285    /// The most recent timestamp to return result from (milliseconds since UNIX epoch)
286    pub end_timestamp: i64,
287    /// Chart bars resolution given in full minutes or keyword "1D"
288    /// Supported resolutions: 1, 3, 5, 10, 15, 30, 60, 120, 180, 360, 720, 1D
289    pub resolution: String,
290}
291
292impl GetTradingViewChartDataParams {
293    /// Creates new parameters for chart data request.
294    #[must_use]
295    pub fn new(
296        instrument_name: String,
297        start_timestamp: i64,
298        end_timestamp: i64,
299        resolution: String,
300    ) -> Self {
301        Self {
302            instrument_name,
303            start_timestamp,
304            end_timestamp,
305            resolution,
306        }
307    }
308}
309
310/// Query parameters for `/public/get_order_book` endpoint.
311#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
312#[builder(setter(into, strip_option))]
313pub struct GetOrderBookParams {
314    /// Instrument name (e.g., "BTC-PERPETUAL")
315    pub instrument_name: String,
316    /// The number of entries to return for bids and asks.
317    /// Valid values: 1, 5, 10, 20, 50, 100, 1000, 10000
318    /// Maximum: 10000
319    #[serde(skip_serializing_if = "Option::is_none")]
320    #[builder(default)]
321    pub depth: Option<u32>,
322}
323
324impl GetOrderBookParams {
325    /// Creates parameters with required fields.
326    #[must_use]
327    pub fn new(instrument_name: String, depth: Option<u32>) -> Self {
328        Self {
329            instrument_name,
330            depth,
331        }
332    }
333}
334
335/// Query parameters for `/private/get_order_state` endpoint.
336/// Retrieves a single order by its ID.
337#[derive(Clone, Debug, Deserialize, Serialize)]
338pub struct GetOrderStateParams {
339    /// The order ID to look up.
340    pub order_id: String,
341}
342
343impl GetOrderStateParams {
344    /// Creates parameters for a specific order ID.
345    #[must_use]
346    pub fn new(order_id: impl Into<String>) -> Self {
347        Self {
348            order_id: order_id.into(),
349        }
350    }
351}
352
353/// Query parameters for `/private/get_open_orders` endpoint.
354/// Retrieves all open orders across all currencies and instruments.
355#[derive(Clone, Debug, Default, Deserialize, Serialize)]
356pub struct GetOpenOrdersParams {}
357
358impl GetOpenOrdersParams {
359    /// Creates parameters to get all open orders.
360    #[must_use]
361    pub fn new() -> Self {
362        Self {}
363    }
364}
365
366/// Query parameters for `/private/get_open_orders_by_instrument` endpoint.
367/// Retrieves open orders for a specific instrument.
368#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
369#[builder(setter(into, strip_option))]
370pub struct GetOpenOrdersByInstrumentParams {
371    /// Instrument name (e.g., "BTC-PERPETUAL")
372    pub instrument_name: String,
373    /// Order type filter: "all", "limit", "stop_all", "stop_limit", "stop_market",
374    /// "take_all", "take_limit", "take_market", "trailing_all", "trailing_stop"
375    #[serde(skip_serializing_if = "Option::is_none")]
376    #[builder(default)]
377    pub r#type: Option<String>,
378}
379
380impl GetOpenOrdersByInstrumentParams {
381    /// Creates parameters for a specific instrument.
382    #[must_use]
383    pub fn new(instrument_name: impl Into<String>) -> Self {
384        Self {
385            instrument_name: instrument_name.into(),
386            r#type: None,
387        }
388    }
389}
390
391/// Query parameters for `/private/get_order_history_by_instrument` endpoint.
392/// Retrieves historical orders for a specific instrument.
393#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
394#[builder(setter(into, strip_option))]
395pub struct GetOrderHistoryByInstrumentParams {
396    /// Instrument name (e.g., "BTC-PERPETUAL")
397    pub instrument_name: String,
398    /// Number of requested items, default - 20
399    #[serde(skip_serializing_if = "Option::is_none")]
400    #[builder(default)]
401    pub count: Option<u32>,
402    /// Offset for pagination
403    #[serde(skip_serializing_if = "Option::is_none")]
404    #[builder(default)]
405    pub offset: Option<u32>,
406    /// Include orders older than 3 days
407    #[serde(skip_serializing_if = "Option::is_none")]
408    #[builder(default)]
409    pub include_old: Option<bool>,
410    /// Include unfilled orders
411    #[serde(skip_serializing_if = "Option::is_none")]
412    #[builder(default)]
413    pub include_unfilled: Option<bool>,
414}
415
416impl GetOrderHistoryByInstrumentParams {
417    /// Creates parameters for a specific instrument.
418    #[must_use]
419    pub fn new(instrument_name: impl Into<String>) -> Self {
420        Self {
421            instrument_name: instrument_name.into(),
422            count: None,
423            offset: None,
424            include_old: None,
425            include_unfilled: None,
426        }
427    }
428
429    /// Creates a new builder for [`GetOrderHistoryByInstrumentParams`].
430    #[must_use]
431    pub fn builder() -> GetOrderHistoryByInstrumentParamsBuilder {
432        GetOrderHistoryByInstrumentParamsBuilder::default()
433    }
434}
435
436/// Query parameters for `/private/get_order_history_by_currency` endpoint.
437/// Retrieves historical orders for a specific currency.
438#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
439#[builder(setter(into, strip_option))]
440pub struct GetOrderHistoryByCurrencyParams {
441    /// Currency filter
442    pub currency: DeribitCurrency,
443    /// Optional product type filter
444    #[serde(skip_serializing_if = "Option::is_none")]
445    #[builder(default)]
446    pub kind: Option<DeribitProductType>,
447    /// Number of requested items, default - 20, maximum - 1000
448    #[serde(skip_serializing_if = "Option::is_none")]
449    #[builder(default)]
450    pub count: Option<u32>,
451    /// Offset for pagination
452    #[serde(skip_serializing_if = "Option::is_none")]
453    #[builder(default)]
454    pub offset: Option<u32>,
455    /// Include orders older than 3 days
456    #[serde(skip_serializing_if = "Option::is_none")]
457    #[builder(default)]
458    pub include_old: Option<bool>,
459    /// Include unfilled orders
460    #[serde(skip_serializing_if = "Option::is_none")]
461    #[builder(default)]
462    pub include_unfilled: Option<bool>,
463}
464
465impl GetOrderHistoryByCurrencyParams {
466    /// Creates parameters for a specific currency.
467    #[must_use]
468    pub fn new(currency: DeribitCurrency) -> Self {
469        Self {
470            currency,
471            kind: None,
472            count: None,
473            offset: None,
474            include_old: None,
475            include_unfilled: None,
476        }
477    }
478
479    /// Creates a new builder for [`GetOrderHistoryByCurrencyParams`].
480    #[must_use]
481    pub fn builder() -> GetOrderHistoryByCurrencyParamsBuilder {
482        GetOrderHistoryByCurrencyParamsBuilder::default()
483    }
484}
485
486/// Query parameters for `/private/get_user_trades_by_instrument_and_time` endpoint.
487/// Retrieves user trades for a specific instrument within a time range.
488#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
489#[builder(setter(into, strip_option))]
490pub struct GetUserTradesByInstrumentAndTimeParams {
491    /// Instrument name (e.g., "BTC-PERPETUAL")
492    pub instrument_name: String,
493    /// Start timestamp in milliseconds since UNIX epoch
494    pub start_timestamp: i64,
495    /// End timestamp in milliseconds since UNIX epoch
496    pub end_timestamp: i64,
497    /// Number of requested items, default - 10, maximum - 1000
498    #[serde(skip_serializing_if = "Option::is_none")]
499    #[builder(default)]
500    pub count: Option<u32>,
501    /// Direction of results sorting: "asc", "desc", or "default"
502    #[serde(skip_serializing_if = "Option::is_none")]
503    #[builder(default)]
504    pub sorting: Option<String>,
505}
506
507impl GetUserTradesByInstrumentAndTimeParams {
508    /// Creates parameters with required fields.
509    #[must_use]
510    pub fn new(
511        instrument_name: impl Into<String>,
512        start_timestamp: i64,
513        end_timestamp: i64,
514    ) -> Self {
515        Self {
516            instrument_name: instrument_name.into(),
517            start_timestamp,
518            end_timestamp,
519            count: None,
520            sorting: None,
521        }
522    }
523
524    /// Creates a new builder for [`GetUserTradesByInstrumentAndTimeParams`].
525    #[must_use]
526    pub fn builder() -> GetUserTradesByInstrumentAndTimeParamsBuilder {
527        GetUserTradesByInstrumentAndTimeParamsBuilder::default()
528    }
529}
530
531/// Query parameters for `/private/get_user_trades_by_currency_and_time` endpoint.
532/// Retrieves user trades for a specific currency within a time range.
533#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
534#[builder(setter(into, strip_option))]
535pub struct GetUserTradesByCurrencyAndTimeParams {
536    /// Currency filter
537    pub currency: DeribitCurrency,
538    /// Start timestamp in milliseconds since UNIX epoch
539    pub start_timestamp: i64,
540    /// End timestamp in milliseconds since UNIX epoch
541    pub end_timestamp: i64,
542    /// Optional product type filter
543    #[serde(skip_serializing_if = "Option::is_none")]
544    #[builder(default)]
545    pub kind: Option<DeribitProductType>,
546    /// Number of requested items, default - 10, maximum - 1000
547    #[serde(skip_serializing_if = "Option::is_none")]
548    #[builder(default)]
549    pub count: Option<u32>,
550    /// Direction of results sorting: "asc", "desc", or "default"
551    #[serde(skip_serializing_if = "Option::is_none")]
552    #[builder(default)]
553    pub sorting: Option<String>,
554}
555
556impl GetUserTradesByCurrencyAndTimeParams {
557    /// Creates parameters with required fields.
558    #[must_use]
559    pub fn new(currency: DeribitCurrency, start_timestamp: i64, end_timestamp: i64) -> Self {
560        Self {
561            currency,
562            start_timestamp,
563            end_timestamp,
564            kind: None,
565            count: None,
566            sorting: None,
567        }
568    }
569
570    /// Creates a new builder for [`GetUserTradesByCurrencyAndTimeParams`].
571    #[must_use]
572    pub fn builder() -> GetUserTradesByCurrencyAndTimeParamsBuilder {
573        GetUserTradesByCurrencyAndTimeParamsBuilder::default()
574    }
575}
576
577/// Query parameters for `/public/get_book_summary_by_currency` endpoint.
578#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
579#[builder(setter(into, strip_option))]
580pub struct GetBookSummaryByCurrencyParams {
581    /// Currency filter (e.g., "BTC", "ETH")
582    pub currency: String,
583    /// Optional product type filter (e.g., "option", "future")
584    #[serde(skip_serializing_if = "Option::is_none")]
585    #[builder(default)]
586    pub kind: Option<String>,
587}
588
589impl GetBookSummaryByCurrencyParams {
590    /// Creates parameters for options book summaries for a given currency.
591    #[must_use]
592    pub fn options(currency: impl Into<String>) -> Self {
593        Self {
594            currency: currency.into(),
595            kind: Some("option".to_string()),
596        }
597    }
598}
599
600/// Query parameters for `/public/ticker` endpoint.
601#[derive(Clone, Debug, Deserialize, Serialize)]
602pub struct GetTickerParams {
603    /// Instrument name (e.g., "BTC-28FEB26-65000-C")
604    pub instrument_name: String,
605}
606
607/// Query parameters for `/private/get_positions` endpoint.
608/// Retrieves positions for a specific currency.
609#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
610#[builder(setter(into, strip_option))]
611pub struct GetPositionsParams {
612    /// Currency filter
613    pub currency: DeribitCurrency,
614    /// Optional product type filter
615    #[serde(skip_serializing_if = "Option::is_none")]
616    #[builder(default)]
617    pub kind: Option<DeribitProductType>,
618}
619
620impl GetPositionsParams {
621    /// Creates parameters for a specific currency.
622    #[must_use]
623    pub fn new(currency: DeribitCurrency) -> Self {
624        Self {
625            currency,
626            kind: None,
627        }
628    }
629
630    /// Creates a new builder for [`GetPositionsParams`].
631    #[must_use]
632    pub fn builder() -> GetPositionsParamsBuilder {
633        GetPositionsParamsBuilder::default()
634    }
635}
636
637#[cfg(test)]
638mod tests {
639    use rstest::rstest;
640    use serde_json::{Value, json};
641
642    use super::*;
643
644    #[rstest]
645    fn test_get_expirations_params_default_payload() {
646        let params = GetExpirationsParams::new("BTC", DeribitExpirationKind::Option);
647        let value: Value = serde_json::to_value(&params).unwrap();
648        assert_eq!(value, json!({"currency": "BTC", "kind": "option"}));
649    }
650
651    #[rstest]
652    fn test_get_expirations_params_full_payload() {
653        let params = GetExpirationsParams::builder()
654            .currency("grouped")
655            .kind(DeribitExpirationKind::Any)
656            .currency_pair("btc_usd")
657            .build()
658            .unwrap();
659        let value: Value = serde_json::to_value(&params).unwrap();
660        assert_eq!(
661            value,
662            json!({
663                "currency": "grouped",
664                "kind": "any",
665                "currency_pair": "btc_usd",
666            }),
667        );
668    }
669
670    #[rstest]
671    fn test_get_expirations_params_combo_kind_serialization() {
672        let params = GetExpirationsParams::new("BTC", DeribitExpirationKind::OptionCombo);
673        let value: Value = serde_json::to_value(&params).unwrap();
674        assert_eq!(value, json!({"currency": "BTC", "kind": "option_combo"}));
675    }
676
677    #[rstest]
678    fn test_get_combos_params_serialization() {
679        let params = GetCombosParams::new(DeribitCurrency::BTC);
680        let value: Value = serde_json::to_value(params).unwrap();
681        assert_eq!(value, json!({"currency": "BTC"}));
682    }
683
684    #[rstest]
685    fn test_get_last_trades_by_currency_params_default_omits_optionals() {
686        // Only `currency` should appear on the wire when no optional fields
687        // are set; skip_serializing_if = Option::is_none must elide the rest.
688        let params = GetLastTradesByCurrencyParams::new(DeribitCurrency::BTC);
689        let value: Value = serde_json::to_value(&params).unwrap();
690        assert_eq!(value, json!({"currency": "BTC"}));
691    }
692
693    #[rstest]
694    fn test_get_last_trades_by_currency_params_full_payload() {
695        // All fields populated. Pins Deribit wire key names and value
696        // serialization (DeribitProductType uses serde rename for combos).
697        let params = GetLastTradesByCurrencyParams {
698            currency: DeribitCurrency::BTC,
699            kind: Some(DeribitProductType::FutureCombo),
700            start_id: Some("100".to_string()),
701            end_id: Some("200".to_string()),
702            count: Some(50),
703            include_old: Some(true),
704            sorting: Some("asc".to_string()),
705        };
706        let value: Value = serde_json::to_value(&params).unwrap();
707        assert_eq!(
708            value,
709            json!({
710                "currency": "BTC",
711                "kind": "future_combo",
712                "start_id": "100",
713                "end_id": "200",
714                "count": 50,
715                "include_old": true,
716                "sorting": "asc",
717            }),
718        );
719    }
720
721    #[rstest]
722    fn test_get_last_trades_by_currency_params_with_kind_constructor() {
723        let params = GetLastTradesByCurrencyParams::with_kind(
724            DeribitCurrency::ETH,
725            DeribitProductType::OptionCombo,
726        );
727        let value: Value = serde_json::to_value(&params).unwrap();
728        assert_eq!(value, json!({"currency": "ETH", "kind": "option_combo"}));
729    }
730
731    #[rstest]
732    fn test_get_last_trades_by_currency_params_builder_partial() {
733        let params = GetLastTradesByCurrencyParams::builder()
734            .currency(DeribitCurrency::BTC)
735            .kind(DeribitProductType::FutureCombo)
736            .count(25_u32)
737            .include_old(true)
738            .build()
739            .unwrap();
740        let value: Value = serde_json::to_value(&params).unwrap();
741        assert_eq!(
742            value,
743            json!({
744                "currency": "BTC",
745                "kind": "future_combo",
746                "count": 25,
747                "include_old": true,
748            }),
749        );
750    }
751}