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.conditional()
258 }
259
260 #[must_use]
264 pub fn stop_market(mut self, side: OrderSide, trigger_price: Decimal, size: Decimal) -> Self {
265 self.order_type = Some(OrderType::StopMarket);
266 self.trigger_price = Some(trigger_price);
267 self.side = Some(side);
268 self.size = Some(size);
269 self.conditional()
270 }
271
272 #[must_use]
276 pub fn take_profit_limit(
277 mut self,
278 side: OrderSide,
279 price: Decimal,
280 trigger_price: Decimal,
281 size: Decimal,
282 ) -> Self {
283 self.order_type = Some(OrderType::LimitIfTouched);
284 self.price = Some(price);
285 self.trigger_price = Some(trigger_price);
286 self.side = Some(side);
287 self.size = Some(size);
288 self.conditional()
289 }
290
291 #[must_use]
295 pub fn take_profit_market(
296 mut self,
297 side: OrderSide,
298 trigger_price: Decimal,
299 size: Decimal,
300 ) -> Self {
301 self.order_type = Some(OrderType::MarketIfTouched);
302 self.trigger_price = Some(trigger_price);
303 self.side = Some(side);
304 self.size = Some(size);
305 self.conditional()
306 }
307
308 #[must_use]
310 pub fn long_term(mut self) -> Self {
311 self.flags = OrderFlags::LongTerm;
312 self
313 }
314
315 #[must_use]
317 pub fn short_term(mut self) -> Self {
318 self.flags = OrderFlags::ShortTerm;
319 self
320 }
321
322 #[must_use]
324 pub fn conditional(mut self) -> Self {
325 self.flags = OrderFlags::Conditional;
326 self
327 }
328
329 #[must_use]
331 pub fn price(mut self, price: Decimal) -> Self {
332 self.price = Some(price);
333 self
334 }
335
336 #[must_use]
338 pub fn size(mut self, size: Decimal) -> Self {
339 self.size = Some(size);
340 self
341 }
342
343 #[must_use]
345 pub fn time_in_force(mut self, tif: OrderTimeInForce) -> Self {
346 self.time_in_force = Some(tif);
347 self
348 }
349
350 #[must_use]
352 pub fn reduce_only(mut self, reduce: bool) -> Self {
353 self.reduce_only = Some(reduce);
354 self
355 }
356
357 #[must_use]
359 pub fn until(mut self, gtof: OrderGoodUntil) -> Self {
360 self.until = Some(gtof);
361 self
362 }
363
364 pub fn build(self) -> Result<Order, anyhow::Error> {
370 let side = self
371 .side
372 .ok_or_else(|| anyhow::anyhow!("Order side not set"))?;
373 let size = self
374 .size
375 .ok_or_else(|| anyhow::anyhow!("Order size not set"))?;
376
377 let quantums = self.market_params.quantize_quantity(size)?;
379
380 let order_id = Some(OrderId {
382 subaccount_id: Some(SubaccountId {
383 owner: self.subaccount_owner.clone(),
384 number: self.subaccount_number,
385 }),
386 client_id: self.client_id,
387 order_flags: match self.flags {
388 OrderFlags::ShortTerm => 0,
389 OrderFlags::LongTerm => 64,
390 OrderFlags::Conditional => 32,
391 },
392 clob_pair_id: self.market_params.clob_pair_id,
393 });
394
395 let until = self
397 .until
398 .ok_or_else(|| anyhow::anyhow!("Order expiration (until) not set"))?;
399
400 let good_til_oneof = match until {
401 OrderGoodUntil::Block(height) => Some(GoodTilOneof::GoodTilBlock(height)),
402 OrderGoodUntil::Time(time) => {
403 Some(GoodTilOneof::GoodTilBlockTime(time.timestamp().try_into()?))
404 }
405 };
406
407 let subticks = if let Some(price) = self.price {
409 self.market_params.quantize_price(price)?
410 } else {
411 0
412 };
413
414 Ok(Order {
415 order_id,
416 side: side as i32,
417 quantums,
418 subticks,
419 good_til_oneof,
420 time_in_force: self.time_in_force.map_or(0, |tif| tif as i32),
421 reduce_only: self.reduce_only.unwrap_or(false),
422 client_metadata: DEFAULT_RUST_CLIENT_METADATA,
423 condition_type: self.condition_type.map_or(0, |ct| ct as i32),
424 conditional_order_trigger_subticks: self
425 .trigger_price
426 .map(|tp| self.market_params.quantize_price(tp))
427 .transpose()?
428 .unwrap_or(0),
429 twap_parameters: None,
430 builder_code_parameters: None,
431 order_router_address: String::new(),
432 })
433 }
434}
435
436impl Default for OrderBuilder {
437 fn default() -> Self {
438 Self {
439 market_params: OrderMarketParams {
440 atomic_resolution: -10,
441 clob_pair_id: 0,
442 oracle_price: None,
443 quantum_conversion_exponent: -9,
444 step_base_quantums: 1_000_000,
445 subticks_per_tick: 100_000,
446 },
447 subaccount_owner: String::new(),
448 subaccount_number: 0,
449 client_id: 0,
450 flags: OrderFlags::ShortTerm,
451 side: Some(OrderSide::Buy),
452 order_type: Some(OrderType::Market),
453 size: None,
454 price: None,
455 time_in_force: None,
456 reduce_only: None,
457 until: None,
458 trigger_price: None,
459 condition_type: None,
460 }
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use rstest::rstest;
467 use rust_decimal_macros::dec;
468
469 use super::*;
470
471 fn sample_market_params() -> OrderMarketParams {
472 OrderMarketParams {
473 atomic_resolution: -10,
474 clob_pair_id: 0,
475 oracle_price: Some(dec!(50000)),
476 quantum_conversion_exponent: -9,
477 step_base_quantums: 1_000_000,
478 subticks_per_tick: 100_000,
479 }
480 }
481
482 #[rstest]
483 fn test_market_params_quantize_price() {
484 let market = sample_market_params();
485 let price = dec!(50000);
486 let subticks = market.quantize_price(price).unwrap();
487 assert_eq!(subticks, 5_000_000_000);
490 }
491
492 #[rstest]
493 fn test_market_params_quantize_quantity() {
494 let market = sample_market_params();
495 let quantity = dec!(0.01);
496 let quantums = market.quantize_quantity(quantity).unwrap();
497 assert_eq!(quantums, 100_000_000);
500 }
501
502 #[rstest]
503 fn test_quantize_price_rounding_up() {
504 let market = sample_market_params();
505 let price = dec!(50000.6);
507 let subticks = market.quantize_price(price).unwrap();
508 assert_eq!(subticks, 5_000_100_000);
509 }
510
511 #[rstest]
512 fn test_quantize_price_rounding_down() {
513 let market = sample_market_params();
514 let price = dec!(49999.4);
516 let subticks = market.quantize_price(price).unwrap();
517 assert_eq!(subticks, 4_999_900_000);
518 }
519
520 #[rstest]
521 fn test_quantize_quantity_rounding_up() {
522 let market = sample_market_params();
523 let quantity = dec!(0.0105); let quantums = market.quantize_quantity(quantity).unwrap();
526 assert_eq!(quantums, 105_000_000);
527 }
528
529 #[rstest]
530 fn test_quantize_quantity_rounding_down() {
531 let market = sample_market_params();
532 let quantity = dec!(0.0104); let quantums = market.quantize_quantity(quantity).unwrap();
535 assert_eq!(quantums, 104_000_000);
536 }
537
538 #[rstest]
539 fn test_quantize_price_minimum_tick() {
540 let market = sample_market_params();
541 let price = dec!(0.001);
543 let subticks = market.quantize_price(price).unwrap();
544 assert_eq!(subticks, market.subticks_per_tick as u64);
545 }
546
547 #[rstest]
548 fn test_quantize_quantity_minimum_quantum() {
549 let market = sample_market_params();
550 let quantity = dec!(0.00000001);
552 let quantums = market.quantize_quantity(quantity).unwrap();
553 assert_eq!(quantums, market.step_base_quantums);
554 }
555
556 #[rstest]
557 fn test_quantize_price_large_values() {
558 let market = sample_market_params();
559 let price = dec!(100000);
561 let subticks = market.quantize_price(price).unwrap();
562 assert_eq!(subticks, 10_000_000_000);
563 }
564
565 #[rstest]
566 fn test_quantize_quantity_large_values() {
567 let market = sample_market_params();
568 let quantity = dec!(10);
570 let quantums = market.quantize_quantity(quantity).unwrap();
571 assert_eq!(quantums, 100_000_000_000);
572 }
573
574 #[rstest]
575 fn test_order_builder_market_buy() {
576 let market = sample_market_params();
577 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 1);
578
579 let order = builder
580 .market(OrderSide::Buy, dec!(0.01))
581 .until(OrderGoodUntil::Block(100))
582 .build()
583 .unwrap();
584
585 assert_eq!(order.side, OrderSide::Buy as i32);
586 assert_eq!(order.quantums, 100_000_000); assert_eq!(order.subticks, 0); assert!(!order.reduce_only);
589 assert_eq!(order.client_metadata, DEFAULT_RUST_CLIENT_METADATA);
590 }
591
592 #[rstest]
593 fn test_order_builder_market_sell() {
594 let market = sample_market_params();
595 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 2);
596
597 let order = builder
598 .market(OrderSide::Sell, dec!(0.02))
599 .until(OrderGoodUntil::Block(100))
600 .build()
601 .unwrap();
602
603 assert_eq!(order.side, OrderSide::Sell as i32);
604 assert_eq!(order.quantums, 200_000_000); }
606
607 #[rstest]
608 fn test_order_builder_limit_buy() {
609 let market = sample_market_params();
610 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 3);
611
612 let order = builder
613 .limit(OrderSide::Buy, dec!(49000), dec!(0.01))
614 .until(OrderGoodUntil::Block(100))
615 .build()
616 .unwrap();
617
618 assert_eq!(order.side, OrderSide::Buy as i32);
619 assert_eq!(order.quantums, 100_000_000); assert_eq!(order.subticks, 4_900_000_000); assert!(!order.reduce_only);
622 }
623
624 #[rstest]
625 fn test_order_builder_limit_sell() {
626 let market = sample_market_params();
627 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 4);
628
629 let order = builder
630 .limit(OrderSide::Sell, dec!(51000), dec!(0.015))
631 .until(OrderGoodUntil::Block(100))
632 .build()
633 .unwrap();
634
635 assert_eq!(order.side, OrderSide::Sell as i32);
636 assert_eq!(order.quantums, 150_000_000); assert_eq!(order.subticks, 5_100_000_000); }
639
640 #[rstest]
641 fn test_order_builder_limit_with_reduce_only() {
642 let market = sample_market_params();
643 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 5);
644
645 let order = builder
646 .limit(OrderSide::Sell, dec!(50000), dec!(0.01))
647 .reduce_only(true)
648 .until(OrderGoodUntil::Block(100))
649 .build()
650 .unwrap();
651
652 assert!(order.reduce_only);
653 }
654
655 #[rstest]
656 fn test_order_builder_short_term_flag() {
657 let market = sample_market_params();
658 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 6);
659
660 let order = builder
661 .short_term()
662 .market(OrderSide::Buy, dec!(0.01))
663 .until(OrderGoodUntil::Block(100))
664 .build()
665 .unwrap();
666
667 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 0);
669 }
670
671 #[rstest]
672 fn test_order_builder_long_term_flag() {
673 let market = sample_market_params();
674 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 7);
675
676 let now = Utc::now();
677 let until = now + Duration::hours(1);
678
679 let order = builder
680 .long_term()
681 .limit(OrderSide::Buy, dec!(50000), dec!(0.01))
682 .until(OrderGoodUntil::Time(until))
683 .build()
684 .unwrap();
685
686 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 64);
688 }
689
690 #[rstest]
691 fn test_order_builder_conditional_flag() {
692 let market = sample_market_params();
693 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 8);
694
695 let order = builder
696 .stop_limit(OrderSide::Sell, dec!(48000), dec!(49000), dec!(0.01))
697 .until(OrderGoodUntil::Block(100))
698 .build()
699 .unwrap();
700
701 assert_eq!(order.order_id.as_ref().unwrap().order_flags, 32);
703 assert_eq!(order.conditional_order_trigger_subticks, 4_900_000_000);
704 }
705
706 #[rstest]
707 fn test_order_builder_missing_size_error() {
708 let market = sample_market_params();
709 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 9);
710
711 let result = builder.until(OrderGoodUntil::Block(100)).build();
712
713 assert!(result.is_err());
714 assert!(result.unwrap_err().to_string().contains("size"));
715 }
716
717 #[rstest]
718 fn test_order_builder_missing_until_error() {
719 let market = sample_market_params();
720 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 10);
721
722 let result = builder.market(OrderSide::Buy, dec!(0.01)).build();
723
724 assert!(result.is_err());
725 }
726
727 #[rstest]
728 fn test_order_builder_time_in_force() {
729 let market = sample_market_params();
730 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 11);
731
732 let order = builder
733 .limit(OrderSide::Buy, dec!(50000), dec!(0.01))
734 .time_in_force(OrderTimeInForce::Ioc)
735 .until(OrderGoodUntil::Block(100))
736 .build()
737 .unwrap();
738
739 assert_eq!(order.time_in_force, OrderTimeInForce::Ioc as i32);
740 }
741
742 #[rstest]
743 fn test_order_builder_clob_pair_id() {
744 let mut market = sample_market_params();
745 market.clob_pair_id = 5;
746
747 let builder = OrderBuilder::new(market, "dydx1test".to_string(), 0, 12);
748
749 let order = builder
750 .market(OrderSide::Buy, dec!(0.01))
751 .until(OrderGoodUntil::Block(100))
752 .build()
753 .unwrap();
754
755 assert_eq!(order.order_id.as_ref().unwrap().clob_pair_id, 5);
756 }
757}