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