Skip to main content

deribit_http/model/response/
subaccount.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 7/3/26
5******************************************************************************/
6//! Subaccount response models
7
8use crate::model::position::Position;
9use pretty_simple_display::{DebugPretty, DisplaySimple};
10use serde::{Deserialize, Serialize};
11use serde_with::skip_serializing_none;
12
13/// Subaccount details with positions
14///
15/// Contains position details for a specific subaccount, including
16/// all open positions and optionally open orders.
17#[skip_serializing_none]
18#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
19pub struct SubaccountDetails {
20    /// Subaccount ID
21    pub uid: i64,
22    /// List of positions for this subaccount
23    pub positions: Vec<Position>,
24    /// Open orders (optional, when with_open_orders=true)
25    pub open_orders: Option<Vec<serde_json::Value>>,
26}
27
28impl SubaccountDetails {
29    /// Create a new subaccount details instance
30    pub fn new(uid: i64, positions: Vec<Position>) -> Self {
31        Self {
32            uid,
33            positions,
34            open_orders: None,
35        }
36    }
37
38    /// Create a new subaccount details instance with open orders
39    pub fn with_open_orders(
40        uid: i64,
41        positions: Vec<Position>,
42        open_orders: Vec<serde_json::Value>,
43    ) -> Self {
44        Self {
45            uid,
46            positions,
47            open_orders: Some(open_orders),
48        }
49    }
50
51    /// Check if this subaccount has any positions
52    pub fn has_positions(&self) -> bool {
53        !self.positions.is_empty()
54    }
55
56    /// Get the number of positions
57    pub fn position_count(&self) -> usize {
58        self.positions.len()
59    }
60
61    /// Check if this subaccount has open orders
62    pub fn has_open_orders(&self) -> bool {
63        self.open_orders
64            .as_ref()
65            .map(|orders: &Vec<serde_json::Value>| !orders.is_empty())
66            .unwrap_or(false)
67    }
68
69    /// Get the number of open orders
70    pub fn open_orders_count(&self) -> usize {
71        self.open_orders
72            .as_ref()
73            .map(|orders: &Vec<serde_json::Value>| orders.len())
74            .unwrap_or(0)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::model::types::Direction;
82
83    fn create_test_position() -> Position {
84        Position {
85            average_price: 49571.3,
86            average_price_usd: None,
87            delta: Some(0.004152776),
88            direction: Direction::Buy,
89            estimated_liquidation_price: Some(2.33),
90            floating_profit_loss: Some(-0.00003451),
91            floating_profit_loss_usd: None,
92            gamma: None,
93            index_price: Some(47897.12),
94            initial_margin: Some(0.000122508),
95            instrument_name: "BTC-PERPETUAL".to_string(),
96            interest_value: None,
97            kind: Some("future".to_string()),
98            leverage: Some(34),
99            maintenance_margin: Some(0.000089286),
100            mark_price: Some(48160.55),
101            open_orders_margin: Some(0.0),
102            realized_funding: Some(-8.8e-7),
103            realized_profit_loss: Some(-8.79e-7),
104            settlement_price: Some(48150.36),
105            size: 200.0,
106            size_currency: Some(0.004152776),
107            theta: None,
108            total_profit_loss: Some(-0.000118183),
109            vega: None,
110            unrealized_profit_loss: None,
111        }
112    }
113
114    #[test]
115    fn test_subaccount_details_new() {
116        let positions = vec![create_test_position()];
117        let details = SubaccountDetails::new(3, positions);
118
119        assert_eq!(details.uid, 3);
120        assert_eq!(details.position_count(), 1);
121        assert!(details.has_positions());
122        assert!(!details.has_open_orders());
123        assert_eq!(details.open_orders_count(), 0);
124    }
125
126    #[test]
127    fn test_subaccount_details_empty() {
128        let details = SubaccountDetails::new(10, vec![]);
129
130        assert_eq!(details.uid, 10);
131        assert_eq!(details.position_count(), 0);
132        assert!(!details.has_positions());
133    }
134
135    #[test]
136    fn test_subaccount_details_deserialization() {
137        let json = r#"{
138            "uid": 3,
139            "positions": [
140                {
141                    "total_profit_loss": -0.000118183,
142                    "size_currency": 0.004152776,
143                    "size": 200,
144                    "settlement_price": 48150.36,
145                    "realized_profit_loss": -8.79e-7,
146                    "realized_funding": -8.8e-7,
147                    "open_orders_margin": 0,
148                    "mark_price": 48160.55,
149                    "maintenance_margin": 0.000089286,
150                    "leverage": 34,
151                    "kind": "future",
152                    "instrument_name": "BTC-PERPETUAL",
153                    "initial_margin": 0.000122508,
154                    "index_price": 47897.12,
155                    "floating_profit_loss": -0.00003451,
156                    "estimated_liquidation_price": 2.33,
157                    "direction": "buy",
158                    "delta": 0.004152776,
159                    "average_price": 49571.3
160                }
161            ]
162        }"#;
163
164        let details: SubaccountDetails = serde_json::from_str(json).unwrap();
165        assert_eq!(details.uid, 3);
166        assert_eq!(details.position_count(), 1);
167        assert_eq!(details.positions[0].instrument_name, "BTC-PERPETUAL");
168    }
169}