1use crate::{impl_json_debug_pretty, impl_json_display};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum SettlementType {
14 Settlement,
16 Delivery,
18 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#[derive(Clone, PartialEq, Serialize, Deserialize)]
33pub struct Settlement {
34 #[serde(alias = "type")]
36 pub settlement_type: SettlementType,
37 pub timestamp: i64,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub instrument_name: Option<String>,
42 #[serde(alias = "position", skip_serializing_if = "Option::is_none")]
44 pub position_size: Option<f64>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub mark_price: Option<f64>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub index_price: Option<f64>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub profit_loss: Option<f64>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub funding: Option<f64>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub session_profit_loss: Option<f64>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub session_bankrupt_cy: Option<f64>,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub session_tax: Option<f64>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub session_tax_rate: Option<f64>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub socialized_losses: Option<f64>,
73 #[serde(flatten)]
75 pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
76}
77
78impl Settlement {
79 pub fn new(settlement_type: SettlementType, timestamp: i64) -> Self {
81 Self {
82 settlement_type,
83 timestamp,
84 instrument_name: None,
85 position_size: None,
86 mark_price: None,
87 index_price: None,
88 profit_loss: None,
89 funding: None,
90 session_profit_loss: None,
91 session_bankrupt_cy: None,
92 session_tax: None,
93 session_tax_rate: None,
94 socialized_losses: None,
95 additional_fields: std::collections::HashMap::new(),
96 }
97 }
98
99 pub fn for_instrument(
101 settlement_type: SettlementType,
102 timestamp: i64,
103 instrument_name: String,
104 ) -> Self {
105 Self {
106 settlement_type,
107 timestamp,
108 instrument_name: Some(instrument_name),
109 position_size: None,
110 mark_price: None,
111 index_price: None,
112 profit_loss: None,
113 funding: None,
114 session_profit_loss: None,
115 session_bankrupt_cy: None,
116 session_tax: None,
117 session_tax_rate: None,
118 socialized_losses: None,
119 additional_fields: std::collections::HashMap::new(),
120 }
121 }
122
123 pub fn with_position(mut self, size: f64, mark_price: f64, index_price: f64) -> Self {
125 self.position_size = Some(size);
126 self.mark_price = Some(mark_price);
127 self.index_price = Some(index_price);
128 self
129 }
130
131 pub fn with_pnl(mut self, pnl: f64) -> Self {
133 self.profit_loss = Some(pnl);
134 self
135 }
136
137 pub fn with_funding(mut self, funding: f64) -> Self {
139 self.funding = Some(funding);
140 self
141 }
142
143 pub fn is_settlement(&self) -> bool {
145 matches!(self.settlement_type, SettlementType::Settlement)
146 }
147
148 pub fn is_delivery(&self) -> bool {
150 matches!(self.settlement_type, SettlementType::Delivery)
151 }
152
153 pub fn is_bankruptcy(&self) -> bool {
155 matches!(self.settlement_type, SettlementType::Bankruptcy)
156 }
157}
158
159impl Default for Settlement {
160 fn default() -> Self {
161 Self::new(SettlementType::default(), 0)
162 }
163}
164
165impl_json_display!(Settlement);
166impl_json_debug_pretty!(Settlement);
167
168#[derive(Clone, PartialEq, Serialize, Deserialize)]
170pub struct Settlements {
171 pub settlements: Vec<Settlement>,
173}
174
175impl Settlements {
176 pub fn new() -> Self {
178 Self {
179 settlements: Vec::new(),
180 }
181 }
182
183 pub fn add(&mut self, settlement: Settlement) {
185 self.settlements.push(settlement);
186 }
187
188 pub fn by_type(&self, settlement_type: SettlementType) -> Vec<&Settlement> {
190 self.settlements
191 .iter()
192 .filter(|s| s.settlement_type == settlement_type)
193 .collect()
194 }
195
196 pub fn by_instrument(&self, instrument_name: &str) -> Vec<&Settlement> {
198 self.settlements
199 .iter()
200 .filter(|s| {
201 s.instrument_name
202 .as_ref()
203 .is_some_and(|name| name == instrument_name)
204 })
205 .collect()
206 }
207}
208
209impl Default for Settlements {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl_json_display!(Settlements);
216impl_json_debug_pretty!(Settlements);
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_settlement_creation() {
224 let settlement = Settlement::new(SettlementType::Settlement, 1640995200000);
225 assert_eq!(settlement.settlement_type, SettlementType::Settlement);
226 assert_eq!(settlement.timestamp, 1640995200000);
227 assert!(settlement.instrument_name.is_none());
228 }
229
230 #[test]
231 fn test_settlement_builder() {
232 let settlement = Settlement::for_instrument(
233 SettlementType::Delivery,
234 1640995200000,
235 "BTC-25MAR23".to_string(),
236 )
237 .with_position(1.5, 45000.0, 44950.0)
238 .with_pnl(75.0);
239
240 assert_eq!(settlement.settlement_type, SettlementType::Delivery);
241 assert_eq!(settlement.instrument_name, Some("BTC-25MAR23".to_string()));
242 assert_eq!(settlement.position_size, Some(1.5));
243 assert_eq!(settlement.profit_loss, Some(75.0));
244 }
245
246 #[test]
247 fn test_settlement_type_checks() {
248 let settlement = Settlement::new(SettlementType::Settlement, 0);
249 assert!(settlement.is_settlement());
250 assert!(!settlement.is_delivery());
251 assert!(!settlement.is_bankruptcy());
252 }
253
254 #[test]
255 fn test_settlements_collection() {
256 let mut settlements = Settlements::new();
257 settlements.add(Settlement::new(SettlementType::Settlement, 1000));
258 settlements.add(Settlement::new(SettlementType::Delivery, 2000));
259
260 assert_eq!(settlements.settlements.len(), 2);
261 assert_eq!(settlements.by_type(SettlementType::Settlement).len(), 1);
262 assert_eq!(settlements.by_type(SettlementType::Delivery).len(), 1);
263 }
264
265 #[test]
266 fn test_serde() {
267 let settlement = Settlement::for_instrument(
268 SettlementType::Settlement,
269 1640995200000,
270 "BTC-PERPETUAL".to_string(),
271 )
272 .with_funding(0.0001);
273
274 let json = serde_json::to_string(&settlement).unwrap();
275 let deserialized: Settlement = serde_json::from_str(&json).unwrap();
276 assert_eq!(settlement, deserialized);
277 }
278}