Skip to main content

deribit_http/model/request/
position.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 7/3/26
5******************************************************************************/
6//! Position request models
7
8use pretty_simple_display::{DebugPretty, DisplaySimple};
9use serde::{Deserialize, Serialize};
10use serde_with::skip_serializing_none;
11
12/// A single trade specification for moving positions
13///
14/// Represents a position trade to be moved between subaccounts.
15#[skip_serializing_none]
16#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
17pub struct MovePositionTrade {
18    /// Instrument name (e.g., "BTC-PERPETUAL")
19    pub instrument_name: String,
20    /// Trade amount (USD for perpetual/inverse, base currency for options/linear)
21    pub amount: f64,
22    /// Price for trade (optional, defaults to average position price)
23    pub price: Option<f64>,
24}
25
26impl MovePositionTrade {
27    /// Create a new move position trade
28    pub fn new(instrument_name: impl Into<String>, amount: f64) -> Self {
29        Self {
30            instrument_name: instrument_name.into(),
31            amount,
32            price: None,
33        }
34    }
35
36    /// Create a new move position trade with a specific price
37    pub fn with_price(instrument_name: impl Into<String>, amount: f64, price: f64) -> Self {
38        Self {
39            instrument_name: instrument_name.into(),
40            amount,
41            price: Some(price),
42        }
43    }
44
45    /// Set the price for this trade
46    #[must_use]
47    pub fn price(mut self, price: f64) -> Self {
48        self.price = Some(price);
49        self
50    }
51}
52
53/// Request to move positions between subaccounts
54///
55/// Contains all parameters needed for the move_positions API call.
56#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
57pub struct MovePositionsRequest {
58    /// Currency symbol (e.g., "BTC", "ETH", "USDC")
59    pub currency: String,
60    /// Source subaccount ID
61    pub source_uid: i64,
62    /// Target subaccount ID
63    pub target_uid: i64,
64    /// List of trades for position move
65    pub trades: Vec<MovePositionTrade>,
66}
67
68impl MovePositionsRequest {
69    /// Create a new move positions request
70    pub fn new(
71        currency: impl Into<String>,
72        source_uid: i64,
73        target_uid: i64,
74        trades: Vec<MovePositionTrade>,
75    ) -> Self {
76        Self {
77            currency: currency.into(),
78            source_uid,
79            target_uid,
80            trades,
81        }
82    }
83
84    /// Add a trade to the request
85    pub fn add_trade(&mut self, trade: MovePositionTrade) {
86        self.trades.push(trade);
87    }
88
89    /// Get the number of trades
90    pub fn trade_count(&self) -> usize {
91        self.trades.len()
92    }
93
94    /// Check if the request has any trades
95    pub fn has_trades(&self) -> bool {
96        !self.trades.is_empty()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_move_position_trade_new() {
106        let trade = MovePositionTrade::new("BTC-PERPETUAL", 100.0);
107        assert_eq!(trade.instrument_name, "BTC-PERPETUAL");
108        assert_eq!(trade.amount, 100.0);
109        assert!(trade.price.is_none());
110    }
111
112    #[test]
113    fn test_move_position_trade_with_price() {
114        let trade = MovePositionTrade::with_price("BTC-PERPETUAL", 100.0, 35800.0);
115        assert_eq!(trade.instrument_name, "BTC-PERPETUAL");
116        assert_eq!(trade.amount, 100.0);
117        assert_eq!(trade.price, Some(35800.0));
118    }
119
120    #[test]
121    fn test_move_positions_request_new() {
122        let trades = vec![MovePositionTrade::new("BTC-PERPETUAL", 100.0)];
123        let request = MovePositionsRequest::new("BTC", 3, 23, trades);
124        assert_eq!(request.currency, "BTC");
125        assert_eq!(request.source_uid, 3);
126        assert_eq!(request.target_uid, 23);
127        assert_eq!(request.trade_count(), 1);
128    }
129
130    #[test]
131    fn test_move_positions_request_serialization() {
132        let trades = vec![
133            MovePositionTrade::with_price("BTC-PERPETUAL", 110.0, 35800.0),
134            MovePositionTrade::new("BTC-28JAN22-32500-C", 0.1),
135        ];
136        let request = MovePositionsRequest::new("BTC", 3, 23, trades);
137
138        let json = serde_json::to_string(&request).unwrap();
139        assert!(json.contains("BTC-PERPETUAL"));
140        assert!(json.contains("source_uid"));
141        assert!(json.contains("target_uid"));
142    }
143}