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