1use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8use serde_with::skip_serializing_none;
9
10#[derive(DebugPretty, DisplaySimple, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13#[derive(Default)]
14pub enum SettlementType {
15 #[default]
17 Settlement,
18 Delivery,
20 Bankruptcy,
22}
23
24#[skip_serializing_none]
26#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
27pub struct Settlement {
28 #[serde(alias = "type")]
30 pub settlement_type: SettlementType,
31 pub timestamp: i64,
33 pub instrument_name: Option<String>,
35 #[serde(alias = "position")]
37 pub position_size: Option<f64>,
38 pub mark_price: Option<f64>,
40 pub index_price: Option<f64>,
42 pub profit_loss: Option<f64>,
44 pub funding: Option<f64>,
46 pub session_profit_loss: Option<f64>,
48 pub session_bankrupt_cy: Option<f64>,
50 pub session_tax: Option<f64>,
52 pub session_tax_rate: Option<f64>,
54 pub socialized_losses: Option<f64>,
56 #[serde(flatten)]
58 pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
59}
60
61impl Settlement {
62 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 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 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 pub fn with_pnl(mut self, pnl: f64) -> Self {
116 self.profit_loss = Some(pnl);
117 self
118 }
119
120 pub fn with_funding(mut self, funding: f64) -> Self {
122 self.funding = Some(funding);
123 self
124 }
125
126 pub fn is_settlement(&self) -> bool {
128 matches!(self.settlement_type, SettlementType::Settlement)
129 }
130
131 pub fn is_delivery(&self) -> bool {
133 matches!(self.settlement_type, SettlementType::Delivery)
134 }
135
136 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#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
150pub struct Settlements {
151 pub settlements: Vec<Settlement>,
153}
154
155impl Settlements {
156 pub fn new() -> Self {
158 Self {
159 settlements: Vec::new(),
160 }
161 }
162
163 pub fn add(&mut self, settlement: Settlement) {
165 self.settlements.push(settlement);
166 }
167
168 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 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}