1#[cfg(test)]
27use chrono::Duration;
28use chrono::{DateTime, Utc};
29use nautilus_model::enums::OrderType;
30use rust_decimal::{Decimal, prelude::ToPrimitive};
31
32use crate::{
33 common::consts::DYDX_NAUTILUS_ORDER_ROUTER_ADDRESS,
34 proto::dydxprotocol::{
35 clob::{
36 Order, OrderId,
37 order::{
38 ConditionType, GoodTilOneof, Side as OrderSide, TimeInForce as OrderTimeInForce,
39 },
40 },
41 subaccounts::SubaccountId,
42 },
43};
44
45pub const SHORT_TERM_ORDER_MAXIMUM_LIFETIME: u32 = 40;
49
50pub const DEFAULT_MARKET_ORDER_SLIPPAGE: Decimal = Decimal::from_parts(5, 0, 0, false, 2);
56
57pub const DEFAULT_RUST_CLIENT_METADATA: u32 = 4;
59
60#[derive(Clone, Debug)]
62pub enum OrderGoodUntil {
63 Block(u32),
66 Time(DateTime<Utc>),
69}
70
71#[derive(Clone, Debug)]
76pub enum OrderFlags {
77 ShortTerm,
79 LongTerm,
81 Conditional,
86}
87
88#[derive(Clone, Debug)]
93pub struct OrderMarketParams {
94 pub atomic_resolution: i32,
96 pub clob_pair_id: u32,
98 pub oracle_price: Option<Decimal>,
100 pub quantum_conversion_exponent: i32,
102 pub step_base_quantums: u64,
104 pub subticks_per_tick: u32,
106}
107
108impl OrderMarketParams {
109 pub fn quantize_price(&self, price: Decimal) -> Result<u64, anyhow::Error> {
115 const QUOTE_QUANTUMS_ATOMIC_RESOLUTION: i32 = -6;
116 let exponent = -(self.atomic_resolution
117 - self.quantum_conversion_exponent
118 - QUOTE_QUANTUMS_ATOMIC_RESOLUTION);
119
120 let factor = if exponent < 0 {
123 Decimal::from(10_i64.pow(exponent.unsigned_abs()))
124 } else {
125 Decimal::new(1, exponent.unsigned_abs())
126 };
127
128 let raw_subticks = price * factor;
129 let subticks_per_tick = Decimal::from(self.subticks_per_tick);
130 let quantums = Self::quantize(&raw_subticks, &subticks_per_tick);
131 let result = quantums.max(subticks_per_tick);
132
133 result
134 .to_u64()
135 .ok_or_else(|| anyhow::anyhow!("Failed to convert price to u64"))
136 }
137
138 pub fn quantize_quantity(&self, quantity: Decimal) -> Result<u64, anyhow::Error> {
144 let factor = if self.atomic_resolution < 0 {
147 Decimal::from(10_i64.pow(self.atomic_resolution.unsigned_abs()))
148 } else {
149 Decimal::new(1, self.atomic_resolution.unsigned_abs())
150 };
151
152 let raw_quantums = quantity * factor;
153 let step_base_quantums = Decimal::from(self.step_base_quantums);
154 let quantums = Self::quantize(&raw_quantums, &step_base_quantums);
155 let result = quantums.max(step_base_quantums);
156
157 result
158 .to_u64()
159 .ok_or_else(|| anyhow::anyhow!("Failed to convert quantity to u64"))
160 }
161
162 fn quantize(value: &Decimal, fraction: &Decimal) -> Decimal {
164 (value / fraction).round() * fraction
165 }
166
167 pub fn market_order_subticks(&self, side: OrderSide) -> Result<u64, anyhow::Error> {
173 let oracle = self
174 .oracle_price
175 .ok_or_else(|| anyhow::anyhow!("Oracle price required for market orders"))?;
176 let worst_price = match side {
177 OrderSide::Buy => oracle * (Decimal::ONE + DEFAULT_MARKET_ORDER_SLIPPAGE),
178 OrderSide::Sell => oracle * (Decimal::ONE - DEFAULT_MARKET_ORDER_SLIPPAGE),
179 _ => oracle,
180 };
181 self.quantize_price(worst_price)
182 }
183
184 #[must_use]
186 pub fn clob_pair_id(&self) -> u32 {
187 self.clob_pair_id
188 }
189}
190
191#[derive(Clone, Debug)]
202pub struct OrderBuilder {
203 market_params: OrderMarketParams,
204 subaccount_owner: String,
205 subaccount_number: u32,
206 client_id: u32,
207 client_metadata: u32,
210 flags: OrderFlags,
211 side: Option<OrderSide>,
212 order_type: Option<OrderType>,
213 size: Option<Decimal>,
214 price: Option<Decimal>,
215 time_in_force: Option<OrderTimeInForce>,
216 reduce_only: Option<bool>,
217 until: Option<OrderGoodUntil>,
218 trigger_price: Option<Decimal>,
219 condition_type: Option<ConditionType>,
220}
221
222impl OrderBuilder {
223 #[must_use]
233 pub fn new(
234 market_params: OrderMarketParams,
235 subaccount_owner: String,
236 subaccount_number: u32,
237 client_id: u32,
238 client_metadata: u32,
239 ) -> Self {
240 Self {
241 market_params,
242 subaccount_owner,
243 subaccount_number,
244 client_id,
245 client_metadata,
246 flags: OrderFlags::ShortTerm,
247 side: Some(OrderSide::Buy),
248 order_type: Some(OrderType::Market),
249 size: None,
250 price: None,
251 time_in_force: None,
252 reduce_only: None,
253 until: None,
254 trigger_price: None,
255 condition_type: None,
256 }
257 }
258
259 #[must_use]
264 pub fn market(mut self, side: OrderSide, size: Decimal) -> Self {
265 self.order_type = Some(OrderType::Market);
266 self.side = Some(side);
267 self.size = Some(size);
268 self.time_in_force = Some(OrderTimeInForce::Ioc);
269 self
270 }
271
272 #[must_use]
277 pub fn limit(mut self, side: OrderSide, price: Decimal, size: Decimal) -> Self {
278 self.order_type = Some(OrderType::Limit);
279 self.price = Some(price);
280 self.side = Some(side);
281 self.size = Some(size);
282 self
283 }
284
285 #[must_use]
289 pub fn stop_limit(
290 mut self,
291 side: OrderSide,
292 price: Decimal,
293 trigger_price: Decimal,
294 size: Decimal,
295 ) -> Self {
296 self.order_type = Some(OrderType::StopLimit);
297 self.price = Some(price);
298 self.trigger_price = Some(trigger_price);
299 self.side = Some(side);
300 self.size = Some(size);
301 self.condition_type = Some(ConditionType::StopLoss);
302 self.conditional()
303 }
304
305 #[must_use]
309 pub fn stop_market(mut self, side: OrderSide, trigger_price: Decimal, size: Decimal) -> Self {
310 self.order_type = Some(OrderType::StopMarket);
311 self.trigger_price = Some(trigger_price);
312 self.side = Some(side);
313 self.size = Some(size);
314 self.condition_type = Some(ConditionType::StopLoss);
315 self.conditional()
316 }
317
318 #[must_use]
322 pub fn take_profit_limit(
323 mut self,
324 side: OrderSide,
325 price: Decimal,
326 trigger_price: Decimal,
327 size: Decimal,
328 ) -> Self {
329 self.order_type = Some(OrderType::LimitIfTouched);
330 self.price = Some(price);
331 self.trigger_price = Some(trigger_price);
332 self.side = Some(side);
333 self.size = Some(size);
334 self.condition_type = Some(ConditionType::TakeProfit);
335 self.conditional()
336 }
337
338 #[must_use]
342 pub fn take_profit_market(
343 mut self,
344 side: OrderSide,
345 trigger_price: Decimal,
346 size: Decimal,
347 ) -> Self {
348 self.order_type = Some(OrderType::MarketIfTouched);
349 self.trigger_price = Some(trigger_price);
350 self.side = Some(side);
351 self.size = Some(size);
352 self.condition_type = Some(ConditionType::TakeProfit);
353 self.conditional()
354 }
355
356 #[must_use]
358 pub fn long_term(mut self) -> Self {
359 self.flags = OrderFlags::LongTerm;
360 self
361 }
362
363 #[must_use]
365 pub fn short_term(mut self) -> Self {
366 self.flags = OrderFlags::ShortTerm;
367 self
368 }
369
370 #[must_use]
372 pub fn conditional(mut self) -> Self {
373 self.flags = OrderFlags::Conditional;
374 self
375 }
376
377 #[must_use]
379 pub fn price(mut self, price: Decimal) -> Self {
380 self.price = Some(price);
381 self
382 }
383
384 #[must_use]
386 pub fn size(mut self, size: Decimal) -> Self {
387 self.size = Some(size);
388 self
389 }
390
391 #[must_use]
393 pub fn time_in_force(mut self, tif: OrderTimeInForce) -> Self {
394 self.time_in_force = Some(tif);
395 self
396 }
397
398 #[must_use]
400 pub fn reduce_only(mut self, reduce: bool) -> Self {
401 self.reduce_only = Some(reduce);
402 self
403 }
404
405 #[must_use]
407 pub fn until(mut self, gtof: OrderGoodUntil) -> Self {
408 self.until = Some(gtof);
409 self
410 }
411
412 pub fn build(self) -> Result<Order, anyhow::Error> {
418 let side = self
419 .side
420 .ok_or_else(|| anyhow::anyhow!("Order side not set"))?;
421 let size = self
422 .size
423 .ok_or_else(|| anyhow::anyhow!("Order size not set"))?;
424
425 let quantums = self.market_params.quantize_quantity(size)?;
427
428 let order_id = Some(OrderId {
430 subaccount_id: Some(SubaccountId {
431 owner: self.subaccount_owner.clone(),
432 number: self.subaccount_number,
433 }),
434 client_id: self.client_id,
435 order_flags: match self.flags {
436 OrderFlags::ShortTerm => 0,
437 OrderFlags::LongTerm => 64,
438 OrderFlags::Conditional => 32,
439 },
440 clob_pair_id: self.market_params.clob_pair_id,
441 });
442
443 let until = self
445 .until
446 .ok_or_else(|| anyhow::anyhow!("Order expiration (until) not set"))?;
447
448 let good_til_oneof = match until {
449 OrderGoodUntil::Block(height) => Some(GoodTilOneof::GoodTilBlock(height)),
450 OrderGoodUntil::Time(time) => {
451 Some(GoodTilOneof::GoodTilBlockTime(time.timestamp().try_into()?))
452 }
453 };
454
455 let subticks = if let Some(price) = self.price {
457 self.market_params.quantize_price(price)?
458 } else if matches!(
459 self.order_type,
460 Some(OrderType::Market | OrderType::StopMarket | OrderType::MarketIfTouched)
461 ) {
462 let side = self
463 .side
464 .ok_or_else(|| anyhow::anyhow!("Order side not set"))?;
465 self.market_params.market_order_subticks(side)?
466 } else {
467 0
468 };
469
470 Ok(Order {
471 order_id,
472 side: side as i32,
473 quantums,
474 subticks,
475 good_til_oneof,
476 time_in_force: self.time_in_force.map_or(0, |tif| tif as i32),
477 reduce_only: self.reduce_only.unwrap_or(false),
478 client_metadata: self.client_metadata,
479 condition_type: self.condition_type.map_or(0, |ct| ct as i32),
480 conditional_order_trigger_subticks: self
481 .trigger_price
482 .map(|tp| self.market_params.quantize_price(tp))
483 .transpose()?
484 .unwrap_or(0),
485 twap_parameters: None,
486 builder_code_parameters: None,
487 order_router_address: DYDX_NAUTILUS_ORDER_ROUTER_ADDRESS.to_string(),
488 })
489 }
490}
491
492impl Default for OrderBuilder {
493 fn default() -> Self {
494 Self {
495 market_params: OrderMarketParams {
496 atomic_resolution: -10,
497 clob_pair_id: 0,
498 oracle_price: Some(Decimal::from(50_000)),
499 quantum_conversion_exponent: -9,
500 step_base_quantums: 1_000_000,
501 subticks_per_tick: 100_000,
502 },
503 subaccount_owner: String::new(),
504 subaccount_number: 0,
505 client_id: 0,
506 client_metadata: DEFAULT_RUST_CLIENT_METADATA,
507 flags: OrderFlags::ShortTerm,
508 side: Some(OrderSide::Buy),
509 order_type: Some(OrderType::Market),
510 size: None,
511 price: None,
512 time_in_force: None,
513 reduce_only: None,
514 until: None,
515 trigger_price: None,
516 condition_type: None,
517 }
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use rstest::rstest;
524 use rust_decimal_macros::dec;
525
526 use super::*;
527
528 fn sample_market_params() -> OrderMarketParams {
529 OrderMarketParams {
530 atomic_resolution: -10,
531 clob_pair_id: 0,
532 oracle_price: Some(dec!(50000)),
533 quantum_conversion_exponent: -9,
534 step_base_quantums: 1_000_000,
535 subticks_per_tick: 100_000,
536 }
537 }
538
539 #[rstest]
540 fn test_market_params_quantize_price() {
541 let market = sample_market_params();
542 let price = dec!(50000);
543 let subticks = market.quantize_price(price).unwrap();
544 assert_eq!(subticks, 5_000_000_000);
547 }
548
549 #[rstest]
550 fn test_market_params_quantize_quantity() {
551 let market = sample_market_params();
552 let quantity = dec!(0.01);
553 let quantums = market.quantize_quantity(quantity).unwrap();
554 assert_eq!(quantums, 100_000_000);
557 }
558
559 #[rstest]
560 fn test_quantize_price_rounding_up() {
561 let market = sample_market_params();
562 let price = dec!(50000.6);
564 let subticks = market.quantize_price(price).unwrap();
565 assert_eq!(subticks, 5_000_100_000);
566 }
567
568 #[rstest]
569 fn test_quantize_price_rounding_down() {
570 let market = sample_market_params();
571 let price = dec!(49999.4);
573 let subticks = market.quantize_price(price).unwrap();
574 assert_eq!(subticks, 4_999_900_000);
575 }
576
577 #[rstest]
578 fn test_quantize_quantity_rounding_up() {
579 let market = sample_market_params();
580 let quantity = dec!(0.0105); let quantums = market.quantize_quantity(quantity).unwrap();
583 assert_eq!(quantums, 105_000_000);
584 }
585
586 #[rstest]
587 fn test_quantize_quantity_rounding_down() {
588 let market = sample_market_params();
589 let quantity = dec!(0.0104); let quantums = market.quantize_quantity(quantity).unwrap();
592 assert_eq!(quantums, 104_000_000);
593 }
594
595 #[rstest]
596 fn test_quantize_price_minimum_tick() {
597 let market = sample_market_params();
598 let price = dec!(0.001);
600 let subticks = market.quantize_price(price).unwrap();
601 assert_eq!(subticks, market.subticks_per_tick as u64);
602 }
603
604 #[rstest]
605 fn test_quantize_quantity_minimum_quantum() {
606 let market = sample_market_params();
607 let quantity = dec!(0.00000001);
609 let quantums = market.quantize_quantity(quantity).unwrap();
610 assert_eq!(quantums, market.step_base_quantums);
611 }
612
613 #[rstest]
614 fn test_quantize_price_large_values() {
615 let market = sample_market_params();
616 let price = dec!(100000);
618 let subticks = market.quantize_price(price).unwrap();
619 assert_eq!(subticks, 10_000_000_000);
620 }
621
622 #[rstest]
623 fn test_quantize_quantity_large_values() {
624 let market = sample_market_params();
625 let quantity = dec!(10);
627 let quantums = market.quantize_quantity(quantity).unwrap();
628 assert_eq!(quantums, 100_000_000_000);
629 }
630
631 #[rstest]
632 fn test_order_builder_market_buy() {
633 let market = sample_market_params();
634 let builder = OrderBuilder::new(
635 market,
636 "dydx1test".to_string(),
637 0,
638 1,
639 DEFAULT_RUST_CLIENT_METADATA,
640 );
641
642 let order = builder
643 .market(OrderSide::Buy, dec!(0.01))
644 .until(OrderGoodUntil::Block(100))
645 .build()
646 .unwrap();
647
648 assert_eq!(order.side, OrderSide::Buy as i32);
649 assert_eq!(order.quantums, 100_000_000); assert_eq!(order.subticks, 5_250_000_000); assert_eq!(order.time_in_force, OrderTimeInForce::Ioc as i32);
652 assert!(!order.reduce_only);
653 assert_eq!(order.client_metadata, DEFAULT_RUST_CLIENT_METADATA);
654 }
655
656 #[rstest]
657 fn test_order_builder_market_sell() {
658 let market = sample_market_params();
659 let builder = OrderBuilder::new(
660 market,
661 "dydx1test".to_string(),
662 0,
663 2,
664 DEFAULT_RUST_CLIENT_METADATA,
665 );
666
667 let order = builder
668 .market(OrderSide::Sell, dec!(0.02))
669 .until(OrderGoodUntil::Block(100))
670 .build()
671 .unwrap();
672
673 assert_eq!(order.side, OrderSide::Sell as i32);
674 assert_eq!(order.quantums, 200_000_000); assert_eq!(order.subticks, 4_750_000_000); assert_eq!(order.time_in_force, OrderTimeInForce::Ioc as i32);
677 }
678
679 #[rstest]
680 fn test_order_builder_market_no_oracle_price_error() {
681 let mut market = sample_market_params();
682 market.oracle_price = None;
683
684 let builder = OrderBuilder::new(
685 market,
686 "dydx1test".to_string(),
687 0,
688 13,
689 DEFAULT_RUST_CLIENT_METADATA,
690 );
691
692 let result = builder
693 .market(OrderSide::Buy, dec!(0.01))
694 .until(OrderGoodUntil::Block(100))
695 .build();
696
697 assert!(result.is_err());
698 assert!(
699 result
700 .unwrap_err()
701 .to_string()
702 .contains("Oracle price required")
703 );
704 }
705
706 #[rstest]
707 fn test_order_builder_limit_buy() {
708 let market = sample_market_params();
709 let builder = OrderBuilder::new(
710 market,
711 "dydx1test".to_string(),
712 0,
713 3,
714 DEFAULT_RUST_CLIENT_METADATA,
715 );
716
717 let order = builder
718 .limit(OrderSide::Buy, dec!(49000), dec!(0.01))
719 .until(OrderGoodUntil::Block(100))
720 .build()
721 .unwrap();
722
723 assert_eq!(order.side, OrderSide::Buy as i32);
724 assert_eq!(order.quantums, 100_000_000); assert_eq!(order.subticks, 4_900_000_000); assert!(!order.reduce_only);
727 }
728
729 #[rstest]
730 fn test_order_builder_limit_sell() {
731 let market = sample_market_params();
732 let builder = OrderBuilder::new(
733 market,
734 "dydx1test".to_string(),
735 0,
736 4,
737 DEFAULT_RUST_CLIENT_METADATA,
738 );
739
740 let order = builder
741 .limit(OrderSide::Sell, dec!(51000), dec!(0.015))
742 .until(OrderGoodUntil::Block(100))
743 .build()
744 .unwrap();
745
746 assert_eq!(order.side, OrderSide::Sell as i32);
747 assert_eq!(order.quantums, 150_000_000); assert_eq!(order.subticks, 5_100_000_000); }
750
751 #[rstest]
752 fn test_order_builder_limit_with_reduce_only() {
753 let market = sample_market_params();
754 let builder = OrderBuilder::new(
755 market,
756 "dydx1test".to_string(),
757 0,
758 5,
759 DEFAULT_RUST_CLIENT_METADATA,
760 );
761
762 let order = builder
763 .limit(OrderSide::Sell, dec!(50000), dec!(0.01))
764 .reduce_only(true)
765 .until(OrderGoodUntil::Block(100))
766 .build()
767 .unwrap();
768
769 assert!(order.reduce_only);
770 }
771
772 #[rstest]
773 fn test_order_builder_short_term_flag() {
774 let market = sample_market_params();
775 let builder = OrderBuilder::new(
776 market,
777 "dydx1test".to_string(),
778 0,
779 6,
780 DEFAULT_RUST_CLIENT_METADATA,
781 );
782
783 let order = builder
784 .short_term()
785 .market(OrderSide::Buy, dec!(0.01))
786 .until(OrderGoodUntil::Block(100))
787 .build()
788 .unwrap();
789
790 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 0);
792 }
793
794 #[rstest]
795 fn test_order_builder_long_term_flag() {
796 let market = sample_market_params();
797 let builder = OrderBuilder::new(
798 market,
799 "dydx1test".to_string(),
800 0,
801 7,
802 DEFAULT_RUST_CLIENT_METADATA,
803 );
804
805 let now = Utc::now();
806 let until = now + Duration::hours(1);
807
808 let order = builder
809 .long_term()
810 .limit(OrderSide::Buy, dec!(50000), dec!(0.01))
811 .until(OrderGoodUntil::Time(until))
812 .build()
813 .unwrap();
814
815 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 64);
817 }
818
819 #[rstest]
820 fn test_order_builder_conditional_flag() {
821 let market = sample_market_params();
822 let builder = OrderBuilder::new(
823 market,
824 "dydx1test".to_string(),
825 0,
826 8,
827 DEFAULT_RUST_CLIENT_METADATA,
828 );
829
830 let order = builder
831 .stop_limit(OrderSide::Sell, dec!(48000), dec!(49000), dec!(0.01))
832 .until(OrderGoodUntil::Block(100))
833 .build()
834 .unwrap();
835
836 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 32);
838 assert_eq!(order.conditional_order_trigger_subticks, 4_900_000_000);
839 }
840
841 #[rstest]
842 fn test_stop_limit_sets_condition_type() {
843 let market = sample_market_params();
844 let builder = OrderBuilder::new(
845 market,
846 "dydx1test".to_string(),
847 0,
848 100,
849 DEFAULT_RUST_CLIENT_METADATA,
850 );
851
852 let order = builder
853 .stop_limit(OrderSide::Sell, dec!(48000), dec!(49000), dec!(0.01))
854 .until(OrderGoodUntil::Block(100))
855 .build()
856 .unwrap();
857
858 assert_eq!(order.condition_type, ConditionType::StopLoss as i32);
859 }
860
861 #[rstest]
862 fn test_stop_market_sets_condition_type() {
863 let market = sample_market_params();
864 let builder = OrderBuilder::new(
865 market,
866 "dydx1test".to_string(),
867 0,
868 101,
869 DEFAULT_RUST_CLIENT_METADATA,
870 );
871
872 let order = builder
873 .stop_market(OrderSide::Sell, dec!(49000), dec!(0.01))
874 .until(OrderGoodUntil::Block(100))
875 .build()
876 .unwrap();
877
878 assert_eq!(order.condition_type, ConditionType::StopLoss as i32);
879 }
880
881 #[rstest]
882 fn test_take_profit_limit_sets_condition_type() {
883 let market = sample_market_params();
884 let builder = OrderBuilder::new(
885 market,
886 "dydx1test".to_string(),
887 0,
888 102,
889 DEFAULT_RUST_CLIENT_METADATA,
890 );
891
892 let order = builder
893 .take_profit_limit(OrderSide::Sell, dec!(52000), dec!(51000), dec!(0.01))
894 .until(OrderGoodUntil::Block(100))
895 .build()
896 .unwrap();
897
898 assert_eq!(order.condition_type, ConditionType::TakeProfit as i32);
899 }
900
901 #[rstest]
902 fn test_take_profit_market_sets_condition_type() {
903 let market = sample_market_params();
904 let builder = OrderBuilder::new(
905 market,
906 "dydx1test".to_string(),
907 0,
908 103,
909 DEFAULT_RUST_CLIENT_METADATA,
910 );
911
912 let order = builder
913 .take_profit_market(OrderSide::Sell, dec!(51000), dec!(0.01))
914 .until(OrderGoodUntil::Block(100))
915 .build()
916 .unwrap();
917
918 assert_eq!(order.condition_type, ConditionType::TakeProfit as i32);
919 }
920
921 #[rstest]
922 fn test_order_builder_missing_size_error() {
923 let market = sample_market_params();
924 let builder = OrderBuilder::new(
925 market,
926 "dydx1test".to_string(),
927 0,
928 9,
929 DEFAULT_RUST_CLIENT_METADATA,
930 );
931
932 let result = builder.until(OrderGoodUntil::Block(100)).build();
933
934 assert!(result.is_err());
935 assert!(result.unwrap_err().to_string().contains("size"));
936 }
937
938 #[rstest]
939 fn test_order_builder_missing_until_error() {
940 let market = sample_market_params();
941 let builder = OrderBuilder::new(
942 market,
943 "dydx1test".to_string(),
944 0,
945 10,
946 DEFAULT_RUST_CLIENT_METADATA,
947 );
948
949 let result = builder.market(OrderSide::Buy, dec!(0.01)).build();
950
951 assert!(result.is_err());
952 }
953
954 #[rstest]
955 fn test_order_builder_time_in_force() {
956 let market = sample_market_params();
957 let builder = OrderBuilder::new(
958 market,
959 "dydx1test".to_string(),
960 0,
961 11,
962 DEFAULT_RUST_CLIENT_METADATA,
963 );
964
965 let order = builder
966 .limit(OrderSide::Buy, dec!(50000), dec!(0.01))
967 .time_in_force(OrderTimeInForce::Ioc)
968 .until(OrderGoodUntil::Block(100))
969 .build()
970 .unwrap();
971
972 assert_eq!(order.time_in_force, OrderTimeInForce::Ioc as i32);
973 }
974
975 #[rstest]
976 fn test_order_builder_clob_pair_id() {
977 let mut market = sample_market_params();
978 market.clob_pair_id = 5;
979
980 let builder = OrderBuilder::new(
981 market,
982 "dydx1test".to_string(),
983 0,
984 12,
985 DEFAULT_RUST_CLIENT_METADATA,
986 );
987
988 let order = builder
989 .market(OrderSide::Buy, dec!(0.01))
990 .until(OrderGoodUntil::Block(100))
991 .build()
992 .unwrap();
993
994 assert_eq!(order.order_id.as_ref().unwrap().clob_pair_id, 5);
995 }
996}