1use std::str::FromStr;
17
18use chrono::{DateTime, Utc};
19use nautilus_core::UnixNanos;
20use nautilus_model::{
21 identifiers::Symbol,
22 instruments::InstrumentAny,
23 types::{Currency, Price, Quantity},
24};
25use rust_decimal::Decimal;
26use rust_decimal_macros::dec;
27
28use super::{
29 instruments::{
30 create_crypto_future, create_crypto_option, create_crypto_perpetual, create_currency_pair,
31 get_currency,
32 },
33 models::InstrumentInfo,
34};
35use crate::{
36 enums::InstrumentType,
37 parse::{normalize_instrument_id, parse_instrument_id},
38};
39
40#[must_use]
41pub fn parse_instrument_any(
42 info: InstrumentInfo,
43 effective: Option<UnixNanos>,
44 ts_init: Option<UnixNanos>,
45 normalize_symbols: bool,
46) -> Vec<InstrumentAny> {
47 match info.instrument_type {
48 InstrumentType::Spot => parse_spot_instrument(info, effective, ts_init, normalize_symbols),
49 InstrumentType::Perpetual => {
50 parse_perp_instrument(info, effective, ts_init, normalize_symbols)
51 }
52 InstrumentType::Future | InstrumentType::Combo => {
53 parse_future_instrument(info, effective, ts_init, normalize_symbols)
54 }
55 InstrumentType::Option => {
56 parse_option_instrument(info, effective, ts_init, normalize_symbols)
57 }
58 }
59}
60
61fn parse_spot_instrument(
62 info: InstrumentInfo,
63 effective: Option<UnixNanos>,
64 ts_init: Option<UnixNanos>,
65 normalize_symbols: bool,
66) -> Vec<InstrumentAny> {
67 let instrument_id = if normalize_symbols {
68 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
69 } else {
70 parse_instrument_id(&info.exchange, info.id)
71 };
72 let raw_symbol = Symbol::new(info.id);
73 let margin_init = dec!(0); let margin_maint = dec!(0); let mut price_increment = parse_price_increment(info.price_increment);
77 let base_currency = get_currency(info.base_currency.to_uppercase().as_str());
78 let mut size_increment = parse_spot_size_increment(info.amount_increment, base_currency);
79 let mut maker_fee = parse_fee_rate(info.maker_fee);
80 let mut taker_fee = parse_fee_rate(info.taker_fee);
81 let mut ts_event = info
82 .changes
83 .as_ref()
84 .and_then(|changes| changes.last().map(|c| UnixNanos::from(c.until)))
85 .unwrap_or_else(|| UnixNanos::from(info.available_since));
86
87 let mut instruments = vec![create_currency_pair(
89 &info,
90 instrument_id,
91 raw_symbol,
92 price_increment,
93 size_increment,
94 margin_init,
95 margin_maint,
96 maker_fee,
97 taker_fee,
98 ts_event,
99 ts_init.unwrap_or(ts_event),
100 )];
101
102 if let Some(changes) = &info.changes {
103 let mut sorted_changes = changes.clone();
105 sorted_changes.sort_by(|a, b| b.until.cmp(&a.until));
106
107 if let Some(effective_time) = effective {
108 for (i, change) in sorted_changes.iter().enumerate() {
110 if change.price_increment.is_none()
111 && change.amount_increment.is_none()
112 && change.contract_multiplier.is_none()
113 {
114 continue; }
116
117 ts_event = UnixNanos::from(change.until);
118
119 if ts_event < effective_time {
120 break; } else if i == sorted_changes.len() - 1 {
122 ts_event = UnixNanos::from(info.available_since);
123 }
124
125 price_increment = change
126 .price_increment
127 .map_or(price_increment, parse_price_increment);
128 size_increment = change.amount_increment.map_or(size_increment, |value| {
129 parse_spot_size_increment(value, base_currency)
130 });
131 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
132 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
133 }
134
135 instruments = vec![create_currency_pair(
137 &info,
138 instrument_id,
139 raw_symbol,
140 price_increment,
141 size_increment,
142 margin_init,
143 margin_maint,
144 maker_fee,
145 taker_fee,
146 ts_event,
147 ts_init.unwrap_or(ts_event),
148 )];
149 } else {
150 for (i, change) in sorted_changes.iter().enumerate() {
152 if change.price_increment.is_none()
153 && change.amount_increment.is_none()
154 && change.contract_multiplier.is_none()
155 {
156 continue; }
158
159 price_increment = change
160 .price_increment
161 .map_or(price_increment, parse_price_increment);
162 size_increment = change.amount_increment.map_or(size_increment, |value| {
163 parse_spot_size_increment(value, base_currency)
164 });
165 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
166 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
167
168 ts_event = if i == sorted_changes.len() - 1 {
170 UnixNanos::from(info.available_since)
171 } else {
172 UnixNanos::from(change.until)
173 };
174
175 instruments.push(create_currency_pair(
176 &info,
177 instrument_id,
178 raw_symbol,
179 price_increment,
180 size_increment,
181 margin_init,
182 margin_maint,
183 maker_fee,
184 taker_fee,
185 ts_event,
186 ts_init.unwrap_or(ts_event),
187 ));
188 }
189
190 instruments.reverse();
192 }
193 }
194
195 instruments
196}
197
198fn parse_perp_instrument(
199 info: InstrumentInfo,
200 effective: Option<UnixNanos>,
201 ts_init: Option<UnixNanos>,
202 normalize_symbols: bool,
203) -> Vec<InstrumentAny> {
204 let instrument_id = if normalize_symbols {
205 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
206 } else {
207 parse_instrument_id(&info.exchange, info.id)
208 };
209 let raw_symbol = Symbol::new(info.id);
210 let margin_init = dec!(0); let margin_maint = dec!(0); let mut price_increment = parse_price_increment(info.price_increment);
214 let mut size_increment = parse_size_increment(info.amount_increment);
215 let mut multiplier = parse_multiplier(info.contract_multiplier);
216 let mut maker_fee = parse_fee_rate(info.maker_fee);
217 let mut taker_fee = parse_fee_rate(info.taker_fee);
218 let mut ts_event = info
219 .changes
220 .as_ref()
221 .and_then(|changes| changes.last().map(|c| UnixNanos::from(c.until)))
222 .unwrap_or_else(|| UnixNanos::from(info.available_since));
223
224 let mut instruments = vec![create_crypto_perpetual(
226 &info,
227 instrument_id,
228 raw_symbol,
229 price_increment,
230 size_increment,
231 multiplier,
232 margin_init,
233 margin_maint,
234 maker_fee,
235 taker_fee,
236 ts_event,
237 ts_init.unwrap_or(ts_event),
238 )];
239
240 if let Some(changes) = &info.changes {
241 let mut sorted_changes = changes.clone();
243 sorted_changes.sort_by(|a, b| b.until.cmp(&a.until));
244
245 if let Some(effective_time) = effective {
246 for (i, change) in sorted_changes.iter().enumerate() {
248 if change.price_increment.is_none()
249 && change.amount_increment.is_none()
250 && change.contract_multiplier.is_none()
251 {
252 continue; }
254
255 ts_event = UnixNanos::from(change.until);
256
257 if ts_event < effective_time {
258 break; } else if i == sorted_changes.len() - 1 {
260 ts_event = UnixNanos::from(info.available_since);
261 }
262
263 price_increment = change
264 .price_increment
265 .map_or(price_increment, parse_price_increment);
266 size_increment = change
267 .amount_increment
268 .map_or(size_increment, parse_size_increment);
269 multiplier = match change.contract_multiplier {
270 Some(value) => Some(Quantity::from(value.to_string())),
271 None => multiplier,
272 };
273 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
274 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
275 }
276
277 instruments = vec![create_crypto_perpetual(
279 &info,
280 instrument_id,
281 raw_symbol,
282 price_increment,
283 size_increment,
284 multiplier,
285 margin_init,
286 margin_maint,
287 maker_fee,
288 taker_fee,
289 ts_event,
290 ts_init.unwrap_or(ts_event),
291 )];
292 } else {
293 for (i, change) in sorted_changes.iter().enumerate() {
295 if change.price_increment.is_none()
296 && change.amount_increment.is_none()
297 && change.contract_multiplier.is_none()
298 {
299 continue; }
301
302 price_increment = change
303 .price_increment
304 .map_or(price_increment, parse_price_increment);
305 size_increment = change
306 .amount_increment
307 .map_or(size_increment, parse_size_increment);
308 multiplier = match change.contract_multiplier {
309 Some(value) => Some(Quantity::from(value.to_string())),
310 None => multiplier,
311 };
312 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
313 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
314
315 ts_event = if i == sorted_changes.len() - 1 {
317 UnixNanos::from(info.available_since)
318 } else {
319 UnixNanos::from(change.until)
320 };
321
322 instruments.push(create_crypto_perpetual(
323 &info,
324 instrument_id,
325 raw_symbol,
326 price_increment,
327 size_increment,
328 multiplier,
329 margin_init,
330 margin_maint,
331 maker_fee,
332 taker_fee,
333 ts_event,
334 ts_init.unwrap_or(ts_event),
335 ));
336 }
337
338 instruments.reverse();
340 }
341 }
342
343 instruments
344}
345
346fn parse_future_instrument(
347 info: InstrumentInfo,
348 effective: Option<UnixNanos>,
349 ts_init: Option<UnixNanos>,
350 normalize_symbols: bool,
351) -> Vec<InstrumentAny> {
352 let instrument_id = if normalize_symbols {
353 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
354 } else {
355 parse_instrument_id(&info.exchange, info.id)
356 };
357 let raw_symbol = Symbol::new(info.id);
358 let activation = parse_datetime_to_unix_nanos(Some(info.available_since));
359 let expiration = parse_datetime_to_unix_nanos(info.expiry);
360 let margin_init = dec!(0); let margin_maint = dec!(0); let mut price_increment = parse_price_increment(info.price_increment);
364 let mut size_increment = parse_size_increment(info.amount_increment);
365 let mut multiplier = parse_multiplier(info.contract_multiplier);
366 let mut maker_fee = parse_fee_rate(info.maker_fee);
367 let mut taker_fee = parse_fee_rate(info.taker_fee);
368 let mut ts_event = info
369 .changes
370 .as_ref()
371 .and_then(|changes| changes.last().map(|c| UnixNanos::from(c.until)))
372 .unwrap_or_else(|| UnixNanos::from(info.available_since));
373
374 let mut instruments = vec![create_crypto_future(
376 &info,
377 instrument_id,
378 raw_symbol,
379 activation,
380 expiration,
381 price_increment,
382 size_increment,
383 multiplier,
384 margin_init,
385 margin_maint,
386 maker_fee,
387 taker_fee,
388 ts_event,
389 ts_init.unwrap_or(ts_event),
390 )];
391
392 if let Some(changes) = &info.changes {
393 let mut sorted_changes = changes.clone();
395 sorted_changes.sort_by(|a, b| b.until.cmp(&a.until));
396
397 if let Some(effective_time) = effective {
398 for (i, change) in sorted_changes.iter().enumerate() {
400 if change.price_increment.is_none()
401 && change.amount_increment.is_none()
402 && change.contract_multiplier.is_none()
403 {
404 continue; }
406
407 ts_event = UnixNanos::from(change.until);
408
409 if ts_event < effective_time {
410 break; } else if i == sorted_changes.len() - 1 {
412 ts_event = UnixNanos::from(info.available_since);
413 }
414
415 price_increment = change
416 .price_increment
417 .map_or(price_increment, parse_price_increment);
418 size_increment = change
419 .amount_increment
420 .map_or(size_increment, parse_size_increment);
421 multiplier = match change.contract_multiplier {
422 Some(value) => Some(Quantity::from(value.to_string())),
423 None => multiplier,
424 };
425 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
426 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
427 }
428
429 instruments = vec![create_crypto_future(
431 &info,
432 instrument_id,
433 raw_symbol,
434 activation,
435 expiration,
436 price_increment,
437 size_increment,
438 multiplier,
439 margin_init,
440 margin_maint,
441 maker_fee,
442 taker_fee,
443 ts_event,
444 ts_init.unwrap_or(ts_event),
445 )];
446 } else {
447 for (i, change) in sorted_changes.iter().enumerate() {
449 if change.price_increment.is_none()
450 && change.amount_increment.is_none()
451 && change.contract_multiplier.is_none()
452 {
453 continue; }
455
456 price_increment = change
457 .price_increment
458 .map_or(price_increment, parse_price_increment);
459 size_increment = change
460 .amount_increment
461 .map_or(size_increment, parse_size_increment);
462 multiplier = match change.contract_multiplier {
463 Some(value) => Some(Quantity::from(value.to_string())),
464 None => multiplier,
465 };
466 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
467 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
468
469 ts_event = if i == sorted_changes.len() - 1 {
471 UnixNanos::from(info.available_since)
472 } else {
473 UnixNanos::from(change.until)
474 };
475
476 instruments.push(create_crypto_future(
477 &info,
478 instrument_id,
479 raw_symbol,
480 activation,
481 expiration,
482 price_increment,
483 size_increment,
484 multiplier,
485 margin_init,
486 margin_maint,
487 maker_fee,
488 taker_fee,
489 ts_event,
490 ts_init.unwrap_or(ts_event),
491 ));
492 }
493
494 instruments.reverse();
496 }
497 }
498
499 instruments
500}
501
502fn parse_option_instrument(
503 info: InstrumentInfo,
504 effective: Option<UnixNanos>,
505 ts_init: Option<UnixNanos>,
506 normalize_symbols: bool,
507) -> Vec<InstrumentAny> {
508 let instrument_id = if normalize_symbols {
509 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
510 } else {
511 parse_instrument_id(&info.exchange, info.id)
512 };
513 let raw_symbol = Symbol::new(info.id);
514 let activation = parse_datetime_to_unix_nanos(Some(info.available_since));
515 let expiration = parse_datetime_to_unix_nanos(info.expiry);
516 let margin_init = dec!(0); let margin_maint = dec!(0); let mut price_increment = parse_price_increment(info.price_increment);
520 let mut size_increment = parse_size_increment(info.amount_increment);
521 let mut multiplier = parse_multiplier(info.contract_multiplier);
522 let mut maker_fee = parse_fee_rate(info.maker_fee);
523 let mut taker_fee = parse_fee_rate(info.taker_fee);
524 let mut ts_event = info
525 .changes
526 .as_ref()
527 .and_then(|changes| changes.last().map(|c| UnixNanos::from(c.until)))
528 .unwrap_or_else(|| UnixNanos::from(info.available_since));
529
530 let mut instruments = vec![create_crypto_option(
532 &info,
533 instrument_id,
534 raw_symbol,
535 activation,
536 expiration,
537 price_increment,
538 size_increment,
539 multiplier,
540 margin_init,
541 margin_maint,
542 maker_fee,
543 taker_fee,
544 ts_event,
545 ts_init.unwrap_or(ts_event),
546 )];
547
548 if let Some(changes) = &info.changes {
549 let mut sorted_changes = changes.clone();
551 sorted_changes.sort_by(|a, b| b.until.cmp(&a.until));
552
553 if let Some(effective_time) = effective {
554 for (i, change) in sorted_changes.iter().enumerate() {
556 if change.price_increment.is_none()
557 && change.amount_increment.is_none()
558 && change.contract_multiplier.is_none()
559 {
560 continue; }
562
563 ts_event = UnixNanos::from(change.until);
564
565 if ts_event < effective_time {
566 break; } else if i == sorted_changes.len() - 1 {
568 ts_event = UnixNanos::from(info.available_since);
569 }
570
571 price_increment = change
572 .price_increment
573 .map_or(price_increment, parse_price_increment);
574 size_increment = change
575 .amount_increment
576 .map_or(size_increment, parse_size_increment);
577 multiplier = match change.contract_multiplier {
578 Some(value) => Some(Quantity::from(value.to_string())),
579 None => multiplier,
580 };
581 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
582 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
583 }
584
585 instruments = vec![create_crypto_option(
587 &info,
588 instrument_id,
589 raw_symbol,
590 activation,
591 expiration,
592 price_increment,
593 size_increment,
594 multiplier,
595 margin_init,
596 margin_maint,
597 maker_fee,
598 taker_fee,
599 ts_event,
600 ts_init.unwrap_or(ts_event),
601 )];
602 } else {
603 for (i, change) in sorted_changes.iter().enumerate() {
605 if change.price_increment.is_none()
606 && change.amount_increment.is_none()
607 && change.contract_multiplier.is_none()
608 {
609 continue; }
611
612 price_increment = change
613 .price_increment
614 .map_or(price_increment, parse_price_increment);
615 size_increment = change
616 .amount_increment
617 .map_or(size_increment, parse_size_increment);
618 multiplier = match change.contract_multiplier {
619 Some(value) => Some(Quantity::from(value.to_string())),
620 None => multiplier,
621 };
622 maker_fee = change.maker_fee.map_or(maker_fee, parse_fee_rate);
623 taker_fee = change.taker_fee.map_or(taker_fee, parse_fee_rate);
624
625 ts_event = if i == sorted_changes.len() - 1 {
627 UnixNanos::from(info.available_since)
628 } else {
629 UnixNanos::from(change.until)
630 };
631
632 instruments.push(create_crypto_option(
633 &info,
634 instrument_id,
635 raw_symbol,
636 activation,
637 expiration,
638 price_increment,
639 size_increment,
640 multiplier,
641 margin_init,
642 margin_maint,
643 maker_fee,
644 taker_fee,
645 ts_event,
646 ts_init.unwrap_or(ts_event),
647 ));
648 }
649
650 instruments.reverse();
652 }
653 }
654
655 instruments
656}
657
658fn parse_price_increment(value: f64) -> Price {
660 Price::from(value.to_string())
661}
662
663fn parse_size_increment(value: f64) -> Quantity {
665 Quantity::from(value.to_string())
666}
667
668fn parse_spot_size_increment(value: f64, currency: Currency) -> Quantity {
670 if value == 0.0 {
671 let exponent = -i32::from(currency.precision);
672 Quantity::from(format!("{}", 10.0_f64.powi(exponent)))
673 } else {
674 Quantity::from(value.to_string())
675 }
676}
677
678fn parse_multiplier(value: Option<f64>) -> Option<Quantity> {
680 value.map(|x| Quantity::from(x.to_string()))
681}
682
683fn parse_fee_rate(value: f64) -> Decimal {
685 Decimal::from_str(&value.to_string()).expect("Invalid decimal value")
686}
687
688fn parse_datetime_to_unix_nanos(value: Option<DateTime<Utc>>) -> UnixNanos {
691 value
692 .map(|dt| UnixNanos::from(dt.timestamp_nanos_opt().unwrap_or(0) as u64))
693 .unwrap_or_default()
694}
695
696#[must_use]
698pub fn parse_settlement_currency(info: &InstrumentInfo, is_inverse: bool) -> String {
699 info.settlement_currency
700 .unwrap_or({
701 if is_inverse {
702 info.base_currency
703 } else {
704 info.quote_currency
705 }
706 })
707 .to_uppercase()
708}
709
710#[cfg(test)]
714mod tests {
715 use nautilus_model::{identifiers::InstrumentId, instruments::Instrument, types::Currency};
716 use rstest::rstest;
717
718 use super::*;
719 use crate::tests::load_test_json;
720
721 #[rstest]
722 fn test_parse_instrument_spot() {
723 let json_data = load_test_json("instrument_spot.json");
724 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
725
726 let instruments = parse_instrument_any(info, None, None, false);
727 let inst0 = instruments[0].clone();
728 let inst1 = instruments[1].clone();
729
730 assert_eq!(inst0.id(), InstrumentId::from("BTC_USDC.DERIBIT"));
731 assert_eq!(inst0.raw_symbol(), Symbol::from("BTC_USDC"));
732 assert_eq!(inst0.underlying(), None);
733 assert_eq!(inst0.base_currency(), Some(Currency::BTC()));
734 assert_eq!(inst0.quote_currency(), Currency::USDC());
735 assert_eq!(inst0.settlement_currency(), Currency::USDC());
736 assert!(!inst0.is_inverse());
737 assert_eq!(inst0.price_precision(), 2);
738 assert_eq!(inst0.size_precision(), 4);
739 assert_eq!(inst0.price_increment(), Price::from("0.01"));
740 assert_eq!(inst0.size_increment(), Quantity::from("0.0001"));
741 assert_eq!(inst0.multiplier(), Quantity::from(1));
742 assert_eq!(inst0.activation_ns(), None);
743 assert_eq!(inst0.expiration_ns(), None);
744 assert_eq!(inst0.min_quantity(), Some(Quantity::from("0.0001")));
745 assert_eq!(inst0.max_quantity(), None);
746 assert_eq!(inst0.min_notional(), None);
747 assert_eq!(inst0.max_notional(), None);
748 assert_eq!(inst0.maker_fee(), dec!(0));
749 assert_eq!(inst0.taker_fee(), dec!(0));
750 assert_eq!(inst0.ts_event().to_rfc3339(), "2023-04-24T00:00:00+00:00");
751 assert_eq!(inst0.ts_init().to_rfc3339(), "2023-04-24T00:00:00+00:00");
752
753 assert_eq!(inst1.id(), InstrumentId::from("BTC_USDC.DERIBIT"));
754 assert_eq!(inst1.raw_symbol(), Symbol::from("BTC_USDC"));
755 assert_eq!(inst1.underlying(), None);
756 assert_eq!(inst1.base_currency(), Some(Currency::BTC()));
757 assert_eq!(inst1.quote_currency(), Currency::USDC());
758 assert_eq!(inst1.settlement_currency(), Currency::USDC());
759 assert!(!inst1.is_inverse());
760 assert_eq!(inst1.price_precision(), 0); assert_eq!(inst1.size_precision(), 4);
762 assert_eq!(inst1.price_increment(), Price::from("1")); assert_eq!(inst1.size_increment(), Quantity::from("0.0001"));
764 assert_eq!(inst1.multiplier(), Quantity::from(1));
765 assert_eq!(inst1.activation_ns(), None);
766 assert_eq!(inst1.expiration_ns(), None);
767 assert_eq!(inst1.min_quantity(), Some(Quantity::from("0.0001")));
768 assert_eq!(inst1.max_quantity(), None);
769 assert_eq!(inst1.min_notional(), None);
770 assert_eq!(inst1.max_notional(), None);
771 assert_eq!(inst1.maker_fee(), dec!(0));
772 assert_eq!(inst1.taker_fee(), dec!(0));
773 assert_eq!(inst1.ts_event().to_rfc3339(), "2024-04-02T12:10:00+00:00");
774 assert_eq!(inst1.ts_init().to_rfc3339(), "2024-04-02T12:10:00+00:00");
775 }
776
777 #[rstest]
778 fn test_parse_instrument_perpetual() {
779 let json_data = load_test_json("instrument_perpetual.json");
780 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
781
782 let effective = UnixNanos::from("2020-08-01T08:00:00+00:00");
783 let instrument =
784 parse_instrument_any(info, Some(effective), Some(UnixNanos::default()), false)
785 .first()
786 .unwrap()
787 .clone();
788
789 assert_eq!(instrument.id(), InstrumentId::from("XBTUSD.BITMEX"));
790 assert_eq!(instrument.raw_symbol(), Symbol::from("XBTUSD"));
791 assert_eq!(instrument.underlying(), None);
792 assert_eq!(instrument.base_currency(), Some(Currency::BTC()));
793 assert_eq!(instrument.quote_currency(), Currency::USD());
794 assert_eq!(instrument.settlement_currency(), Currency::BTC());
795 assert!(instrument.is_inverse());
796 assert_eq!(instrument.price_precision(), 1);
797 assert_eq!(instrument.size_precision(), 0);
798 assert_eq!(instrument.price_increment(), Price::from("0.5"));
799 assert_eq!(instrument.size_increment(), Quantity::from(1));
800 assert_eq!(instrument.multiplier(), Quantity::from(1));
801 assert_eq!(instrument.activation_ns(), None);
802 assert_eq!(instrument.expiration_ns(), None);
803 assert_eq!(instrument.min_quantity(), Some(Quantity::from(100)));
804 assert_eq!(instrument.max_quantity(), None);
805 assert_eq!(instrument.min_notional(), None);
806 assert_eq!(instrument.max_notional(), None);
807 assert_eq!(instrument.maker_fee(), dec!(0.00050));
808 assert_eq!(instrument.taker_fee(), dec!(0.00050));
809 }
810
811 #[rstest]
812 fn test_parse_instrument_future() {
813 let json_data = load_test_json("instrument_future.json");
814 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
815
816 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
817 .first()
818 .unwrap()
819 .clone();
820
821 assert_eq!(instrument.id(), InstrumentId::from("BTC-14FEB25.DERIBIT"));
822 assert_eq!(instrument.raw_symbol(), Symbol::from("BTC-14FEB25"));
823 assert_eq!(instrument.underlying().unwrap().as_str(), "BTC");
824 assert_eq!(instrument.base_currency(), Some(Currency::BTC()));
825 assert_eq!(instrument.quote_currency(), Currency::USD());
826 assert_eq!(instrument.settlement_currency(), Currency::BTC());
827 assert!(instrument.is_inverse());
828 assert_eq!(instrument.price_precision(), 1); assert_eq!(instrument.size_precision(), 0); assert_eq!(instrument.price_increment(), Price::from("2.5"));
831 assert_eq!(instrument.size_increment(), Quantity::from(10));
832 assert_eq!(instrument.multiplier(), Quantity::from(1));
833 assert_eq!(
834 instrument.activation_ns(),
835 Some(UnixNanos::from(1_738_281_600_000_000_000))
836 );
837 assert_eq!(
838 instrument.expiration_ns(),
839 Some(UnixNanos::from(1_739_520_000_000_000_000))
840 );
841 assert_eq!(instrument.min_quantity(), Some(Quantity::from(10)));
842 assert_eq!(instrument.max_quantity(), None);
843 assert_eq!(instrument.min_notional(), None);
844 assert_eq!(instrument.max_notional(), None);
845 assert_eq!(instrument.maker_fee(), dec!(0));
846 assert_eq!(instrument.taker_fee(), dec!(0));
847 }
848
849 #[rstest]
850 fn test_parse_instrument_combo() {
851 let json_data = load_test_json("instrument_combo.json");
852 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
853
854 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
855 .first()
856 .unwrap()
857 .clone();
858
859 assert_eq!(
860 instrument.id(),
861 InstrumentId::from("BTC-FS-28MAR25_PERP.DERIBIT")
862 );
863 assert_eq!(instrument.raw_symbol(), Symbol::from("BTC-FS-28MAR25_PERP"));
864 assert_eq!(instrument.underlying().unwrap().as_str(), "BTC");
865 assert_eq!(instrument.base_currency(), Some(Currency::BTC()));
866 assert_eq!(instrument.quote_currency(), Currency::USD());
867 assert_eq!(instrument.settlement_currency(), Currency::BTC());
868 assert!(instrument.is_inverse());
869 assert_eq!(instrument.price_precision(), 1); assert_eq!(instrument.size_precision(), 0); assert_eq!(instrument.price_increment(), Price::from("0.5"));
872 assert_eq!(instrument.size_increment(), Quantity::from(10));
873 assert_eq!(instrument.multiplier(), Quantity::from(1));
874 assert_eq!(
875 instrument.activation_ns(),
876 Some(UnixNanos::from(1_711_670_400_000_000_000))
877 );
878 assert_eq!(
879 instrument.expiration_ns(),
880 Some(UnixNanos::from(1_743_148_800_000_000_000))
881 );
882 assert_eq!(instrument.min_quantity(), Some(Quantity::from(10)));
883 assert_eq!(instrument.max_quantity(), None);
884 assert_eq!(instrument.min_notional(), None);
885 assert_eq!(instrument.max_notional(), None);
886 assert_eq!(instrument.maker_fee(), dec!(0));
887 assert_eq!(instrument.taker_fee(), dec!(0));
888 }
889
890 #[rstest]
891 fn test_parse_instrument_option() {
892 let json_data = load_test_json("instrument_option.json");
893 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
894
895 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
896 .first()
897 .unwrap()
898 .clone();
899
900 assert_eq!(
901 instrument.id(),
902 InstrumentId::from("BTC-25APR25-200000-P.DERIBIT")
903 );
904 assert_eq!(
905 instrument.raw_symbol(),
906 Symbol::from("BTC-25APR25-200000-P")
907 );
908 assert_eq!(instrument.underlying().unwrap().as_str(), "BTC");
909 assert_eq!(instrument.base_currency(), Some(Currency::BTC()));
910 assert_eq!(instrument.quote_currency(), Currency::BTC());
911 assert_eq!(instrument.settlement_currency(), Currency::BTC());
912 assert!(!instrument.is_inverse());
913 assert_eq!(instrument.price_precision(), 4);
914 assert_eq!(instrument.size_precision(), 1); assert_eq!(instrument.price_increment(), Price::from("0.0001"));
916 assert_eq!(instrument.size_increment(), Quantity::from("0.1"));
917 assert_eq!(instrument.multiplier(), Quantity::from(1));
918 assert_eq!(
919 instrument.activation_ns(),
920 Some(UnixNanos::from(1_738_281_600_000_000_000))
921 );
922 assert_eq!(
923 instrument.expiration_ns(),
924 Some(UnixNanos::from(1_745_568_000_000_000_000))
925 );
926 assert_eq!(instrument.min_quantity(), Some(Quantity::from("0.1")));
927 assert_eq!(instrument.max_quantity(), None);
928 assert_eq!(instrument.min_notional(), None);
929 assert_eq!(instrument.max_notional(), None);
930 assert_eq!(instrument.maker_fee(), dec!(0));
931 assert_eq!(instrument.taker_fee(), dec!(0));
932 }
933}