deribit_base/model/
settlement.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6
7use crate::{impl_json_debug_pretty, impl_json_display};
8use serde::{Deserialize, Serialize};
9
10/// Settlement event types
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum SettlementType {
14    /// Regular settlement event
15    Settlement,
16    /// Delivery event for futures/options
17    Delivery,
18    /// Bankruptcy event
19    Bankruptcy,
20}
21
22impl Default for SettlementType {
23    fn default() -> Self {
24        Self::Settlement
25    }
26}
27
28impl_json_display!(SettlementType);
29impl_json_debug_pretty!(SettlementType);
30
31/// Settlement event information
32#[derive(Clone, PartialEq, Serialize, Deserialize)]
33pub struct Settlement {
34    /// Type of settlement event
35    #[serde(alias = "type")]
36    pub settlement_type: SettlementType,
37    /// Timestamp of the settlement event (milliseconds since Unix epoch)
38    pub timestamp: i64,
39    /// Instrument name (settlement and delivery only)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub instrument_name: Option<String>,
42    /// Position size in quote currency (settlement and delivery only)
43    #[serde(alias = "position", skip_serializing_if = "Option::is_none")]
44    pub position_size: Option<f64>,
45    /// Mark price at settlement time in quote currency (settlement and delivery only)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub mark_price: Option<f64>,
48    /// Underlying index price at time of event in quote currency (settlement and delivery only)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub index_price: Option<f64>,
51    /// Profit and loss in base currency (settlement and delivery only)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub profit_loss: Option<f64>,
54    /// Funding in base currency (settlement for perpetual product only)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub funding: Option<f64>,
57    // Additional fields from deribit-http types.rs
58    /// Session profit and loss (optional)
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub session_profit_loss: Option<f64>,
61    /// Session bankruptcy (optional)
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub session_bankrupt_cy: Option<f64>,
64    /// Session tax (optional)
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub session_tax: Option<f64>,
67    /// Session tax rate (optional)
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub session_tax_rate: Option<f64>,
70    /// Socialized losses (optional)
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub socialized_losses: Option<f64>,
73    /// Additional fields that might be present in the API response
74    #[serde(flatten)]
75    pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
76}
77
78impl Settlement {
79    /// Create a new settlement event
80    pub fn new(settlement_type: SettlementType, timestamp: i64) -> Self {
81        Self {
82            settlement_type,
83            timestamp,
84            instrument_name: None,
85            position_size: None,
86            mark_price: None,
87            index_price: None,
88            profit_loss: None,
89            funding: None,
90            session_profit_loss: None,
91            session_bankrupt_cy: None,
92            session_tax: None,
93            session_tax_rate: None,
94            socialized_losses: None,
95            additional_fields: std::collections::HashMap::new(),
96        }
97    }
98
99    /// Create a settlement event for an instrument
100    pub fn for_instrument(
101        settlement_type: SettlementType,
102        timestamp: i64,
103        instrument_name: String,
104    ) -> Self {
105        Self {
106            settlement_type,
107            timestamp,
108            instrument_name: Some(instrument_name),
109            position_size: None,
110            mark_price: None,
111            index_price: None,
112            profit_loss: None,
113            funding: None,
114            session_profit_loss: None,
115            session_bankrupt_cy: None,
116            session_tax: None,
117            session_tax_rate: None,
118            socialized_losses: None,
119            additional_fields: std::collections::HashMap::new(),
120        }
121    }
122
123    /// Set position details
124    pub fn with_position(mut self, size: f64, mark_price: f64, index_price: f64) -> Self {
125        self.position_size = Some(size);
126        self.mark_price = Some(mark_price);
127        self.index_price = Some(index_price);
128        self
129    }
130
131    /// Set profit/loss
132    pub fn with_pnl(mut self, pnl: f64) -> Self {
133        self.profit_loss = Some(pnl);
134        self
135    }
136
137    /// Set funding (for perpetuals)
138    pub fn with_funding(mut self, funding: f64) -> Self {
139        self.funding = Some(funding);
140        self
141    }
142
143    /// Check if this is a settlement event
144    pub fn is_settlement(&self) -> bool {
145        matches!(self.settlement_type, SettlementType::Settlement)
146    }
147
148    /// Check if this is a delivery event
149    pub fn is_delivery(&self) -> bool {
150        matches!(self.settlement_type, SettlementType::Delivery)
151    }
152
153    /// Check if this is a bankruptcy event
154    pub fn is_bankruptcy(&self) -> bool {
155        matches!(self.settlement_type, SettlementType::Bankruptcy)
156    }
157}
158
159impl Default for Settlement {
160    fn default() -> Self {
161        Self::new(SettlementType::default(), 0)
162    }
163}
164
165impl_json_display!(Settlement);
166impl_json_debug_pretty!(Settlement);
167
168/// Collection of settlements
169#[derive(Clone, PartialEq, Serialize, Deserialize)]
170pub struct Settlements {
171    /// List of settlement events
172    pub settlements: Vec<Settlement>,
173}
174
175impl Settlements {
176    /// Create a new settlements collection
177    pub fn new() -> Self {
178        Self {
179            settlements: Vec::new(),
180        }
181    }
182
183    /// Add a settlement to the collection
184    pub fn add(&mut self, settlement: Settlement) {
185        self.settlements.push(settlement);
186    }
187
188    /// Get settlements by type
189    pub fn by_type(&self, settlement_type: SettlementType) -> Vec<&Settlement> {
190        self.settlements
191            .iter()
192            .filter(|s| s.settlement_type == settlement_type)
193            .collect()
194    }
195
196    /// Get settlements for a specific instrument
197    pub fn by_instrument(&self, instrument_name: &str) -> Vec<&Settlement> {
198        self.settlements
199            .iter()
200            .filter(|s| {
201                s.instrument_name
202                    .as_ref()
203                    .is_some_and(|name| name == instrument_name)
204            })
205            .collect()
206    }
207}
208
209impl Default for Settlements {
210    fn default() -> Self {
211        Self::new()
212    }
213}
214
215impl_json_display!(Settlements);
216impl_json_debug_pretty!(Settlements);
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_settlement_creation() {
224        let settlement = Settlement::new(SettlementType::Settlement, 1640995200000);
225        assert_eq!(settlement.settlement_type, SettlementType::Settlement);
226        assert_eq!(settlement.timestamp, 1640995200000);
227        assert!(settlement.instrument_name.is_none());
228    }
229
230    #[test]
231    fn test_settlement_builder() {
232        let settlement = Settlement::for_instrument(
233            SettlementType::Delivery,
234            1640995200000,
235            "BTC-25MAR23".to_string(),
236        )
237        .with_position(1.5, 45000.0, 44950.0)
238        .with_pnl(75.0);
239
240        assert_eq!(settlement.settlement_type, SettlementType::Delivery);
241        assert_eq!(settlement.instrument_name, Some("BTC-25MAR23".to_string()));
242        assert_eq!(settlement.position_size, Some(1.5));
243        assert_eq!(settlement.profit_loss, Some(75.0));
244    }
245
246    #[test]
247    fn test_settlement_type_checks() {
248        let settlement = Settlement::new(SettlementType::Settlement, 0);
249        assert!(settlement.is_settlement());
250        assert!(!settlement.is_delivery());
251        assert!(!settlement.is_bankruptcy());
252    }
253
254    #[test]
255    fn test_settlements_collection() {
256        let mut settlements = Settlements::new();
257        settlements.add(Settlement::new(SettlementType::Settlement, 1000));
258        settlements.add(Settlement::new(SettlementType::Delivery, 2000));
259
260        assert_eq!(settlements.settlements.len(), 2);
261        assert_eq!(settlements.by_type(SettlementType::Settlement).len(), 1);
262        assert_eq!(settlements.by_type(SettlementType::Delivery).len(), 1);
263    }
264
265    #[test]
266    fn test_serde() {
267        let settlement = Settlement::for_instrument(
268            SettlementType::Settlement,
269            1640995200000,
270            "BTC-PERPETUAL".to_string(),
271        )
272        .with_funding(0.0001);
273
274        let json = serde_json::to_string(&settlement).unwrap();
275        let deserialized: Settlement = serde_json::from_str(&json).unwrap();
276        assert_eq!(settlement, deserialized);
277    }
278}