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