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