1#[cfg(test)]
27use chrono::Duration;
28use chrono::{DateTime, Utc};
29use nautilus_model::enums::OrderType;
30use rust_decimal::{Decimal, prelude::ToPrimitive};
31
32use crate::proto::dydxprotocol::{
33 clob::{
34 Order, OrderId,
35 order::{ConditionType, GoodTilOneof, Side as OrderSide, TimeInForce as OrderTimeInForce},
36 },
37 subaccounts::SubaccountId,
38};
39
40pub const SHORT_TERM_ORDER_MAXIMUM_LIFETIME: u32 = 20;
44
45pub const DEFAULT_RUST_CLIENT_METADATA: u32 = 4;
47
48#[derive(Clone, Debug)]
50pub enum OrderGoodUntil {
51 Block(u32),
54 Time(DateTime<Utc>),
57}
58
59#[derive(Clone, Debug)]
64pub enum OrderFlags {
65 ShortTerm,
67 LongTerm,
69 Conditional,
74}
75
76#[derive(Clone, Debug)]
81pub struct OrderMarketParams {
82 pub atomic_resolution: i32,
84 pub clob_pair_id: u32,
86 pub oracle_price: Option<Decimal>,
88 pub quantum_conversion_exponent: i32,
90 pub step_base_quantums: u64,
92 pub subticks_per_tick: u32,
94}
95
96impl OrderMarketParams {
97 pub fn quantize_price(&self, price: Decimal) -> Result<u64, anyhow::Error> {
103 const QUOTE_QUANTUMS_ATOMIC_RESOLUTION: i32 = -6;
104 let exponent = -(self.atomic_resolution
105 - self.quantum_conversion_exponent
106 - QUOTE_QUANTUMS_ATOMIC_RESOLUTION);
107
108 let factor = if exponent < 0 {
111 Decimal::from(10_i64.pow(exponent.unsigned_abs()))
112 } else {
113 Decimal::new(1, exponent.unsigned_abs())
114 };
115
116 let raw_subticks = price * factor;
117 let subticks_per_tick = Decimal::from(self.subticks_per_tick);
118 let quantums = Self::quantize(&raw_subticks, &subticks_per_tick);
119 let result = quantums.max(subticks_per_tick);
120
121 result
122 .to_u64()
123 .ok_or_else(|| anyhow::anyhow!("Failed to convert price to u64"))
124 }
125
126 pub fn quantize_quantity(&self, quantity: Decimal) -> Result<u64, anyhow::Error> {
132 let factor = if self.atomic_resolution < 0 {
135 Decimal::from(10_i64.pow(self.atomic_resolution.unsigned_abs()))
136 } else {
137 Decimal::new(1, self.atomic_resolution.unsigned_abs())
138 };
139
140 let raw_quantums = quantity * factor;
141 let step_base_quantums = Decimal::from(self.step_base_quantums);
142 let quantums = Self::quantize(&raw_quantums, &step_base_quantums);
143 let result = quantums.max(step_base_quantums);
144
145 result
146 .to_u64()
147 .ok_or_else(|| anyhow::anyhow!("Failed to convert quantity to u64"))
148 }
149
150 fn quantize(value: &Decimal, fraction: &Decimal) -> Decimal {
152 (value / fraction).round() * fraction
153 }
154
155 #[must_use]
157 pub fn clob_pair_id(&self) -> u32 {
158 self.clob_pair_id
159 }
160}
161
162#[derive(Clone, Debug)]
173pub struct OrderBuilder {
174 market_params: OrderMarketParams,
175 subaccount_owner: String,
176 subaccount_number: u32,
177 client_id: u32,
178 flags: OrderFlags,
179 side: Option<OrderSide>,
180 order_type: Option<OrderType>,
181 size: Option<Decimal>,
182 price: Option<Decimal>,
183 time_in_force: Option<OrderTimeInForce>,
184 reduce_only: Option<bool>,
185 until: Option<OrderGoodUntil>,
186 trigger_price: Option<Decimal>,
187 condition_type: Option<ConditionType>,
188}
189
190impl OrderBuilder {
191 #[must_use]
193 pub fn new(
194 market_params: OrderMarketParams,
195 subaccount_owner: String,
196 subaccount_number: u32,
197 client_id: u32,
198 ) -> Self {
199 Self {
200 market_params,
201 subaccount_owner,
202 subaccount_number,
203 client_id,
204 flags: OrderFlags::ShortTerm,
205 side: Some(OrderSide::Buy),
206 order_type: Some(OrderType::Market),
207 size: None,
208 price: None,
209 time_in_force: None,
210 reduce_only: None,
211 until: None,
212 trigger_price: None,
213 condition_type: None,
214 }
215 }
216
217 #[must_use]
221 pub fn market(mut self, side: OrderSide, size: Decimal) -> Self {
222 self.order_type = Some(OrderType::Market);
223 self.side = Some(side);
224 self.size = Some(size);
225 self
226 }
227
228 #[must_use]
233 pub fn limit(mut self, side: OrderSide, price: Decimal, size: Decimal) -> Self {
234 self.order_type = Some(OrderType::Limit);
235 self.price = Some(price);
236 self.side = Some(side);
237 self.size = Some(size);
238 self
239 }
240
241 #[must_use]
245 pub fn stop_limit(
246 mut self,
247 side: OrderSide,
248 price: Decimal,
249 trigger_price: Decimal,
250 size: Decimal,
251 ) -> Self {
252 self.order_type = Some(OrderType::StopLimit);
253 self.price = Some(price);
254 self.trigger_price = Some(trigger_price);
255 self.side = Some(side);
256 self.size = Some(size);
257 self.condition_type = Some(ConditionType::StopLoss);
258 self.conditional()
259 }
260
261 #[must_use]
265 pub fn stop_market(mut self, side: OrderSide, trigger_price: Decimal, size: Decimal) -> Self {
266 self.order_type = Some(OrderType::StopMarket);
267 self.trigger_price = Some(trigger_price);
268 self.side = Some(side);
269 self.size = Some(size);
270 self.condition_type = Some(ConditionType::StopLoss);
271 self.conditional()
272 }
273
274 #[must_use]
278 pub fn take_profit_limit(
279 mut self,
280 side: OrderSide,
281 price: Decimal,
282 trigger_price: Decimal,
283 size: Decimal,
284 ) -> Self {
285 self.order_type = Some(OrderType::LimitIfTouched);
286 self.price = Some(price);
287 self.trigger_price = Some(trigger_price);
288 self.side = Some(side);
289 self.size = Some(size);
290 self.condition_type = Some(ConditionType::TakeProfit);
291 self.conditional()
292 }
293
294 #[must_use]
298 pub fn take_profit_market(
299 mut self,
300 side: OrderSide,
301 trigger_price: Decimal,
302 size: Decimal,
303 ) -> Self {
304 self.order_type = Some(OrderType::MarketIfTouched);
305 self.trigger_price = Some(trigger_price);
306 self.side = Some(side);
307 self.size = Some(size);
308 self.condition_type = Some(ConditionType::TakeProfit);
309 self.conditional()
310 }
311
312 #[must_use]
314 pub fn long_term(mut self) -> Self {
315 self.flags = OrderFlags::LongTerm;
316 self
317 }
318
319 #[must_use]
321 pub fn short_term(mut self) -> Self {
322 self.flags = OrderFlags::ShortTerm;
323 self
324 }
325
326 #[must_use]
328 pub fn conditional(mut self) -> Self {
329 self.flags = OrderFlags::Conditional;
330 self
331 }
332
333 #[must_use]
335 pub fn price(mut self, price: Decimal) -> Self {
336 self.price = Some(price);
337 self
338 }
339
340 #[must_use]
342 pub fn size(mut self, size: Decimal) -> Self {
343 self.size = Some(size);
344 self
345 }
346
347 #[must_use]
349 pub fn time_in_force(mut self, tif: OrderTimeInForce) -> Self {
350 self.time_in_force = Some(tif);
351 self
352 }
353
354 #[must_use]
356 pub fn reduce_only(mut self, reduce: bool) -> Self {
357 self.reduce_only = Some(reduce);
358 self
359 }
360
361 #[must_use]
363 pub fn until(mut self, gtof: OrderGoodUntil) -> Self {
364 self.until = Some(gtof);
365 self
366 }
367
368 pub fn build(self) -> Result<Order, anyhow::Error> {
374 let side = self
375 .side
376 .ok_or_else(|| anyhow::anyhow!("Order side not set"))?;
377 let size = self
378 .size
379 .ok_or_else(|| anyhow::anyhow!("Order size not set"))?;
380
381 let quantums = self.market_params.quantize_quantity(size)?;
383
384 let order_id = Some(OrderId {
386 subaccount_id: Some(SubaccountId {
387 owner: self.subaccount_owner.clone(),
388 number: self.subaccount_number,
389 }),
390 client_id: self.client_id,
391 order_flags: match self.flags {
392 OrderFlags::ShortTerm => 0,
393 OrderFlags::LongTerm => 64,
394 OrderFlags::Conditional => 32,
395 },
396 clob_pair_id: self.market_params.clob_pair_id,
397 });
398
399 let until = self
401 .until
402 .ok_or_else(|| anyhow::anyhow!("Order expiration (until) not set"))?;
403
404 let good_til_oneof = match until {
405 OrderGoodUntil::Block(height) => Some(GoodTilOneof::GoodTilBlock(height)),
406 OrderGoodUntil::Time(time) => {
407 Some(GoodTilOneof::GoodTilBlockTime(time.timestamp().try_into()?))
408 }
409 };
410
411 let subticks = if let Some(price) = self.price {
413 self.market_params.quantize_price(price)?
414 } else {
415 0
416 };
417
418 Ok(Order {
419 order_id,
420 side: side as i32,
421 quantums,
422 subticks,
423 good_til_oneof,
424 time_in_force: self.time_in_force.map_or(0, |tif| tif as i32),
425 reduce_only: self.reduce_only.unwrap_or(false),
426 client_metadata: DEFAULT_RUST_CLIENT_METADATA,
427 condition_type: self.condition_type.map_or(0, |ct| ct as i32),
428 conditional_order_trigger_subticks: self
429 .trigger_price
430 .map(|tp| self.market_params.quantize_price(tp))
431 .transpose()?
432 .unwrap_or(0),
433 twap_parameters: None,
434 builder_code_parameters: None,
435 order_router_address: String::new(),
436 })
437 }
438}
439
440impl Default for OrderBuilder {
441 fn default() -> Self {
442 Self {
443 market_params: OrderMarketParams {
444 atomic_resolution: -10,
445 clob_pair_id: 0,
446 oracle_price: None,
447 quantum_conversion_exponent: -9,
448 step_base_quantums: 1_000_000,
449 subticks_per_tick: 100_000,
450 },
451 subaccount_owner: String::new(),
452 subaccount_number: 0,
453 client_id: 0,
454 flags: OrderFlags::ShortTerm,
455 side: Some(OrderSide::Buy),
456 order_type: Some(OrderType::Market),
457 size: None,
458 price: None,
459 time_in_force: None,
460 reduce_only: None,
461 until: None,
462 trigger_price: None,
463 condition_type: None,
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use rstest::rstest;
471 use rust_decimal_macros::dec;
472
473 use super::*;
474
475 fn sample_market_params() -> OrderMarketParams {
476 OrderMarketParams {
477 atomic_resolution: -10,
478 clob_pair_id: 0,
479 oracle_price: Some(dec!(50000)),
480 quantum_conversion_exponent: -9,
481 step_base_quantums: 1_000_000,
482 subticks_per_tick: 100_000,
483 }
484 }
485
486 #[rstest]
487 fn test_market_params_quantize_price() {
488 let market = sample_market_params();
489 let price = dec!(50000);
490 let subticks = market.quantize_price(price).unwrap();
491 assert_eq!(subticks, 5_000_000_000);
494 }
495
496 #[rstest]
497 fn test_market_params_quantize_quantity() {
498 let market = sample_market_params();
499 let quantity = dec!(0.01);
500 let quantums = market.quantize_quantity(quantity).unwrap();
501 assert_eq!(quantums, 100_000_000);
504 }
505
506 #[rstest]
507 fn test_quantize_price_rounding_up() {
508 let market = sample_market_params();
509 let price = dec!(50000.6);
511 let subticks = market.quantize_price(price).unwrap();
512 assert_eq!(subticks, 5_000_100_000);
513 }
514
515 #[rstest]
516 fn test_quantize_price_rounding_down() {
517 let market = sample_market_params();
518 let price = dec!(49999.4);
520 let subticks = market.quantize_price(price).unwrap();
521 assert_eq!(subticks, 4_999_900_000);
522 }
523
524 #[rstest]
525 fn test_quantize_quantity_rounding_up() {
526 let market = sample_market_params();
527 let quantity = dec!(0.0105); let quantums = market.quantize_quantity(quantity).unwrap();
530 assert_eq!(quantums, 105_000_000);
531 }
532
533 #[rstest]
534 fn test_quantize_quantity_rounding_down() {
535 let market = sample_market_params();
536 let quantity = dec!(0.0104); let quantums = market.quantize_quantity(quantity).unwrap();
539 assert_eq!(quantums, 104_000_000);
540 }
541
542 #[rstest]
543 fn test_quantize_price_minimum_tick() {
544 let market = sample_market_params();
545 let price = dec!(0.001);
547 let subticks = market.quantize_price(price).unwrap();
548 assert_eq!(subticks, market.subticks_per_tick as u64);
549 }
550
551 #[rstest]
552 fn test_quantize_quantity_minimum_quantum() {
553 let market = sample_market_params();
554 let quantity = dec!(0.00000001);
556 let quantums = market.quantize_quantity(quantity).unwrap();
557 assert_eq!(quantums, market.step_base_quantums);
558 }
559
560 #[rstest]
561 fn test_quantize_price_large_values() {
562 let market = sample_market_params();
563 let price = dec!(100000);
565 let subticks = market.quantize_price(price).unwrap();
566 assert_eq!(subticks, 10_000_000_000);
567 }
568
569 #[rstest]
570 fn test_quantize_quantity_large_values() {
571 let market = sample_market_params();
572 let quantity = dec!(10);
574 let quantums = market.quantize_quantity(quantity).unwrap();
575 assert_eq!(quantums, 100_000_000_000);
576 }
577
578 #[rstest]
579 fn test_order_builder_market_buy() {
580 let market = sample_market_params();
581 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 1);
582
583 let order = builder
584 .market(OrderSide::Buy, dec!(0.01))
585 .until(OrderGoodUntil::Block(100))
586 .build()
587 .unwrap();
588
589 assert_eq!(order.side, OrderSide::Buy as i32);
590 assert_eq!(order.quantums, 100_000_000); assert_eq!(order.subticks, 0); assert!(!order.reduce_only);
593 assert_eq!(order.client_metadata, DEFAULT_RUST_CLIENT_METADATA);
594 }
595
596 #[rstest]
597 fn test_order_builder_market_sell() {
598 let market = sample_market_params();
599 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 2);
600
601 let order = builder
602 .market(OrderSide::Sell, dec!(0.02))
603 .until(OrderGoodUntil::Block(100))
604 .build()
605 .unwrap();
606
607 assert_eq!(order.side, OrderSide::Sell as i32);
608 assert_eq!(order.quantums, 200_000_000); }
610
611 #[rstest]
612 fn test_order_builder_limit_buy() {
613 let market = sample_market_params();
614 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 3);
615
616 let order = builder
617 .limit(OrderSide::Buy, dec!(49000), dec!(0.01))
618 .until(OrderGoodUntil::Block(100))
619 .build()
620 .unwrap();
621
622 assert_eq!(order.side, OrderSide::Buy as i32);
623 assert_eq!(order.quantums, 100_000_000); assert_eq!(order.subticks, 4_900_000_000); assert!(!order.reduce_only);
626 }
627
628 #[rstest]
629 fn test_order_builder_limit_sell() {
630 let market = sample_market_params();
631 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 4);
632
633 let order = builder
634 .limit(OrderSide::Sell, dec!(51000), dec!(0.015))
635 .until(OrderGoodUntil::Block(100))
636 .build()
637 .unwrap();
638
639 assert_eq!(order.side, OrderSide::Sell as i32);
640 assert_eq!(order.quantums, 150_000_000); assert_eq!(order.subticks, 5_100_000_000); }
643
644 #[rstest]
645 fn test_order_builder_limit_with_reduce_only() {
646 let market = sample_market_params();
647 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 5);
648
649 let order = builder
650 .limit(OrderSide::Sell, dec!(50000), dec!(0.01))
651 .reduce_only(true)
652 .until(OrderGoodUntil::Block(100))
653 .build()
654 .unwrap();
655
656 assert!(order.reduce_only);
657 }
658
659 #[rstest]
660 fn test_order_builder_short_term_flag() {
661 let market = sample_market_params();
662 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 6);
663
664 let order = builder
665 .short_term()
666 .market(OrderSide::Buy, dec!(0.01))
667 .until(OrderGoodUntil::Block(100))
668 .build()
669 .unwrap();
670
671 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 0);
673 }
674
675 #[rstest]
676 fn test_order_builder_long_term_flag() {
677 let market = sample_market_params();
678 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 7);
679
680 let now = Utc::now();
681 let until = now + Duration::hours(1);
682
683 let order = builder
684 .long_term()
685 .limit(OrderSide::Buy, dec!(50000), dec!(0.01))
686 .until(OrderGoodUntil::Time(until))
687 .build()
688 .unwrap();
689
690 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 64);
692 }
693
694 #[rstest]
695 fn test_order_builder_conditional_flag() {
696 let market = sample_market_params();
697 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 8);
698
699 let order = builder
700 .stop_limit(OrderSide::Sell, dec!(48000), dec!(49000), dec!(0.01))
701 .until(OrderGoodUntil::Block(100))
702 .build()
703 .unwrap();
704
705 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 32);
707 assert_eq!(order.conditional_order_trigger_subticks, 4_900_000_000);
708 }
709
710 #[rstest]
711 fn test_stop_limit_sets_condition_type() {
712 let market = sample_market_params();
713 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 100);
714
715 let order = builder
716 .stop_limit(OrderSide::Sell, dec!(48000), dec!(49000), dec!(0.01))
717 .until(OrderGoodUntil::Block(100))
718 .build()
719 .unwrap();
720
721 assert_eq!(order.condition_type, ConditionType::StopLoss as i32);
722 }
723
724 #[rstest]
725 fn test_stop_market_sets_condition_type() {
726 let market = sample_market_params();
727 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 101);
728
729 let order = builder
730 .stop_market(OrderSide::Sell, dec!(49000), dec!(0.01))
731 .until(OrderGoodUntil::Block(100))
732 .build()
733 .unwrap();
734
735 assert_eq!(order.condition_type, ConditionType::StopLoss as i32);
736 }
737
738 #[rstest]
739 fn test_take_profit_limit_sets_condition_type() {
740 let market = sample_market_params();
741 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 102);
742
743 let order = builder
744 .take_profit_limit(OrderSide::Sell, dec!(52000), dec!(51000), dec!(0.01))
745 .until(OrderGoodUntil::Block(100))
746 .build()
747 .unwrap();
748
749 assert_eq!(order.condition_type, ConditionType::TakeProfit as i32);
750 }
751
752 #[rstest]
753 fn test_take_profit_market_sets_condition_type() {
754 let market = sample_market_params();
755 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 103);
756
757 let order = builder
758 .take_profit_market(OrderSide::Sell, dec!(51000), dec!(0.01))
759 .until(OrderGoodUntil::Block(100))
760 .build()
761 .unwrap();
762
763 assert_eq!(order.condition_type, ConditionType::TakeProfit as i32);
764 }
765
766 #[rstest]
767 fn test_order_builder_missing_size_error() {
768 let market = sample_market_params();
769 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 9);
770
771 let result = builder.until(OrderGoodUntil::Block(100)).build();
772
773 assert!(result.is_err());
774 assert!(result.unwrap_err().to_string().contains("size"));
775 }
776
777 #[rstest]
778 fn test_order_builder_missing_until_error() {
779 let market = sample_market_params();
780 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 10);
781
782 let result = builder.market(OrderSide::Buy, dec!(0.01)).build();
783
784 assert!(result.is_err());
785 }
786
787 #[rstest]
788 fn test_order_builder_time_in_force() {
789 let market = sample_market_params();
790 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 11);
791
792 let order = builder
793 .limit(OrderSide::Buy, dec!(50000), dec!(0.01))
794 .time_in_force(OrderTimeInForce::Ioc)
795 .until(OrderGoodUntil::Block(100))
796 .build()
797 .unwrap();
798
799 assert_eq!(order.time_in_force, OrderTimeInForce::Ioc as i32);
800 }
801
802 #[rstest]
803 fn test_order_builder_clob_pair_id() {
804 let mut market = sample_market_params();
805 market.clob_pair_id = 5;
806
807 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 12);
808
809 let order = builder
810 .market(OrderSide::Buy, dec!(0.01))
811 .until(OrderGoodUntil::Block(100))
812 .build()
813 .unwrap();
814
815 assert_eq!(order.order_id.as_ref().unwrap().clob_pair_id, 5);
816 }
817}