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