1use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum InstrumentKind {
13 Future,
15 Option,
17 Spot,
19 #[serde(rename = "future_combo")]
21 FutureCombo,
22 #[serde(rename = "option_combo")]
24 OptionCombo,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum OptionType {
31 Call,
33 Put,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "lowercase")]
40pub enum InstrumentType {
41 Linear,
43 Reversed,
45}
46
47#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
49pub struct Instrument {
50 pub instrument_name: String,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub price_index: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub kind: Option<InstrumentKind>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub currency: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub is_active: Option<bool>,
64 pub expiration_timestamp: Option<i64>,
66 pub strike: Option<f64>,
68 pub option_type: Option<OptionType>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub tick_size: Option<f64>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub min_trade_amount: Option<f64>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub contract_size: Option<f64>,
79 pub settlement_period: Option<String>,
81 pub instrument_type: Option<InstrumentType>,
83 pub quote_currency: Option<String>,
85 pub settlement_currency: Option<String>,
87 pub creation_timestamp: Option<i64>,
89 pub max_leverage: Option<f64>,
91 pub maker_commission: Option<f64>,
93 pub taker_commission: Option<f64>,
95 pub instrument_id: Option<u32>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub base_currency: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub counter_currency: Option<String>,
103}
104
105impl Instrument {
106 pub fn is_perpetual(&self) -> bool {
108 self.expiration_timestamp.is_none()
109 && self
110 .kind
111 .as_ref()
112 .is_some_and(|k| matches!(k, InstrumentKind::Future))
113 }
114
115 pub fn is_option(&self) -> bool {
117 self.kind
118 .as_ref()
119 .is_some_and(|k| matches!(k, InstrumentKind::Option | InstrumentKind::OptionCombo))
120 }
121
122 pub fn is_future(&self) -> bool {
124 self.kind
125 .as_ref()
126 .is_some_and(|k| matches!(k, InstrumentKind::Future | InstrumentKind::FutureCombo))
127 }
128
129 pub fn is_spot(&self) -> bool {
131 self.kind
132 .as_ref()
133 .is_some_and(|k| matches!(k, InstrumentKind::Spot))
134 }
135}
136
137#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
139pub struct IndexData {
140 pub btc: Option<f64>,
142 pub eth: Option<f64>,
144 pub usdc: Option<f64>,
146 pub usdt: Option<f64>,
148 pub eurr: Option<f64>,
150 pub edp: f64,
152}
153
154#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
156pub struct IndexPriceData {
157 pub index_price: f64,
159 pub estimated_delivery_price: f64,
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 fn create_test_instrument() -> Instrument {
168 Instrument {
169 instrument_name: "BTC-PERPETUAL".to_string(),
170 price_index: Some("btc_usd".to_string()),
171 kind: Some(InstrumentKind::Future),
172 currency: Some("BTC".to_string()),
173 is_active: Some(true),
174 expiration_timestamp: None, strike: None,
176 option_type: None,
177 tick_size: Some(0.5),
178 min_trade_amount: Some(10.0),
179 contract_size: Some(1.0),
180 settlement_period: Some("perpetual".to_string()),
181 instrument_type: Some(InstrumentType::Linear),
182 quote_currency: Some("USD".to_string()),
183 settlement_currency: Some("BTC".to_string()),
184 creation_timestamp: Some(1640995200000),
185 max_leverage: Some(100.0),
186 maker_commission: Some(0.0001),
187 taker_commission: Some(0.0005),
188 instrument_id: Some(12345),
189 base_currency: Some("BTC".to_string()),
190 counter_currency: Some("USD".to_string()),
191 }
192 }
193
194 fn create_test_option() -> Instrument {
195 Instrument {
196 instrument_name: "BTC-25DEC25-50000-C".to_string(),
197 price_index: Some("btc_usd".to_string()),
198 kind: Some(InstrumentKind::Option),
199 currency: Some("BTC".to_string()),
200 is_active: Some(true),
201 expiration_timestamp: Some(1735084800000),
202 strike: Some(50000.0),
203 option_type: Some(OptionType::Call),
204 tick_size: Some(0.0005),
205 min_trade_amount: Some(0.1),
206 contract_size: Some(1.0),
207 settlement_period: Some("week".to_string()),
208 instrument_type: Some(InstrumentType::Linear),
209 quote_currency: Some("USD".to_string()),
210 settlement_currency: Some("BTC".to_string()),
211 creation_timestamp: Some(1640995200000),
212 max_leverage: Some(10.0),
213 maker_commission: Some(0.0003),
214 taker_commission: Some(0.0003),
215 instrument_id: Some(67890),
216 base_currency: Some("BTC".to_string()),
217 counter_currency: Some("USD".to_string()),
218 }
219 }
220
221 #[test]
222 fn test_instrument_is_perpetual() {
223 let perpetual = create_test_instrument();
224 assert!(perpetual.is_perpetual());
225
226 let option = create_test_option();
227 assert!(!option.is_perpetual());
228
229 let mut future_with_expiry = create_test_instrument();
230 future_with_expiry.expiration_timestamp = Some(1735084800000);
231 assert!(!future_with_expiry.is_perpetual());
232 }
233
234 #[test]
235 fn test_instrument_is_option() {
236 let option = create_test_option();
237 assert!(option.is_option());
238
239 let perpetual = create_test_instrument();
240 assert!(!perpetual.is_option());
241
242 let mut option_combo = create_test_option();
243 option_combo.kind = Some(InstrumentKind::OptionCombo);
244 assert!(option_combo.is_option());
245 }
246
247 #[test]
248 fn test_instrument_is_future() {
249 let future = create_test_instrument();
250 assert!(future.is_future());
251
252 let option = create_test_option();
253 assert!(!option.is_future());
254
255 let mut future_combo = create_test_instrument();
256 future_combo.kind = Some(InstrumentKind::FutureCombo);
257 assert!(future_combo.is_future());
258 }
259
260 #[test]
261 fn test_instrument_is_spot() {
262 let mut spot = create_test_instrument();
263 spot.kind = Some(InstrumentKind::Spot);
264 assert!(spot.is_spot());
265
266 let future = create_test_instrument();
267 assert!(!future.is_spot());
268
269 let option = create_test_option();
270 assert!(!option.is_spot());
271 }
272
273 #[test]
274 fn test_instrument_kind_serialization() {
275 assert_eq!(
276 serde_json::to_string(&InstrumentKind::Future).unwrap(),
277 "\"future\""
278 );
279 assert_eq!(
280 serde_json::to_string(&InstrumentKind::Option).unwrap(),
281 "\"option\""
282 );
283 assert_eq!(
284 serde_json::to_string(&InstrumentKind::Spot).unwrap(),
285 "\"spot\""
286 );
287 assert_eq!(
288 serde_json::to_string(&InstrumentKind::FutureCombo).unwrap(),
289 "\"future_combo\""
290 );
291 assert_eq!(
292 serde_json::to_string(&InstrumentKind::OptionCombo).unwrap(),
293 "\"option_combo\""
294 );
295 }
296
297 #[test]
298 fn test_option_type_serialization() {
299 assert_eq!(
300 serde_json::to_string(&OptionType::Call).unwrap(),
301 "\"call\""
302 );
303 assert_eq!(serde_json::to_string(&OptionType::Put).unwrap(), "\"put\"");
304 }
305
306 #[test]
307 fn test_instrument_type_serialization() {
308 assert_eq!(
309 serde_json::to_string(&InstrumentType::Linear).unwrap(),
310 "\"linear\""
311 );
312 assert_eq!(
313 serde_json::to_string(&InstrumentType::Reversed).unwrap(),
314 "\"reversed\""
315 );
316 }
317
318 #[test]
319 fn test_instrument_serialization() {
320 let instrument = create_test_instrument();
321 let json = serde_json::to_string(&instrument).unwrap();
322 let deserialized: Instrument = serde_json::from_str(&json).unwrap();
323 assert_eq!(instrument.instrument_name, deserialized.instrument_name);
324 assert_eq!(instrument.kind, deserialized.kind);
325 }
326
327 #[test]
328 fn test_index_data_creation() {
329 let index_data = IndexData {
330 btc: Some(0.5),
331 eth: Some(0.3),
332 usdc: Some(0.1),
333 usdt: Some(0.05),
334 eurr: Some(0.05),
335 edp: 50000.0,
336 };
337
338 assert_eq!(index_data.btc, Some(0.5));
339 assert_eq!(index_data.edp, 50000.0);
340 }
341
342 #[test]
343 fn test_index_price_data_creation() {
344 let index_price_data = IndexPriceData {
345 index_price: 50000.0,
346 estimated_delivery_price: 50100.0,
347 };
348
349 assert_eq!(index_price_data.index_price, 50000.0);
350 assert_eq!(index_price_data.estimated_delivery_price, 50100.0);
351 }
352
353 #[test]
354 fn test_debug_and_display_implementations() {
355 let instrument = create_test_instrument();
356 let debug_str = format!("{:?}", instrument);
357 let display_str = format!("{}", instrument);
358
359 assert!(debug_str.contains("BTC-PERPETUAL"));
360 assert!(display_str.contains("BTC-PERPETUAL"));
361 }
362}