1use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8
9#[derive(DebugPretty, DisplaySimple, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum SettlementType {
13 Settlement,
15 Delivery,
17 Bankruptcy,
19}
20
21impl Default for SettlementType {
22 fn default() -> Self {
23 Self::Settlement
24 }
25}
26
27#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
29pub struct Settlement {
30 #[serde(alias = "type")]
32 pub settlement_type: SettlementType,
33 pub timestamp: i64,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub instrument_name: Option<String>,
38 #[serde(alias = "position", skip_serializing_if = "Option::is_none")]
40 pub position_size: Option<f64>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub mark_price: Option<f64>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub index_price: Option<f64>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub profit_loss: Option<f64>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub funding: Option<f64>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub session_profit_loss: Option<f64>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub session_bankrupt_cy: Option<f64>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub session_tax: Option<f64>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub session_tax_rate: Option<f64>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub socialized_losses: Option<f64>,
69 #[serde(flatten)]
71 pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
72}
73
74impl Settlement {
75 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 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 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 pub fn with_pnl(mut self, pnl: f64) -> Self {
129 self.profit_loss = Some(pnl);
130 self
131 }
132
133 pub fn with_funding(mut self, funding: f64) -> Self {
135 self.funding = Some(funding);
136 self
137 }
138
139 pub fn is_settlement(&self) -> bool {
141 matches!(self.settlement_type, SettlementType::Settlement)
142 }
143
144 pub fn is_delivery(&self) -> bool {
146 matches!(self.settlement_type, SettlementType::Delivery)
147 }
148
149 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#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
163pub struct Settlements {
164 pub settlements: Vec<Settlement>,
166}
167
168impl Settlements {
169 pub fn new() -> Self {
171 Self {
172 settlements: Vec::new(),
173 }
174 }
175
176 pub fn add(&mut self, settlement: Settlement) {
178 self.settlements.push(settlement);
179 }
180
181 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 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}