Skip to main content

deribit_http/model/
block_trade.rs

1//! Block trade models for the Deribit API.
2//!
3//! This module contains request and response types for block trade endpoints,
4//! including executing, verifying, and managing block trades.
5
6use serde::{Deserialize, Serialize};
7
8/// Role in a block trade (maker or taker).
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum BlockTradeRole {
12    /// Maker role in the block trade
13    Maker,
14    /// Taker role in the block trade
15    Taker,
16}
17
18impl std::fmt::Display for BlockTradeRole {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            Self::Maker => write!(f, "maker"),
22            Self::Taker => write!(f, "taker"),
23        }
24    }
25}
26
27/// Direction of a trade (buy or sell).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum TradeDirection {
31    /// Buy direction
32    Buy,
33    /// Sell direction
34    Sell,
35}
36
37impl std::fmt::Display for TradeDirection {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::Buy => write!(f, "buy"),
41            Self::Sell => write!(f, "sell"),
42        }
43    }
44}
45
46/// Individual trade item within a block trade.
47///
48/// Represents a single leg of a block trade with instrument, price, amount, and direction.
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct BlockTradeItem {
51    /// Instrument name (e.g., "BTC-PERPETUAL", "BTC-28MAR25-100000-C")
52    pub instrument_name: String,
53    /// Price for the trade in base currency
54    pub price: f64,
55    /// Trade size. For perpetual and inverse futures, amount is in USD units.
56    /// For options and linear futures, it is in the underlying base currency.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub amount: Option<f64>,
59    /// Direction of trade from the maker perspective
60    pub direction: TradeDirection,
61}
62
63impl BlockTradeItem {
64    /// Creates a new block trade item.
65    ///
66    /// # Arguments
67    ///
68    /// * `instrument_name` - The instrument name
69    /// * `price` - The trade price
70    /// * `amount` - The trade amount (optional)
71    /// * `direction` - The trade direction
72    #[must_use]
73    pub fn new(
74        instrument_name: impl Into<String>,
75        price: f64,
76        amount: Option<f64>,
77        direction: TradeDirection,
78    ) -> Self {
79        Self {
80            instrument_name: instrument_name.into(),
81            price,
82            amount,
83            direction,
84        }
85    }
86}
87
88/// Request parameters for executing a block trade.
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct ExecuteBlockTradeRequest {
91    /// Timestamp shared with other party, in milliseconds since UNIX epoch
92    pub timestamp: u64,
93    /// Nonce shared with other party
94    pub nonce: String,
95    /// Role in the trade (maker or taker)
96    pub role: BlockTradeRole,
97    /// List of trades for the block trade
98    pub trades: Vec<BlockTradeItem>,
99    /// Signature from the counterparty generated by verify_block_trade
100    pub counterparty_signature: String,
101}
102
103/// Request parameters for verifying a block trade.
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct VerifyBlockTradeRequest {
106    /// Timestamp shared with other party, in milliseconds since UNIX epoch
107    pub timestamp: u64,
108    /// Nonce shared with other party
109    pub nonce: String,
110    /// Role in the trade (maker or taker)
111    pub role: BlockTradeRole,
112    /// List of trades for the block trade
113    pub trades: Vec<BlockTradeItem>,
114}
115
116/// Request parameters for simulating a block trade.
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct SimulateBlockTradeRequest {
119    /// Role in the trade (maker or taker), optional
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub role: Option<BlockTradeRole>,
122    /// List of trades for the block trade
123    pub trades: Vec<BlockTradeItem>,
124}
125
126/// Request parameters for getting block trades with filters.
127#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
128pub struct GetBlockTradesRequest {
129    /// Currency to filter by (e.g., "BTC", "ETH")
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub currency: Option<String>,
132    /// Number of requested items (default: 20)
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub count: Option<u32>,
135    /// Continuation token for pagination
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub continuation: Option<String>,
138    /// Filter trades from this timestamp (milliseconds since UNIX epoch)
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub start_timestamp: Option<u64>,
141    /// Filter trades up to this timestamp (milliseconds since UNIX epoch)
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub end_timestamp: Option<u64>,
144}
145
146/// Request parameters for getting block trade requests.
147#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
148pub struct GetBlockTradeRequestsParams {
149    /// Broker code to filter by (optional)
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub broker_code: Option<String>,
152}
153
154/// Detailed trade information within a block trade result.
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct BlockTradeTradeInfo {
157    /// Unique trade identifier
158    pub trade_id: String,
159    /// Trade sequence number
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub trade_seq: Option<u64>,
162    /// Timestamp in milliseconds since UNIX epoch
163    pub timestamp: u64,
164    /// Direction of the tick (0=Plus, 1=Zero-Plus, 2=Minus, 3=Zero-Minus)
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub tick_direction: Option<i32>,
167    /// Order state
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub state: Option<String>,
170    /// Whether this was a reduce-only trade
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub reduce_only: Option<bool>,
173    /// Trade price
174    pub price: f64,
175    /// Whether this was a post-only order
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub post_only: Option<bool>,
178    /// Order type (limit, market, liquidation)
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub order_type: Option<String>,
181    /// Order ID
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub order_id: Option<String>,
184    /// Matching ID (always null for block trades)
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub matching_id: Option<String>,
187    /// Mark price at time of trade
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub mark_price: Option<f64>,
190    /// Liquidity indicator (M=maker, T=taker)
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub liquidity: Option<String>,
193    /// Implied volatility (options only)
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub iv: Option<f64>,
196    /// Instrument name
197    pub instrument_name: String,
198    /// Index price at time of trade
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub index_price: Option<f64>,
201    /// Fee currency
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub fee_currency: Option<String>,
204    /// Fee amount
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub fee: Option<f64>,
207    /// Trade direction
208    pub direction: TradeDirection,
209    /// Block trade ID
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub block_trade_id: Option<String>,
212    /// Trade amount
213    pub amount: f64,
214    /// Underlying price (options only)
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub underlying_price: Option<f64>,
217    /// Whether trade was created via API
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub api: Option<bool>,
220    /// Advanced order type (usd or implv, options only)
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub advanced: Option<String>,
223    /// User-defined label
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub label: Option<String>,
226    /// Whether MMP was active
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub mmp: Option<bool>,
229    /// Quote ID (for mass_quote orders)
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub quote_id: Option<String>,
232    /// Combo ID (for combo trades)
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub combo_id: Option<String>,
235    /// Profit and loss in base currency
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub profit_loss: Option<f64>,
238    /// Trade size in contract units
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub contracts: Option<f64>,
241    /// Block RFQ quote ID
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub block_rfq_quote_id: Option<u64>,
244}
245
246/// Block trade information returned from get_block_trade and get_block_trades.
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248pub struct BlockTrade {
249    /// Block trade ID
250    pub id: String,
251    /// Timestamp in milliseconds since UNIX epoch
252    pub timestamp: u64,
253    /// List of trades in this block trade
254    pub trades: Vec<BlockTradeTradeInfo>,
255    /// Name of the application that executed the block trade (optional)
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub app_name: Option<String>,
258    /// Broker code associated with the trade
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub broker_code: Option<String>,
261    /// Broker name associated with the trade
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub broker_name: Option<String>,
264}
265
266/// Result from executing a block trade.
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct BlockTradeResult {
269    /// Block trade ID
270    pub id: String,
271    /// Timestamp in milliseconds since UNIX epoch
272    pub timestamp: u64,
273    /// List of executed trades
274    pub trades: Vec<BlockTradeTradeInfo>,
275}
276
277/// Signature returned from verify_block_trade.
278#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
279pub struct BlockTradeSignature {
280    /// Block trade signature, valid for 5 minutes around the given timestamp
281    pub signature: String,
282}
283
284/// Pending block trade request information.
285#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct BlockTradeRequest {
287    /// Timestamp in milliseconds since UNIX epoch
288    pub timestamp: u64,
289    /// Nonce shared with other party
290    pub nonce: String,
291    /// Role expected for this request
292    pub role: BlockTradeRole,
293    /// List of trades in the request
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub trades: Option<Vec<BlockTradeItem>>,
296    /// Broker code (for broker requests)
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub broker_code: Option<String>,
299    /// Counterparty user ID
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub counterparty_user_id: Option<u64>,
302    /// State of the request
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub state: Option<String>,
305}
306
307/// Response for get_block_trades containing a list of block trades.
308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
309pub struct GetBlockTradesResponse {
310    /// List of block trades
311    pub block_trades: Vec<BlockTrade>,
312    /// Continuation token for pagination
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub continuation: Option<String>,
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    fn test_block_trade_role_serialization() {
323        assert_eq!(
324            serde_json::to_string(&BlockTradeRole::Maker).unwrap(),
325            r#""maker""#
326        );
327        assert_eq!(
328            serde_json::to_string(&BlockTradeRole::Taker).unwrap(),
329            r#""taker""#
330        );
331    }
332
333    #[test]
334    fn test_block_trade_role_deserialization() {
335        assert_eq!(
336            serde_json::from_str::<BlockTradeRole>(r#""maker""#).unwrap(),
337            BlockTradeRole::Maker
338        );
339        assert_eq!(
340            serde_json::from_str::<BlockTradeRole>(r#""taker""#).unwrap(),
341            BlockTradeRole::Taker
342        );
343    }
344
345    #[test]
346    fn test_trade_direction_serialization() {
347        assert_eq!(
348            serde_json::to_string(&TradeDirection::Buy).unwrap(),
349            r#""buy""#
350        );
351        assert_eq!(
352            serde_json::to_string(&TradeDirection::Sell).unwrap(),
353            r#""sell""#
354        );
355    }
356
357    #[test]
358    fn test_block_trade_item_serialization() {
359        let item = BlockTradeItem::new("BTC-PERPETUAL", 50000.0, Some(100.0), TradeDirection::Buy);
360        let json = serde_json::to_string(&item).unwrap();
361        assert!(json.contains("BTC-PERPETUAL"));
362        assert!(json.contains("50000"));
363        assert!(json.contains("buy"));
364    }
365
366    #[test]
367    fn test_execute_block_trade_request_serialization() {
368        let request = ExecuteBlockTradeRequest {
369            timestamp: 1565172650935,
370            nonce: "test_nonce".to_string(),
371            role: BlockTradeRole::Maker,
372            trades: vec![BlockTradeItem::new(
373                "BTC-PERPETUAL",
374                50000.0,
375                Some(100.0),
376                TradeDirection::Buy,
377            )],
378            counterparty_signature: "sig123".to_string(),
379        };
380        let json = serde_json::to_string(&request).unwrap();
381        assert!(json.contains("1565172650935"));
382        assert!(json.contains("test_nonce"));
383        assert!(json.contains("maker"));
384        assert!(json.contains("sig123"));
385    }
386
387    #[test]
388    fn test_verify_block_trade_request_serialization() {
389        let request = VerifyBlockTradeRequest {
390            timestamp: 1565172650935,
391            nonce: "test_nonce".to_string(),
392            role: BlockTradeRole::Taker,
393            trades: vec![BlockTradeItem::new(
394                "ETH-PERPETUAL",
395                3000.0,
396                Some(50.0),
397                TradeDirection::Sell,
398            )],
399        };
400        let json = serde_json::to_string(&request).unwrap();
401        assert!(json.contains("taker"));
402        assert!(json.contains("ETH-PERPETUAL"));
403    }
404
405    #[test]
406    fn test_block_trade_signature_deserialization() {
407        let json = r#"{"signature":"1565172710935.1ESE83qh.abc123"}"#;
408        let sig: BlockTradeSignature = serde_json::from_str(json).unwrap();
409        assert_eq!(sig.signature, "1565172710935.1ESE83qh.abc123");
410    }
411
412    #[test]
413    fn test_block_trade_deserialization() {
414        let json = r#"{
415            "id": "61",
416            "timestamp": 1565089523720,
417            "trades": [
418                {
419                    "trade_id": "92437",
420                    "timestamp": 1565089523719,
421                    "price": 0.0001,
422                    "instrument_name": "BTC-9AUG19-10250-C",
423                    "direction": "sell",
424                    "amount": 10
425                }
426            ],
427            "broker_code": "ABC123"
428        }"#;
429        let trade: BlockTrade = serde_json::from_str(json).unwrap();
430        assert_eq!(trade.id, "61");
431        assert_eq!(trade.trades.len(), 1);
432        assert_eq!(trade.broker_code, Some("ABC123".to_string()));
433    }
434
435    #[test]
436    fn test_get_block_trades_request_default() {
437        let request = GetBlockTradesRequest::default();
438        assert!(request.currency.is_none());
439        assert!(request.count.is_none());
440        assert!(request.continuation.is_none());
441    }
442
443    #[test]
444    fn test_simulate_block_trade_request_serialization() {
445        let request = SimulateBlockTradeRequest {
446            role: Some(BlockTradeRole::Maker),
447            trades: vec![BlockTradeItem::new(
448                "BTC-PERPETUAL",
449                50000.0,
450                Some(40.0),
451                TradeDirection::Buy,
452            )],
453        };
454        let json = serde_json::to_string(&request).unwrap();
455        assert!(json.contains("maker"));
456        assert!(json.contains("BTC-PERPETUAL"));
457    }
458}