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