Skip to main content

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