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