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")]
12#[derive(Default)]
13pub enum SettlementType {
14 #[default]
16 Settlement,
17 Delivery,
19 Bankruptcy,
21}
22
23#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
25pub struct Settlement {
26 #[serde(alias = "type")]
28 pub settlement_type: SettlementType,
29 pub timestamp: i64,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub instrument_name: Option<String>,
34 #[serde(alias = "position", skip_serializing_if = "Option::is_none")]
36 pub position_size: Option<f64>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub mark_price: Option<f64>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub index_price: Option<f64>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub profit_loss: Option<f64>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub funding: Option<f64>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub session_profit_loss: Option<f64>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub session_bankrupt_cy: Option<f64>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub session_tax: Option<f64>,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub session_tax_rate: Option<f64>,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub socialized_losses: Option<f64>,
65 #[serde(flatten)]
67 pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
68}
69
70impl Settlement {
71 pub fn new(settlement_type: SettlementType, timestamp: i64) -> Self {
73 Self {
74 settlement_type,
75 timestamp,
76 instrument_name: None,
77 position_size: None,
78 mark_price: None,
79 index_price: None,
80 profit_loss: None,
81 funding: None,
82 session_profit_loss: None,
83 session_bankrupt_cy: None,
84 session_tax: None,
85 session_tax_rate: None,
86 socialized_losses: None,
87 additional_fields: std::collections::HashMap::new(),
88 }
89 }
90
91 pub fn for_instrument(
93 settlement_type: SettlementType,
94 timestamp: i64,
95 instrument_name: String,
96 ) -> Self {
97 Self {
98 settlement_type,
99 timestamp,
100 instrument_name: Some(instrument_name),
101 position_size: None,
102 mark_price: None,
103 index_price: None,
104 profit_loss: None,
105 funding: None,
106 session_profit_loss: None,
107 session_bankrupt_cy: None,
108 session_tax: None,
109 session_tax_rate: None,
110 socialized_losses: None,
111 additional_fields: std::collections::HashMap::new(),
112 }
113 }
114
115 pub fn with_position(mut self, size: f64, mark_price: f64, index_price: f64) -> Self {
117 self.position_size = Some(size);
118 self.mark_price = Some(mark_price);
119 self.index_price = Some(index_price);
120 self
121 }
122
123 pub fn with_pnl(mut self, pnl: f64) -> Self {
125 self.profit_loss = Some(pnl);
126 self
127 }
128
129 pub fn with_funding(mut self, funding: f64) -> Self {
131 self.funding = Some(funding);
132 self
133 }
134
135 pub fn is_settlement(&self) -> bool {
137 matches!(self.settlement_type, SettlementType::Settlement)
138 }
139
140 pub fn is_delivery(&self) -> bool {
142 matches!(self.settlement_type, SettlementType::Delivery)
143 }
144
145 pub fn is_bankruptcy(&self) -> bool {
147 matches!(self.settlement_type, SettlementType::Bankruptcy)
148 }
149}
150
151impl Default for Settlement {
152 fn default() -> Self {
153 Self::new(SettlementType::default(), 0)
154 }
155}
156
157#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
159pub struct Settlements {
160 pub settlements: Vec<Settlement>,
162}
163
164impl Settlements {
165 pub fn new() -> Self {
167 Self {
168 settlements: Vec::new(),
169 }
170 }
171
172 pub fn add(&mut self, settlement: Settlement) {
174 self.settlements.push(settlement);
175 }
176
177 pub fn by_type(&self, settlement_type: SettlementType) -> Vec<&Settlement> {
179 self.settlements
180 .iter()
181 .filter(|s| s.settlement_type == settlement_type)
182 .collect()
183 }
184
185 pub fn by_instrument(&self, instrument_name: &str) -> Vec<&Settlement> {
187 self.settlements
188 .iter()
189 .filter(|s| {
190 s.instrument_name
191 .as_ref()
192 .is_some_and(|name| name == instrument_name)
193 })
194 .collect()
195 }
196}
197
198impl Default for Settlements {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_settlement_creation() {
210 let settlement = Settlement::new(SettlementType::Settlement, 1640995200000);
211 assert_eq!(settlement.settlement_type, SettlementType::Settlement);
212 assert_eq!(settlement.timestamp, 1640995200000);
213 assert!(settlement.instrument_name.is_none());
214 }
215
216 #[test]
217 fn test_settlement_builder() {
218 let settlement = Settlement::for_instrument(
219 SettlementType::Delivery,
220 1640995200000,
221 "BTC-25MAR23".to_string(),
222 )
223 .with_position(1.5, 45000.0, 44950.0)
224 .with_pnl(75.0);
225
226 assert_eq!(settlement.settlement_type, SettlementType::Delivery);
227 assert_eq!(settlement.instrument_name, Some("BTC-25MAR23".to_string()));
228 assert_eq!(settlement.position_size, Some(1.5));
229 assert_eq!(settlement.profit_loss, Some(75.0));
230 }
231
232 #[test]
233 fn test_settlement_type_checks() {
234 let settlement = Settlement::new(SettlementType::Settlement, 0);
235 assert!(settlement.is_settlement());
236 assert!(!settlement.is_delivery());
237 assert!(!settlement.is_bankruptcy());
238 }
239
240 #[test]
241 fn test_settlements_collection() {
242 let mut settlements = Settlements::new();
243 settlements.add(Settlement::new(SettlementType::Settlement, 1000));
244 settlements.add(Settlement::new(SettlementType::Delivery, 2000));
245
246 assert_eq!(settlements.settlements.len(), 2);
247 assert_eq!(settlements.by_type(SettlementType::Settlement).len(), 1);
248 assert_eq!(settlements.by_type(SettlementType::Delivery).len(), 1);
249 }
250
251 #[test]
252 fn test_serde() {
253 let settlement = Settlement::for_instrument(
254 SettlementType::Settlement,
255 1640995200000,
256 "BTC-PERPETUAL".to_string(),
257 )
258 .with_funding(0.0001);
259
260 let json = serde_json::to_string(&settlement).unwrap();
261 let deserialized: Settlement = serde_json::from_str(&json).unwrap();
262 assert_eq!(settlement, deserialized);
263 }
264}