1use std::str::FromStr;
17
18use chrono::{DateTime, Utc};
19use nautilus_core::UnixNanos;
20use nautilus_model::{
21 identifiers::Symbol,
22 instruments::InstrumentAny,
23 types::{Price, Quantity},
24};
25use rust_decimal::Decimal;
26use rust_decimal_macros::dec;
27
28use super::{
29 instruments::{
30 create_crypto_future, create_crypto_perpetual, create_currency_pair, create_option_contract,
31 },
32 models::InstrumentInfo,
33};
34use crate::{
35 enums::InstrumentType,
36 parse::{normalize_instrument_id, parse_instrument_id},
37};
38
39#[must_use]
40pub fn parse_instrument_any(
41 info: InstrumentInfo,
42 effective: Option<UnixNanos>,
43 ts_init: Option<UnixNanos>,
44 normalize_symbols: bool,
45) -> Vec<InstrumentAny> {
46 match info.instrument_type {
47 InstrumentType::Spot => parse_spot_instrument(info, effective, ts_init, normalize_symbols),
48 InstrumentType::Perpetual => {
49 parse_perp_instrument(info, effective, ts_init, normalize_symbols)
50 }
51 InstrumentType::Future | InstrumentType::Combo => {
52 parse_future_instrument(info, effective, ts_init, normalize_symbols)
53 }
54 InstrumentType::Option => {
55 parse_option_instrument(info, effective, ts_init, normalize_symbols)
56 }
57 }
58}
59
60fn parse_spot_instrument(
61 info: InstrumentInfo,
62 effective: Option<UnixNanos>,
63 ts_init: Option<UnixNanos>,
64 normalize_symbols: bool,
65) -> Vec<InstrumentAny> {
66 let instrument_id = if normalize_symbols {
67 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
68 } else {
69 parse_instrument_id(&info.exchange, info.id)
70 };
71 let raw_symbol = Symbol::new(info.id);
72 let margin_init = dec!(0); let margin_maint = dec!(0); let maker_fee =
75 Decimal::from_str(info.maker_fee.to_string().as_str()).expect("Invalid decimal value");
76 let taker_fee =
77 Decimal::from_str(info.taker_fee.to_string().as_str()).expect("Invalid decimal value");
78
79 let mut price_increment = get_price_increment(info.price_increment);
80 let mut size_increment = get_size_increment(info.amount_increment);
81 let mut ts_event = UnixNanos::from(info.available_since);
82
83 let mut instruments: Vec<InstrumentAny> = Vec::new();
84
85 instruments.push(create_currency_pair(
86 &info,
87 instrument_id,
88 raw_symbol,
89 price_increment,
90 size_increment,
91 margin_init,
92 margin_maint,
93 maker_fee,
94 taker_fee,
95 ts_event,
96 ts_init.unwrap_or(ts_event),
97 ));
98
99 if let Some(changes) = &info.changes {
100 for change in changes {
101 price_increment = match change.price_increment {
102 Some(value) => get_price_increment(value),
103 None => price_increment,
104 };
105 size_increment = match change.amount_increment {
106 Some(value) => get_size_increment(value),
107 None => size_increment,
108 };
109 ts_event = UnixNanos::from(change.until);
110
111 instruments.push(create_currency_pair(
112 &info,
113 instrument_id,
114 raw_symbol,
115 price_increment,
116 size_increment,
117 margin_init,
118 margin_maint,
119 maker_fee,
120 taker_fee,
121 ts_event,
122 ts_init.unwrap_or(ts_event),
123 ));
124 }
125 }
126
127 if let Some(effective) = effective {
128 instruments.retain(|i| i.ts_event() <= effective);
130 if instruments.is_empty() {
131 return Vec::new();
132 }
133 instruments.sort_by_key(|i| std::cmp::Reverse(i.ts_event()));
135 instruments.truncate(1);
137 }
138
139 instruments
140}
141
142fn parse_perp_instrument(
143 info: InstrumentInfo,
144 effective: Option<UnixNanos>,
145 ts_init: Option<UnixNanos>,
146 normalize_symbols: bool,
147) -> Vec<InstrumentAny> {
148 let instrument_id = if normalize_symbols {
149 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
150 } else {
151 parse_instrument_id(&info.exchange, info.id)
152 };
153 let raw_symbol = Symbol::new(info.id);
154 let margin_init = dec!(0); let margin_maint = dec!(0); let maker_fee =
157 Decimal::from_str(info.maker_fee.to_string().as_str()).expect("Invalid decimal value");
158 let taker_fee =
159 Decimal::from_str(info.taker_fee.to_string().as_str()).expect("Invalid decimal value");
160
161 let mut price_increment = get_price_increment(info.price_increment);
162 let mut size_increment = get_size_increment(info.amount_increment);
163 let mut multiplier = get_multiplier(info.contract_multiplier);
164 let mut ts_event = UnixNanos::from(info.available_since);
165
166 let mut instruments = Vec::new();
167
168 instruments.push(create_crypto_perpetual(
169 &info,
170 instrument_id,
171 raw_symbol,
172 price_increment,
173 size_increment,
174 multiplier,
175 margin_init,
176 margin_maint,
177 maker_fee,
178 taker_fee,
179 ts_event,
180 ts_init.unwrap_or(ts_event),
181 ));
182
183 if let Some(changes) = &info.changes {
184 for change in changes {
185 price_increment = match change.price_increment {
186 Some(value) => get_price_increment(value),
187 None => price_increment,
188 };
189 size_increment = match change.amount_increment {
190 Some(value) => get_size_increment(value),
191 None => size_increment,
192 };
193 multiplier = match change.contract_multiplier {
194 Some(value) => Some(Quantity::from(value.to_string())),
195 None => multiplier,
196 };
197 ts_event = UnixNanos::from(change.until);
198
199 instruments.push(create_crypto_perpetual(
200 &info,
201 instrument_id,
202 raw_symbol,
203 price_increment,
204 size_increment,
205 multiplier,
206 margin_init,
207 margin_maint,
208 maker_fee,
209 taker_fee,
210 ts_event,
211 ts_init.unwrap_or(ts_event),
212 ));
213 }
214 }
215
216 if let Some(effective) = effective {
217 instruments.retain(|i| i.ts_event() <= effective);
219 if instruments.is_empty() {
220 return Vec::new();
221 }
222 instruments.sort_by_key(|i| std::cmp::Reverse(i.ts_event()));
224 instruments.truncate(1);
226 }
227
228 instruments
229}
230
231fn parse_future_instrument(
232 info: InstrumentInfo,
233 effective: Option<UnixNanos>,
234 ts_init: Option<UnixNanos>,
235 normalize_symbols: bool,
236) -> Vec<InstrumentAny> {
237 let instrument_id = if normalize_symbols {
238 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
239 } else {
240 parse_instrument_id(&info.exchange, info.id)
241 };
242 let raw_symbol = Symbol::new(info.id);
243 let activation = parse_datetime_to_unix_nanos(Some(info.available_since));
244 let expiration = parse_datetime_to_unix_nanos(info.expiry);
245 let margin_init = dec!(0); let margin_maint = dec!(0); let maker_fee =
248 Decimal::from_str(info.maker_fee.to_string().as_str()).expect("Invalid decimal value");
249 let taker_fee =
250 Decimal::from_str(info.taker_fee.to_string().as_str()).expect("Invalid decimal value");
251
252 let mut price_increment = get_price_increment(info.price_increment);
253 let mut size_increment = get_size_increment(info.amount_increment);
254 let mut multiplier = get_multiplier(info.contract_multiplier);
255 let mut ts_event = UnixNanos::from(info.available_since);
256
257 let mut instruments = Vec::new();
258
259 instruments.push(create_crypto_future(
260 &info,
261 instrument_id,
262 raw_symbol,
263 activation,
264 expiration,
265 price_increment,
266 size_increment,
267 multiplier,
268 margin_init,
269 margin_maint,
270 maker_fee,
271 taker_fee,
272 ts_event,
273 ts_init.unwrap_or(ts_event),
274 ));
275
276 if let Some(changes) = &info.changes {
277 for change in changes {
278 price_increment = match change.price_increment {
279 Some(value) => get_price_increment(value),
280 None => price_increment,
281 };
282 size_increment = match change.amount_increment {
283 Some(value) => get_size_increment(value),
284 None => size_increment,
285 };
286 multiplier = match change.contract_multiplier {
287 Some(value) => Some(Quantity::from(value.to_string())),
288 None => multiplier,
289 };
290 ts_event = UnixNanos::from(change.until);
291
292 instruments.push(create_crypto_future(
293 &info,
294 instrument_id,
295 raw_symbol,
296 activation,
297 expiration,
298 price_increment,
299 size_increment,
300 multiplier,
301 margin_init,
302 margin_maint,
303 maker_fee,
304 taker_fee,
305 ts_event,
306 ts_init.unwrap_or(ts_event),
307 ));
308 }
309 }
310
311 if let Some(effective) = effective {
312 instruments.retain(|i| i.ts_event() <= effective);
314 if instruments.is_empty() {
315 return Vec::new();
316 }
317 instruments.sort_by_key(|i| std::cmp::Reverse(i.ts_event()));
319 instruments.truncate(1);
321 }
322
323 instruments
324}
325
326fn parse_option_instrument(
327 info: InstrumentInfo,
328 effective: Option<UnixNanos>,
329 ts_init: Option<UnixNanos>,
330 normalize_symbols: bool,
331) -> Vec<InstrumentAny> {
332 let instrument_id = if normalize_symbols {
333 normalize_instrument_id(&info.exchange, info.id, &info.instrument_type, info.inverse)
334 } else {
335 parse_instrument_id(&info.exchange, info.id)
336 };
337 let raw_symbol = Symbol::new(info.id);
338 let activation = parse_datetime_to_unix_nanos(Some(info.available_since));
339 let expiration = parse_datetime_to_unix_nanos(info.expiry);
340 let margin_init = dec!(0); let margin_maint = dec!(0); let maker_fee =
343 Decimal::from_str(info.maker_fee.to_string().as_str()).expect("Invalid decimal value");
344 let taker_fee =
345 Decimal::from_str(info.taker_fee.to_string().as_str()).expect("Invalid decimal value");
346
347 let mut price_increment = get_price_increment(info.price_increment);
348 let mut multiplier = get_multiplier(info.contract_multiplier);
349 let mut ts_event = UnixNanos::from(info.available_since);
350
351 let mut instruments = Vec::new();
352
353 instruments.push(create_option_contract(
354 &info,
355 instrument_id,
356 raw_symbol,
357 activation,
358 expiration,
359 price_increment,
360 multiplier,
361 margin_init,
362 margin_maint,
363 maker_fee,
364 taker_fee,
365 ts_event,
366 ts_init.unwrap_or(ts_event),
367 ));
368
369 if let Some(changes) = &info.changes {
370 for change in changes {
371 price_increment = match change.price_increment {
372 Some(value) => get_price_increment(value),
373 None => price_increment,
374 };
375
376 multiplier = match change.contract_multiplier {
377 Some(value) => Some(Quantity::from(value.to_string())),
378 None => multiplier,
379 };
380 ts_event = UnixNanos::from(change.until);
381
382 instruments.push(create_option_contract(
383 &info,
384 instrument_id,
385 raw_symbol,
386 activation,
387 expiration,
388 price_increment,
389 multiplier,
390 margin_init,
391 margin_maint,
392 maker_fee,
393 taker_fee,
394 ts_event,
395 ts_init.unwrap_or(ts_event),
396 ));
397 }
398 }
399
400 if let Some(effective) = effective {
401 instruments.retain(|i| i.ts_event() <= effective);
403 if instruments.is_empty() {
404 return Vec::new();
405 }
406 instruments.sort_by_key(|i| std::cmp::Reverse(i.ts_event()));
408 instruments.truncate(1);
410 }
411
412 instruments
413}
414
415fn get_price_increment(value: f64) -> Price {
417 Price::from(value.to_string())
418}
419
420fn get_size_increment(value: f64) -> Quantity {
422 Quantity::from(value.to_string())
423}
424
425fn get_multiplier(value: Option<f64>) -> Option<Quantity> {
426 value.map(|x| Quantity::from(x.to_string()))
427}
428
429fn parse_datetime_to_unix_nanos(value: Option<DateTime<Utc>>) -> UnixNanos {
432 value
433 .map(|dt| UnixNanos::from(dt.timestamp_nanos_opt().unwrap_or(0) as u64))
434 .unwrap_or_default()
435}
436
437#[cfg(test)]
441mod tests {
442 use nautilus_model::{identifiers::InstrumentId, types::Currency};
443 use rstest::rstest;
444
445 use super::*;
446 use crate::tests::load_test_json;
447
448 #[rstest]
449 fn test_parse_instrument_spot() {
450 let json_data = load_test_json("instrument_spot.json");
451 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
452
453 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
454 .first()
455 .unwrap()
456 .clone();
457
458 assert_eq!(instrument.id(), InstrumentId::from("BTC_USDC.DERIBIT"));
459 assert_eq!(instrument.raw_symbol(), Symbol::from("BTC_USDC"));
460 assert_eq!(instrument.underlying(), None);
461 assert_eq!(instrument.base_currency(), Some(Currency::BTC()));
462 assert_eq!(instrument.quote_currency(), Currency::USDC());
463 assert_eq!(instrument.settlement_currency(), Currency::USDC());
464 assert!(!instrument.is_inverse());
465 assert_eq!(instrument.price_precision(), 0);
466 assert_eq!(instrument.size_precision(), 4);
467 assert_eq!(instrument.price_increment(), Price::from("1"));
468 assert_eq!(instrument.size_increment(), Quantity::from("0.0001"));
469 assert_eq!(instrument.multiplier(), Quantity::from(1));
470 assert_eq!(instrument.activation_ns(), None);
471 assert_eq!(instrument.expiration_ns(), None);
472 assert_eq!(instrument.min_quantity(), Some(Quantity::from("0.0001")));
473 assert_eq!(instrument.max_quantity(), None);
474 assert_eq!(instrument.min_notional(), None);
475 assert_eq!(instrument.max_notional(), None);
476 assert_eq!(instrument.maker_fee(), dec!(0));
477 assert_eq!(instrument.taker_fee(), dec!(0));
478 }
479
480 #[rstest]
481 fn test_parse_instrument_perpetual() {
482 let json_data = load_test_json("instrument_perpetual.json");
483 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
484
485 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
486 .first()
487 .unwrap()
488 .clone();
489
490 assert_eq!(instrument.id(), InstrumentId::from("XBTUSD.BITMEX"));
491 assert_eq!(instrument.raw_symbol(), Symbol::from("XBTUSD"));
492 assert_eq!(instrument.underlying(), None);
493 assert_eq!(instrument.base_currency(), Some(Currency::BTC()));
494 assert_eq!(instrument.quote_currency(), Currency::USD());
495 assert_eq!(instrument.settlement_currency(), Currency::USD());
496 assert!(instrument.is_inverse());
497 assert_eq!(instrument.price_precision(), 1);
498 assert_eq!(instrument.size_precision(), 0);
499 assert_eq!(instrument.price_increment(), Price::from("0.5"));
500 assert_eq!(instrument.size_increment(), Quantity::from(1));
501 assert_eq!(instrument.multiplier(), Quantity::from(1));
502 assert_eq!(instrument.activation_ns(), None);
503 assert_eq!(instrument.expiration_ns(), None);
504 assert_eq!(instrument.min_quantity(), Some(Quantity::from(1)));
505 assert_eq!(instrument.max_quantity(), None);
506 assert_eq!(instrument.min_notional(), None);
507 assert_eq!(instrument.max_notional(), None);
508 assert_eq!(instrument.maker_fee(), dec!(-0.00025));
509 assert_eq!(instrument.taker_fee(), dec!(0.00075));
510 }
511
512 #[rstest]
513 fn test_parse_instrument_future() {
514 let json_data = load_test_json("instrument_future.json");
515 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
516
517 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
518 .first()
519 .unwrap()
520 .clone();
521
522 assert_eq!(instrument.id(), InstrumentId::from("BTC-14FEB25.DERIBIT"));
523 assert_eq!(instrument.raw_symbol(), Symbol::from("BTC-14FEB25"));
524 assert_eq!(instrument.underlying().unwrap().as_str(), "BTC");
525 assert_eq!(instrument.base_currency(), None);
526 assert_eq!(instrument.quote_currency(), Currency::USD());
527 assert_eq!(instrument.settlement_currency(), Currency::BTC());
528 assert!(instrument.is_inverse());
529 assert_eq!(instrument.price_precision(), 1); assert_eq!(instrument.size_precision(), 0); assert_eq!(instrument.price_increment(), Price::from("2.5"));
532 assert_eq!(instrument.size_increment(), Quantity::from(10));
533 assert_eq!(instrument.multiplier(), Quantity::from(1));
534 assert_eq!(
535 instrument.activation_ns(),
536 Some(UnixNanos::from(1738281600000000000))
537 );
538 assert_eq!(
539 instrument.expiration_ns(),
540 Some(UnixNanos::from(1739520000000000000))
541 );
542 assert_eq!(instrument.min_quantity(), Some(Quantity::from(10)));
543 assert_eq!(instrument.max_quantity(), None);
544 assert_eq!(instrument.min_notional(), None);
545 assert_eq!(instrument.max_notional(), None);
546 assert_eq!(instrument.maker_fee(), dec!(0));
547 assert_eq!(instrument.taker_fee(), dec!(0));
548 }
549
550 #[rstest]
551 fn test_parse_instrument_combo() {
552 let json_data = load_test_json("instrument_combo.json");
553 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
554
555 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
556 .first()
557 .unwrap()
558 .clone();
559
560 assert_eq!(
561 instrument.id(),
562 InstrumentId::from("BTC-FS-28MAR25_PERP.DERIBIT")
563 );
564 assert_eq!(instrument.raw_symbol(), Symbol::from("BTC-FS-28MAR25_PERP"));
565 assert_eq!(instrument.underlying().unwrap().as_str(), "BTC");
566 assert_eq!(instrument.base_currency(), None);
567 assert_eq!(instrument.quote_currency(), Currency::USD());
568 assert_eq!(instrument.settlement_currency(), Currency::BTC());
569 assert!(instrument.is_inverse());
570 assert_eq!(instrument.price_precision(), 1); assert_eq!(instrument.size_precision(), 0); assert_eq!(instrument.price_increment(), Price::from("0.5"));
573 assert_eq!(instrument.size_increment(), Quantity::from(10));
574 assert_eq!(instrument.multiplier(), Quantity::from(1));
575 assert_eq!(
576 instrument.activation_ns(),
577 Some(UnixNanos::from(1711670400000000000))
578 );
579 assert_eq!(
580 instrument.expiration_ns(),
581 Some(UnixNanos::from(1743148800000000000))
582 );
583 assert_eq!(instrument.min_quantity(), Some(Quantity::from(10)));
584 assert_eq!(instrument.max_quantity(), None);
585 assert_eq!(instrument.min_notional(), None);
586 assert_eq!(instrument.max_notional(), None);
587 assert_eq!(instrument.maker_fee(), dec!(0));
588 assert_eq!(instrument.taker_fee(), dec!(0));
589 }
590
591 #[rstest]
592 fn test_parse_instrument_option() {
593 let json_data = load_test_json("instrument_option.json");
594 let info: InstrumentInfo = serde_json::from_str(&json_data).unwrap();
595
596 let instrument = parse_instrument_any(info, None, Some(UnixNanos::default()), false)
597 .first()
598 .unwrap()
599 .clone();
600
601 assert_eq!(
602 instrument.id(),
603 InstrumentId::from("BTC-25APR25-200000-P.DERIBIT")
604 );
605 assert_eq!(
606 instrument.raw_symbol(),
607 Symbol::from("BTC-25APR25-200000-P")
608 );
609 assert_eq!(instrument.underlying().unwrap().as_str(), "BTC");
610 assert_eq!(instrument.base_currency(), None);
611 assert_eq!(instrument.quote_currency(), Currency::BTC());
612 assert_eq!(instrument.settlement_currency(), Currency::BTC());
613 assert_eq!(instrument.price_precision(), 4);
615 assert_eq!(instrument.price_increment(), Price::from("0.0001"));
617 assert_eq!(instrument.multiplier(), Quantity::from(1));
619 assert_eq!(
620 instrument.activation_ns(),
621 Some(UnixNanos::from(1738281600000000000))
622 );
623 assert_eq!(
624 instrument.expiration_ns(),
625 Some(UnixNanos::from(1745568000000000000))
626 );
627 assert_eq!(instrument.min_quantity(), Some(Quantity::from("0.1")));
628 assert_eq!(instrument.max_quantity(), None);
629 assert_eq!(instrument.min_notional(), None);
630 assert_eq!(instrument.max_notional(), None);
631 assert_eq!(instrument.maker_fee(), dec!(0));
632 assert_eq!(instrument.taker_fee(), dec!(0));
633 }
634}