1use std::sync::atomic::{AtomicU64, Ordering};
31
32use nautilus_core::time::get_atomic_clock_realtime;
33use nautilus_model::{
34 enums::{OrderSide, OrderType, TimeInForce},
35 orders::{Order, OrderAny},
36};
37use rust_decimal::Decimal;
38use ustr::Ustr;
39
40use crate::{
41 common::{
42 consts::{LOT_SIZE_SCALE, POLYMARKET_NAUTILUS_BUILDER_CODE, USDC_DECIMALS},
43 enums::{PolymarketOrderSide, PolymarketOrderType, SignatureType},
44 },
45 http::models::PolymarketOrder,
46 signing::eip712::OrderSigner,
47};
48
49pub const ZERO_BYTES32: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
51
52#[derive(Debug)]
58pub struct PolymarketOrderBuilder {
59 order_signer: OrderSigner,
60 signer_address: String,
61 maker_address: String,
62 signature_type: SignatureType,
63 last_timestamp_ms: AtomicU64,
64}
65
66impl PolymarketOrderBuilder {
67 pub fn new(
69 order_signer: OrderSigner,
70 signer_address: String,
71 maker_address: String,
72 signature_type: SignatureType,
73 ) -> Self {
74 Self {
75 order_signer,
76 signer_address,
77 maker_address,
78 signature_type,
79 last_timestamp_ms: AtomicU64::new(0),
80 }
81 }
82
83 fn next_timestamp_ms(&self) -> u64 {
86 let now_ms = get_atomic_clock_realtime().get_time_ns().as_u64() / 1_000_000;
87
88 loop {
89 let prev = self.last_timestamp_ms.load(Ordering::Relaxed);
90 let candidate = prev.saturating_add(1).max(now_ms);
91
92 if self
93 .last_timestamp_ms
94 .compare_exchange_weak(prev, candidate, Ordering::Relaxed, Ordering::Relaxed)
95 .is_ok()
96 {
97 return candidate;
98 }
99 }
100 }
101
102 #[expect(clippy::too_many_arguments)]
107 pub fn build_limit_order(
108 &self,
109 token_id: &str,
110 side: PolymarketOrderSide,
111 price: Decimal,
112 quantity: Decimal,
113 expiration: &str,
114 neg_risk: bool,
115 tick_decimals: u32,
116 ) -> anyhow::Result<PolymarketOrder> {
117 let (maker_amount, taker_amount) =
118 compute_maker_taker_amounts(price, quantity, side, tick_decimals);
119 self.build_and_sign(
120 token_id,
121 side,
122 maker_amount,
123 taker_amount,
124 expiration,
125 neg_risk,
126 )
127 }
128
129 pub fn build_market_order(
137 &self,
138 token_id: &str,
139 side: PolymarketOrderSide,
140 price: Decimal,
141 amount: Decimal,
142 neg_risk: bool,
143 tick_decimals: u32,
144 ) -> anyhow::Result<PolymarketOrder> {
145 let (maker_amount, taker_amount) =
146 compute_market_maker_taker_amounts(price, amount, side, tick_decimals);
147 self.build_and_sign(token_id, side, maker_amount, taker_amount, "0", neg_risk)
148 }
149
150 pub fn validate_limit_order(order: &OrderAny) -> Result<(), String> {
152 if order.is_reduce_only() {
153 return Err("Reduce-only orders not supported on Polymarket".to_string());
154 }
155
156 if order.order_type() != OrderType::Limit {
157 return Err(format!(
158 "Unsupported order type for Polymarket: {:?}",
159 order.order_type()
160 ));
161 }
162
163 if order.is_quote_quantity() {
164 return Err("Quote quantity not supported for limit orders".to_string());
165 }
166
167 if order.price().is_none() {
168 return Err("Limit orders require a price".to_string());
169 }
170
171 if PolymarketOrderType::try_from(order.time_in_force()).is_err() {
172 return Err(format!(
173 "Unsupported time in force: {:?}",
174 order.time_in_force()
175 ));
176 }
177
178 if PolymarketOrderSide::try_from(order.order_side()).is_err() {
179 return Err(format!("Invalid order side: {:?}", order.order_side()));
180 }
181
182 if order.is_post_only()
183 && !matches!(order.time_in_force(), TimeInForce::Gtc | TimeInForce::Gtd)
184 {
185 return Err("Post-only orders require GTC or GTD time in force".to_string());
186 }
187
188 Ok(())
189 }
190
191 pub fn validate_market_order(order: &OrderAny) -> Result<(), String> {
193 if order.is_reduce_only() {
194 return Err("Reduce-only orders not supported on Polymarket".to_string());
195 }
196
197 if order.order_type() != OrderType::Market {
198 return Err(format!(
199 "Expected Market order, was {:?}",
200 order.order_type()
201 ));
202 }
203
204 match order.order_side() {
207 OrderSide::Buy => {
208 if !order.is_quote_quantity() {
209 return Err(
210 "Market BUY orders require quote_quantity=true (amount in pUSD)"
211 .to_string(),
212 );
213 }
214 }
215 OrderSide::Sell => {
216 if order.is_quote_quantity() {
217 return Err(
218 "Market SELL orders require quote_quantity=false (amount in shares)"
219 .to_string(),
220 );
221 }
222 }
223 _ => {
224 return Err(format!("Invalid order side: {:?}", order.order_side()));
225 }
226 }
227
228 Ok(())
229 }
230
231 fn build_and_sign(
232 &self,
233 token_id: &str,
234 side: PolymarketOrderSide,
235 maker_amount: Decimal,
236 taker_amount: Decimal,
237 expiration: &str,
238 neg_risk: bool,
239 ) -> anyhow::Result<PolymarketOrder> {
240 let salt = generate_salt();
241 let timestamp_ms = self.next_timestamp_ms();
242
243 let mut poly_order = PolymarketOrder {
244 salt,
245 maker: self.maker_address.clone(),
246 signer: self.signer_address.clone(),
247 token_id: Ustr::from(token_id),
248 maker_amount,
249 taker_amount,
250 side,
251 signature_type: self.signature_type,
252 expiration: expiration.to_string(),
253 timestamp: timestamp_ms.to_string(),
254 metadata: ZERO_BYTES32.to_string(),
255 builder: POLYMARKET_NAUTILUS_BUILDER_CODE.to_string(),
256 signature: String::new(),
257 };
258
259 let signature = self
260 .order_signer
261 .sign_order(&poly_order, neg_risk)
262 .map_err(|e| anyhow::anyhow!("EIP-712 signing failed: {e}"))?;
263 poly_order.signature = signature;
264
265 Ok(poly_order)
266 }
267}
268
269fn to_fixed_decimal(d: Decimal) -> Decimal {
270 let mantissa = d.normalize().trunc_with_scale(USDC_DECIMALS).mantissa();
271 Decimal::from(mantissa)
272}
273
274pub fn compute_maker_taker_amounts(
283 price: Decimal,
284 quantity: Decimal,
285 side: PolymarketOrderSide,
286 tick_decimals: u32,
287) -> (Decimal, Decimal) {
288 let precision = tick_decimals + LOT_SIZE_SCALE;
289 let qty = quantity.trunc_with_scale(LOT_SIZE_SCALE);
290
291 match side {
292 PolymarketOrderSide::Buy => {
293 let maker_amount = to_fixed_decimal((qty * price).trunc_with_scale(precision));
294 let taker_amount = to_fixed_decimal(qty);
295 (maker_amount, taker_amount)
296 }
297 PolymarketOrderSide::Sell => {
298 let maker_amount = to_fixed_decimal(qty);
299 let taker_amount = to_fixed_decimal((qty * price).trunc_with_scale(precision));
300 (maker_amount, taker_amount)
301 }
302 }
303}
304
305pub fn compute_market_maker_taker_amounts(
315 price: Decimal,
316 amount: Decimal,
317 side: PolymarketOrderSide,
318 tick_decimals: u32,
319) -> (Decimal, Decimal) {
320 let precision = tick_decimals + LOT_SIZE_SCALE;
321 let amt = amount.trunc_with_scale(LOT_SIZE_SCALE);
322
323 match side {
324 PolymarketOrderSide::Buy => {
325 let maker_amount = to_fixed_decimal(amt);
326 let taker_amount = to_fixed_decimal((amt / price).trunc_with_scale(precision));
327 (maker_amount, taker_amount)
328 }
329 PolymarketOrderSide::Sell => {
330 let maker_amount = to_fixed_decimal(amt);
331 let taker_amount = to_fixed_decimal((amt * price).trunc_with_scale(precision));
332 (maker_amount, taker_amount)
333 }
334 }
335}
336
337pub fn generate_salt() -> u64 {
343 let bytes = uuid::Uuid::new_v4().into_bytes();
344 u64::from_le_bytes(bytes[..8].try_into().unwrap()) & ((1u64 << 53) - 1)
345}
346
347#[cfg(test)]
348mod tests {
349 use nautilus_core::{UUID4, UnixNanos};
350 use nautilus_model::{
351 enums::{OrderSide, TimeInForce},
352 identifiers::{ClientOrderId, InstrumentId, StrategyId, TraderId},
353 orders::{LimitOrder, MarketOrder, OrderAny},
354 types::{Price, Quantity},
355 };
356 use rstest::rstest;
357 use rust_decimal::Decimal;
358 use rust_decimal_macros::dec;
359
360 use super::*;
361 use crate::common::enums::PolymarketOrderSide;
362
363 fn make_limit(
364 reduce_only: bool,
365 quote_quantity: bool,
366 post_only: bool,
367 tif: TimeInForce,
368 ) -> OrderAny {
369 let expire_time = if tif == TimeInForce::Gtd {
370 Some(UnixNanos::from(2_000_000_000_000_000_000u64))
371 } else {
372 None
373 };
374 OrderAny::Limit(LimitOrder::new(
375 TraderId::from("TESTER-001"),
376 StrategyId::from("S-001"),
377 InstrumentId::from("TEST.POLYMARKET"),
378 ClientOrderId::from("O-001"),
379 OrderSide::Buy,
380 Quantity::from("10"),
381 Price::from("0.50"),
382 tif,
383 expire_time,
384 post_only,
385 reduce_only,
386 quote_quantity,
387 None,
388 None,
389 None,
390 None,
391 None,
392 None,
393 None,
394 None,
395 None,
396 None,
397 None,
398 UUID4::new(),
399 UnixNanos::default(),
400 ))
401 }
402
403 fn make_market(side: OrderSide, quote_quantity: bool) -> OrderAny {
404 OrderAny::Market(MarketOrder::new(
405 TraderId::from("TESTER-001"),
406 StrategyId::from("S-001"),
407 InstrumentId::from("TEST.POLYMARKET"),
408 ClientOrderId::from("O-001"),
409 side,
410 Quantity::from("10"),
411 TimeInForce::Ioc,
412 UUID4::new(),
413 UnixNanos::default(),
414 false,
415 quote_quantity,
416 None,
417 None,
418 None,
419 None,
420 None,
421 None,
422 None,
423 None,
424 ))
425 }
426
427 #[rstest]
428 fn test_validate_limit_order_valid() {
429 let order = make_limit(false, false, false, TimeInForce::Gtc);
430 assert!(PolymarketOrderBuilder::validate_limit_order(&order).is_ok());
431 }
432
433 #[rstest]
434 fn test_validate_limit_order_reduce_only_denied() {
435 let order = make_limit(true, false, false, TimeInForce::Gtc);
436 let err = PolymarketOrderBuilder::validate_limit_order(&order).unwrap_err();
437 assert!(err.contains("Reduce-only"));
438 }
439
440 #[rstest]
441 fn test_validate_limit_order_quote_quantity_denied() {
442 let order = make_limit(false, true, false, TimeInForce::Gtc);
443 let err = PolymarketOrderBuilder::validate_limit_order(&order).unwrap_err();
444 assert!(err.contains("Quote quantity"));
445 }
446
447 #[rstest]
448 fn test_validate_limit_order_post_only_ioc_denied() {
449 let order = make_limit(false, false, true, TimeInForce::Ioc);
450 let err = PolymarketOrderBuilder::validate_limit_order(&order).unwrap_err();
451 assert!(err.contains("Post-only"));
452 }
453
454 #[rstest]
455 fn test_validate_limit_order_post_only_gtc_allowed() {
456 let order = make_limit(false, false, true, TimeInForce::Gtc);
457 assert!(PolymarketOrderBuilder::validate_limit_order(&order).is_ok());
458 }
459
460 #[rstest]
461 fn test_validate_limit_order_no_order_side_denied() {
462 let order = OrderAny::Limit(LimitOrder::new(
463 TraderId::from("TESTER-001"),
464 StrategyId::from("S-001"),
465 InstrumentId::from("TEST.POLYMARKET"),
466 ClientOrderId::from("O-NO-SIDE"),
467 OrderSide::NoOrderSide,
468 Quantity::from("10"),
469 Price::from("0.50"),
470 TimeInForce::Gtc,
471 None,
472 false,
473 false,
474 false,
475 None,
476 None,
477 None,
478 None,
479 None,
480 None,
481 None,
482 None,
483 None,
484 None,
485 None,
486 UUID4::new(),
487 UnixNanos::default(),
488 ));
489 let err = PolymarketOrderBuilder::validate_limit_order(&order).unwrap_err();
490 assert!(err.contains("Invalid order side"));
491 }
492
493 #[rstest]
494 fn test_validate_market_order_buy_with_quote_qty() {
495 let order = make_market(OrderSide::Buy, true);
496 assert!(PolymarketOrderBuilder::validate_market_order(&order).is_ok());
497 }
498
499 #[rstest]
500 fn test_validate_market_order_buy_without_quote_qty_denied() {
501 let order = make_market(OrderSide::Buy, false);
502 let err = PolymarketOrderBuilder::validate_market_order(&order).unwrap_err();
503 assert!(err.contains("quote_quantity=true"));
504 }
505
506 #[rstest]
507 fn test_validate_market_order_sell_without_quote_qty() {
508 let order = make_market(OrderSide::Sell, false);
509 assert!(PolymarketOrderBuilder::validate_market_order(&order).is_ok());
510 }
511
512 #[rstest]
513 fn test_validate_market_order_sell_with_quote_qty_denied() {
514 let order = make_market(OrderSide::Sell, true);
515 let err = PolymarketOrderBuilder::validate_market_order(&order).unwrap_err();
516 assert!(err.contains("quote_quantity=false"));
517 }
518
519 #[rstest]
520 fn test_validate_market_order_wrong_type_denied() {
521 let limit = make_limit(false, false, false, TimeInForce::Gtc);
523 let err = PolymarketOrderBuilder::validate_market_order(&limit).unwrap_err();
524 assert!(err.contains("Expected Market order"));
525 }
526
527 fn make_test_builder() -> PolymarketOrderBuilder {
528 use crate::{common::credential::EvmPrivateKey, signing::eip712::OrderSigner};
529 let pk = EvmPrivateKey::new(
530 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
531 )
532 .unwrap();
533 let signer = OrderSigner::new(&pk).unwrap();
534 let addr = format!("{:#x}", signer.address());
535 PolymarketOrderBuilder::new(signer, addr.clone(), addr, SignatureType::Eoa)
536 }
537
538 #[rstest]
539 fn test_next_timestamp_ms_is_strictly_monotonic() {
540 let builder = make_test_builder();
541 let mut prev = builder.next_timestamp_ms();
542 for _ in 0..1_000 {
543 let next = builder.next_timestamp_ms();
544 assert!(
545 next > prev,
546 "timestamp not strictly monotonic: {prev} >= {next}"
547 );
548 prev = next;
549 }
550 }
551
552 #[rstest]
553 fn test_build_orders_produce_unique_timestamps() {
554 let builder = make_test_builder();
555 let mut timestamps = ahash::AHashSet::new();
556
557 for _ in 0..50 {
558 let order = builder
559 .build_limit_order(
560 "71321045679252212594626385532706912750332728571942532289631379312455583992563",
561 PolymarketOrderSide::Buy,
562 dec!(0.50),
563 dec!(10),
564 "0",
565 false,
566 2,
567 )
568 .unwrap();
569 assert!(
570 timestamps.insert(order.timestamp.clone()),
571 "duplicate timestamp {} in burst",
572 order.timestamp,
573 );
574 }
575 }
576
577 #[rstest]
578 fn test_built_order_carries_nautilus_builder_code() {
579 let builder = make_test_builder();
580 let order = builder
581 .build_limit_order(
582 "71321045679252212594626385532706912750332728571942532289631379312455583992563",
583 PolymarketOrderSide::Buy,
584 dec!(0.50),
585 dec!(10),
586 "0",
587 false,
588 2,
589 )
590 .unwrap();
591 assert_eq!(order.builder, POLYMARKET_NAUTILUS_BUILDER_CODE);
592 }
593
594 #[rstest]
595 fn test_build_limit_order_expiration_passthrough() {
596 let builder = make_test_builder();
597 let order = builder
598 .build_limit_order(
599 "71321045679252212594626385532706912750332728571942532289631379312455583992563",
600 PolymarketOrderSide::Buy,
601 dec!(0.50),
602 dec!(10),
603 "1735689600",
604 false,
605 2,
606 )
607 .unwrap();
608 assert_eq!(order.expiration, "1735689600");
609 }
610
611 #[rstest]
612 fn test_validate_limit_order_gtd_with_expire_accepted() {
613 let order = make_limit(false, false, false, TimeInForce::Gtd);
619 assert!(PolymarketOrderBuilder::validate_limit_order(&order).is_ok());
620 }
621
622 #[rstest]
623 fn test_build_market_buy_order_wire_shape() {
624 let builder = make_test_builder();
627 let order = builder
628 .build_market_order(
629 "71321045679252212594626385532706912750332728571942532289631379312455583992563",
630 PolymarketOrderSide::Buy,
631 dec!(0.50),
632 dec!(10),
633 false,
634 2,
635 )
636 .unwrap();
637
638 assert_eq!(order.expiration, "0");
639 assert_eq!(order.builder, POLYMARKET_NAUTILUS_BUILDER_CODE);
640 assert_eq!(order.metadata, ZERO_BYTES32);
641 assert_eq!(order.side, PolymarketOrderSide::Buy);
642 assert!(!order.timestamp.is_empty());
643 assert!(!order.signature.is_empty());
644 assert_eq!(order.maker_amount, dec!(10_000_000));
647 assert_eq!(order.taker_amount, dec!(20_000_000));
648 }
649
650 #[rstest]
651 fn test_build_market_sell_order_wire_shape() {
652 let builder = make_test_builder();
655 let order = builder
656 .build_market_order(
657 "71321045679252212594626385532706912750332728571942532289631379312455583992563",
658 PolymarketOrderSide::Sell,
659 dec!(0.50),
660 dec!(20),
661 false,
662 2,
663 )
664 .unwrap();
665
666 assert_eq!(order.expiration, "0");
667 assert_eq!(order.side, PolymarketOrderSide::Sell);
668 assert_eq!(order.maker_amount, dec!(20_000_000));
669 assert_eq!(order.taker_amount, dec!(10_000_000));
670 assert!(!order.signature.is_empty());
671 }
672
673 #[rstest]
674 fn test_generate_salt_uniqueness() {
675 let s1 = generate_salt();
676 let s2 = generate_salt();
677 assert_ne!(s1, s2);
678 }
679
680 #[rstest]
681 fn test_generate_salt_within_53_bits() {
682 for _ in 0..100 {
683 let s = generate_salt();
684 assert!(s < (1u64 << 53));
685 }
686 }
687
688 #[rstest]
691 #[case(dec!(0.50), dec!(100), PolymarketOrderSide::Buy, 2, dec!(50_000_000), dec!(100_000_000))]
692 #[case(dec!(0.50), dec!(100), PolymarketOrderSide::Sell, 2, dec!(100_000_000), dec!(50_000_000))]
693 #[case(dec!(0.75), dec!(200), PolymarketOrderSide::Buy, 2, dec!(150_000_000), dec!(200_000_000))]
694 #[case(dec!(0.567), dec!(23.456), PolymarketOrderSide::Buy, 3, dec!(13_296_150), dec!(23_450_000))]
696 #[case(dec!(0.55), dec!(10), PolymarketOrderSide::Buy, 1, dec!(5_500_000), dec!(10_000_000))]
697 fn test_compute_maker_taker_amounts(
698 #[case] price: Decimal,
699 #[case] quantity: Decimal,
700 #[case] side: PolymarketOrderSide,
701 #[case] tick_decimals: u32,
702 #[case] expected_maker: Decimal,
703 #[case] expected_taker: Decimal,
704 ) {
705 let (maker, taker) = compute_maker_taker_amounts(price, quantity, side, tick_decimals);
706 assert_eq!(maker, expected_maker);
707 assert_eq!(taker, expected_taker);
708 }
709
710 #[rstest]
717 #[case::buy_tick_tenth(
719 dec!(0.5), dec!(21.04), PolymarketOrderSide::Buy, 1,
720 dec!(10_520_000), dec!(21_040_000),
721 )]
722 #[case::sell_tick_tenth(
723 dec!(0.5), dec!(21.04), PolymarketOrderSide::Sell, 1,
724 dec!(21_040_000), dec!(10_520_000),
725 )]
726 #[case::buy_tick_hundredth(
728 dec!(0.56), dec!(21.04), PolymarketOrderSide::Buy, 2,
729 dec!(11_782_400), dec!(21_040_000),
730 )]
731 #[case::sell_tick_hundredth(
732 dec!(0.56), dec!(21.04), PolymarketOrderSide::Sell, 2,
733 dec!(21_040_000), dec!(11_782_400),
734 )]
735 #[case::buy_decimal_accuracy_24(
736 dec!(0.24), dec!(15), PolymarketOrderSide::Buy, 2,
737 dec!(3_600_000), dec!(15_000_000),
738 )]
739 #[case::buy_decimal_accuracy_82(
740 dec!(0.82), dec!(101), PolymarketOrderSide::Buy, 2,
741 dec!(82_820_000), dec!(101_000_000),
742 )]
743 #[case::buy_decimal_accuracy_18233(
744 dec!(0.58), dec!(18233.33), PolymarketOrderSide::Buy, 2,
745 dec!(10_575_331_400), dec!(18_233_330_000),
746 )]
747 #[case::buy_tick_thousandth(
749 dec!(0.056), dec!(21.04), PolymarketOrderSide::Buy, 3,
750 dec!(1_178_240), dec!(21_040_000),
751 )]
752 #[case::sell_tick_thousandth(
753 dec!(0.056), dec!(21.04), PolymarketOrderSide::Sell, 3,
754 dec!(21_040_000), dec!(1_178_240),
755 )]
756 #[case::buy_tick_ten_thousandth(
758 dec!(0.0056), dec!(21.04), PolymarketOrderSide::Buy, 4,
759 dec!(117_824), dec!(21_040_000),
760 )]
761 #[case::sell_tick_ten_thousandth(
762 dec!(0.0056), dec!(21.04), PolymarketOrderSide::Sell, 4,
763 dec!(21_040_000), dec!(117_824),
764 )]
765 fn test_compute_maker_taker_amounts_sdk_parity(
766 #[case] price: Decimal,
767 #[case] quantity: Decimal,
768 #[case] side: PolymarketOrderSide,
769 #[case] tick_decimals: u32,
770 #[case] expected_maker: Decimal,
771 #[case] expected_taker: Decimal,
772 ) {
773 let (maker, taker) = compute_maker_taker_amounts(price, quantity, side, tick_decimals);
774 assert_eq!(maker, expected_maker);
775 assert_eq!(taker, expected_taker);
776 }
777
778 #[rstest]
781 #[case(dec!(0.50), dec!(50), PolymarketOrderSide::Buy, 2, dec!(50_000_000), dec!(100_000_000))]
782 #[case(dec!(0.50), dec!(100), PolymarketOrderSide::Sell, 2, dec!(100_000_000), dec!(50_000_000))]
783 #[case(dec!(0.75), dec!(150), PolymarketOrderSide::Buy, 2, dec!(150_000_000), dec!(200_000_000))]
784 #[case(dec!(0.211), dec!(23.696681), PolymarketOrderSide::Sell, 3, dec!(23_690_000), dec!(4_998_590))]
786 fn test_compute_market_maker_taker_amounts(
787 #[case] price: Decimal,
788 #[case] amount: Decimal,
789 #[case] side: PolymarketOrderSide,
790 #[case] tick_decimals: u32,
791 #[case] expected_maker: Decimal,
792 #[case] expected_taker: Decimal,
793 ) {
794 let (maker, taker) = compute_market_maker_taker_amounts(price, amount, side, tick_decimals);
795 assert_eq!(maker, expected_maker);
796 assert_eq!(taker, expected_taker);
797 }
798
799 #[rstest]
800 fn test_compute_maker_taker_qty_truncated_to_lot_size() {
801 let (maker, taker) =
804 compute_maker_taker_amounts(dec!(0.567), dec!(23.456), PolymarketOrderSide::Buy, 3);
805 assert_eq!(maker, dec!(13_296_150));
806 assert_eq!(taker, dec!(23_450_000));
807 }
808
809 #[rstest]
810 fn test_compute_maker_taker_zero_amounts() {
811 let (maker, taker) =
812 compute_maker_taker_amounts(dec!(0.50), dec!(0), PolymarketOrderSide::Buy, 2);
813 assert_eq!(maker, dec!(0));
814 assert_eq!(taker, dec!(0));
815 }
816
817 #[rstest]
818 fn test_compute_maker_taker_tick_decimals_1() {
819 let (maker, taker) =
820 compute_maker_taker_amounts(dec!(0.3), dec!(50), PolymarketOrderSide::Buy, 1);
821 assert_eq!(maker, dec!(15_000_000));
822 assert_eq!(taker, dec!(50_000_000));
823 }
824
825 #[rstest]
826 fn test_compute_maker_taker_tick_decimals_3_sell() {
827 let (maker, taker) =
830 compute_maker_taker_amounts(dec!(0.789), dec!(15.123), PolymarketOrderSide::Sell, 3);
831 assert_eq!(maker, dec!(15_120_000));
832 assert_eq!(taker, dec!(11_929_680));
833 }
834
835 #[rstest]
836 fn test_compute_market_maker_taker_zero_amount() {
837 let (maker, taker) =
838 compute_market_maker_taker_amounts(dec!(0.50), dec!(0), PolymarketOrderSide::Buy, 2);
839 assert_eq!(maker, dec!(0));
840 assert_eq!(taker, dec!(0));
841 }
842
843 #[rstest]
844 fn test_compute_market_sell_position_close() {
845 let (maker, taker) = compute_market_maker_taker_amounts(
849 dec!(0.211),
850 dec!(23.696681),
851 PolymarketOrderSide::Sell,
852 3,
853 );
854 assert_eq!(maker, dec!(23_690_000));
855 assert_eq!(taker, dec!(4_998_590));
857 assert_eq!(maker % dec!(10_000), dec!(0));
859 assert_eq!(taker % dec!(10), dec!(0));
861 }
862
863 #[rstest]
864 fn test_to_fixed_decimal_basic() {
865 assert_eq!(to_fixed_decimal(dec!(13.29955)), dec!(13_299_550));
866 assert_eq!(to_fixed_decimal(dec!(100)), dec!(100_000_000));
867 assert_eq!(to_fixed_decimal(dec!(0)), dec!(0));
868 }
869}