1use crate::{
17 error::{Bound, BoundsViolation, OrderError, OrderField, PrecisionLimit, PrecisionViolation},
18 order::{OrderKind, TrailingOffsetType},
19};
20use rust_decimal::Decimal;
21use rustrade_instrument::Side;
22use serde::{Deserialize, Serialize};
23
24#[non_exhaustive]
26#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
27#[serde(rename_all = "snake_case")]
28pub enum PreSendRules {
29 #[default]
32 Enforce,
33 Off,
36}
37
38#[non_exhaustive]
43#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
44pub struct ValueRule {
45 pub min: Option<Decimal>,
47 pub max: Option<Decimal>,
49 pub step: Option<Decimal>,
51}
52
53impl ValueRule {
54 pub fn new(min: Decimal, max: Decimal, step: Decimal) -> Self {
56 let on = |value: Decimal| (value > Decimal::ZERO).then_some(value);
57 Self {
58 min: on(min),
59 max: on(max),
60 step: on(step),
61 }
62 }
63
64 pub fn is_off(&self) -> bool {
66 self.min.is_none() && self.max.is_none() && self.step.is_none()
67 }
68
69 #[must_use]
83 pub fn round_down(&self, value: Decimal) -> Option<Decimal> {
84 round_down_to(value, self.grid())
85 }
86
87 #[must_use]
92 pub fn round_up(&self, value: Decimal) -> Option<Decimal> {
93 round_up_to(value, self.grid())
94 }
95
96 fn grid(&self) -> Option<Decimal> {
99 self.step.filter(|step| *step > Decimal::ZERO)
100 }
101
102 fn check<AssetKey, InstrumentKey>(
105 &self,
106 field: OrderField,
107 value: Decimal,
108 ) -> Result<(), OrderError<AssetKey, InstrumentKey>> {
109 if let Some(step) = self.grid()
110 && !(value % step).is_zero()
111 {
112 return Err(OrderError::InvalidPrecision(PrecisionViolation::new(
113 field,
114 value,
115 PrecisionLimit::NotOnGrid { step },
116 )));
117 }
118 if let Some(min) = self.min.filter(|min| value < *min) {
119 return Err(OrderError::OutOfBounds(BoundsViolation::new(
120 field,
121 Bound::Min,
122 min,
123 value,
124 )));
125 }
126 if let Some(max) = self.max.filter(|max| value > *max) {
127 return Err(OrderError::OutOfBounds(BoundsViolation::new(
128 field,
129 Bound::Max,
130 max,
131 value,
132 )));
133 }
134 Ok(())
135 }
136}
137
138fn round_down_to(value: Decimal, step: Option<Decimal>) -> Option<Decimal> {
140 let Some(step) = step else {
141 return Some(value);
142 };
143 let remainder = value % step;
145 if remainder.is_zero() {
146 return Some(value);
147 }
148 let toward_zero = value - remainder;
149 let rounded = if remainder.is_sign_negative() {
150 toward_zero.checked_sub(step)?
151 } else {
152 toward_zero
153 };
154 on_grid(rounded, step)
155}
156
157fn round_up_to(value: Decimal, step: Option<Decimal>) -> Option<Decimal> {
159 let Some(step) = step else {
160 return Some(value);
161 };
162 let remainder = value % step;
163 if remainder.is_zero() {
164 return Some(value);
165 }
166 let toward_zero = value - remainder;
167 let rounded = if remainder.is_sign_negative() {
168 toward_zero
169 } else {
170 toward_zero.checked_add(step)?
171 };
172 on_grid(rounded, step)
173}
174
175fn on_grid(value: Decimal, step: Decimal) -> Option<Decimal> {
178 (value % step).is_zero().then_some(value)
179}
180
181fn common_step(a: Decimal, b: Decimal) -> Option<Decimal> {
184 let scale = a.scale().max(b.scale());
186 let whole = |step: Decimal| {
187 10_i128
188 .checked_pow(scale - step.scale())
189 .and_then(|factor| step.mantissa().checked_mul(factor))
190 };
191 let (a, b) = (whole(a)?, whole(b)?);
192 let (mut x, mut y) = (a, b);
193 while y != 0 {
194 (x, y) = (y, x % y);
195 }
196 let lcm = (a / x).checked_mul(b)?;
197 Decimal::try_from_i128_with_scale(lcm, scale)
198 .ok()
199 .map(|step| step.normalize())
200}
201
202#[non_exhaustive]
207#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
208pub struct OrderRules {
209 pub price: ValueRule,
212 pub quantity: ValueRule,
214 pub market_quantity: ValueRule,
216 pub notional: ValueRule,
218 #[serde(default)]
226 pub market_notional: ValueRule,
227 #[serde(default)]
230 pub trailing_offset_above: ValueRule,
231 #[serde(default)]
234 pub trailing_offset_below: ValueRule,
235}
236
237impl OrderRules {
238 pub fn new(
242 price: ValueRule,
243 quantity: ValueRule,
244 market_quantity: ValueRule,
245 notional: ValueRule,
246 ) -> Self {
247 Self {
248 price,
249 quantity,
250 market_quantity,
251 notional,
252 market_notional: ValueRule::default(),
253 trailing_offset_above: ValueRule::default(),
254 trailing_offset_below: ValueRule::default(),
255 }
256 }
257
258 #[must_use]
260 pub fn with_market_notional(self, market_notional: ValueRule) -> Self {
261 Self {
262 market_notional,
263 ..self
264 }
265 }
266
267 #[must_use]
270 pub fn with_trailing_offsets(self, above: ValueRule, below: ValueRule) -> Self {
271 Self {
272 trailing_offset_above: above,
273 trailing_offset_below: below,
274 ..self
275 }
276 }
277
278 pub fn is_off(&self) -> bool {
280 self.price.is_off()
281 && self.quantity.is_off()
282 && self.market_quantity.is_off()
283 && self.notional.is_off()
284 && self.market_notional.is_off()
285 && self.trailing_offset_above.is_off()
286 && self.trailing_offset_below.is_off()
287 }
288
289 #[must_use]
298 pub fn round_quantity_down(&self, kind: OrderKind, quantity: Decimal) -> Option<Decimal> {
299 round_down_to(quantity, self.quantity_grid(kind)?)
300 }
301
302 #[must_use]
305 pub fn round_quantity_up(&self, kind: OrderKind, quantity: Decimal) -> Option<Decimal> {
306 round_up_to(quantity, self.quantity_grid(kind)?)
307 }
308
309 fn quantity_grid(&self, kind: OrderKind) -> Option<Option<Decimal>> {
312 let quantity = self.quantity.grid();
313 if !matches!(kind, OrderKind::Market) {
314 return Some(quantity);
315 }
316 match (quantity, self.market_quantity.grid()) {
317 (Some(a), Some(b)) => common_step(a, b).map(Some),
318 (step, None) | (None, step) => Some(step),
319 }
320 }
321
322 pub fn check<AssetKey, InstrumentKey>(
352 &self,
353 side: Side,
354 kind: OrderKind,
355 price: Option<Decimal>,
356 quantity: Decimal,
357 ) -> Result<(), OrderError<AssetKey, InstrumentKey>> {
358 let (limit_price, trigger_price, has_own_limit) = match kind {
361 OrderKind::Market => (None, None, false),
362 OrderKind::Limit => (price, None, true),
363 OrderKind::Stop { trigger_price } | OrderKind::TakeProfit { trigger_price } => {
364 (None, Some(trigger_price), false)
365 }
366 OrderKind::StopLimit { trigger_price }
367 | OrderKind::TakeProfitLimit { trigger_price } => (price, Some(trigger_price), true),
368 OrderKind::TrailingStop {
369 activation_price, ..
370 } => (None, activation_price, false),
371 OrderKind::TrailingStopFixedLimit {
372 activation_price, ..
373 } => (price, activation_price, true),
374 OrderKind::TrailingStopLimit { .. } => (None, None, false),
376 OrderKind::ConditionalUnresolved {
378 family,
379 trigger_price,
380 } => (
381 price.filter(|_| family.has_limit_price()),
382 trigger_price,
383 family.has_limit_price(),
384 ),
385 };
386
387 if let Some(price) = limit_price {
388 self.price.check(OrderField::Price, price)?;
389 }
390 if let Some(trigger) = trigger_price {
391 self.price.check(OrderField::TriggerPrice, trigger)?;
392 }
393 self.check_trailing_offset(side, kind)?;
394 self.quantity.check(OrderField::Quantity, quantity)?;
395 if matches!(kind, OrderKind::Market) {
396 self.market_quantity.check(OrderField::Quantity, quantity)?;
397 }
398 if let Some(price) = limit_price.filter(|_| has_own_limit) {
399 let notional = price.saturating_mul(quantity);
401 self.notional.check(OrderField::Notional, notional)?;
402 }
403 Ok(())
404 }
405
406 fn check_trailing_offset<AssetKey, InstrumentKey>(
409 &self,
410 side: Side,
411 kind: OrderKind,
412 ) -> Result<(), OrderError<AssetKey, InstrumentKey>> {
413 let (offset, offset_type) = match kind {
414 OrderKind::TrailingStop {
415 offset,
416 offset_type,
417 ..
418 }
419 | OrderKind::TrailingStopLimit {
420 offset,
421 offset_type,
422 ..
423 }
424 | OrderKind::TrailingStopFixedLimit {
425 offset,
426 offset_type,
427 ..
428 } => (offset, offset_type),
429 OrderKind::Market
431 | OrderKind::Limit
432 | OrderKind::Stop { .. }
433 | OrderKind::StopLimit { .. }
434 | OrderKind::TakeProfit { .. }
435 | OrderKind::TakeProfitLimit { .. }
436 | OrderKind::ConditionalUnresolved { .. } => return Ok(()),
437 };
438 let basis_points = match side {
440 Side::Buy => &self.trailing_offset_above,
441 Side::Sell => &self.trailing_offset_below,
442 };
443 let percent = |bound: Option<Decimal>| bound.map(|bound| bound / Decimal::ONE_HUNDRED);
445 let range = match offset_type {
447 TrailingOffsetType::BasisPoints => ValueRule {
448 step: None,
449 ..*basis_points
450 },
451 TrailingOffsetType::Percentage => ValueRule {
452 min: percent(basis_points.min),
453 max: percent(basis_points.max),
454 step: None,
455 },
456 TrailingOffsetType::Absolute => return Ok(()),
457 };
458 range.check(OrderField::TrailingOffset, offset)
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use crate::order::{ConditionalFamily, TrailingOffsetType};
466 use rust_decimal_macros::dec;
467
468 type Error = OrderError<(), ()>;
469
470 fn rules() -> OrderRules {
472 OrderRules::new(
473 ValueRule::new(dec!(0.01), dec!(1000000), dec!(0.01)),
474 ValueRule::new(dec!(0.001), dec!(100), dec!(0.001)),
475 ValueRule::new(dec!(0), dec!(10), dec!(0)),
476 ValueRule::new(dec!(5), dec!(9000000), dec!(0)),
477 )
478 }
479
480 fn check(kind: OrderKind, price: Option<Decimal>, quantity: Decimal) -> Result<(), Error> {
481 rules().check(Side::Buy, kind, price, quantity)
482 }
483
484 fn bounds(field: OrderField, bound: Bound, limit: Decimal, value: Decimal) -> Error {
485 OrderError::OutOfBounds(BoundsViolation::new(field, bound, limit, value))
486 }
487
488 fn off_grid(field: OrderField, value: Decimal, step: Decimal) -> Error {
489 OrderError::InvalidPrecision(PrecisionViolation::new(
490 field,
491 value,
492 PrecisionLimit::NotOnGrid { step },
493 ))
494 }
495
496 #[test]
497 fn zero_or_negative_means_the_part_is_off() {
498 let rule = ValueRule::new(dec!(0), dec!(-1), dec!(0));
499 assert!(rule.is_off());
500 assert!(OrderRules::default().is_off());
501 assert!(!rules().is_off());
502 let on = ValueRule::new(dec!(1), dec!(0), dec!(0));
504 let off = ValueRule::default();
505 assert!(!OrderRules::new(on, off, off, off).is_off());
506 assert!(!OrderRules::new(off, on, off, off).is_off());
507 assert!(!OrderRules::new(off, off, on, off).is_off());
508 assert!(!OrderRules::new(off, off, off, on).is_off());
509 assert_eq!(
510 OrderRules::default().check::<(), ()>(
511 Side::Buy,
512 OrderKind::Limit,
513 Some(dec!(0.0001)),
514 dec!(7.77)
515 ),
516 Ok(())
517 );
518 }
519
520 #[test]
521 fn a_step_of_zero_set_by_hand_is_no_grid_not_a_panic() {
522 let mut rules = OrderRules::default();
523 rules.quantity.step = Some(Decimal::ZERO);
524 assert_eq!(
525 rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(1.5)),
526 Ok(())
527 );
528 }
529
530 #[test]
531 fn a_limit_order_on_the_grid_and_in_range_passes() {
532 assert_eq!(
533 check(OrderKind::Limit, Some(dec!(50000.12)), dec!(0.5)),
534 Ok(())
535 );
536 }
537
538 #[test]
539 fn a_price_off_the_tick_grid_is_refused_with_the_step() {
540 assert_eq!(
541 check(OrderKind::Limit, Some(dec!(50000.123)), dec!(0.5)),
542 Err(off_grid(OrderField::Price, dec!(50000.123), dec!(0.01)))
543 );
544 }
545
546 #[test]
547 fn a_quantity_off_the_lot_grid_is_refused_with_the_step() {
548 assert_eq!(
549 check(OrderKind::Limit, Some(dec!(50000)), dec!(0.0015)),
550 Err(off_grid(OrderField::Quantity, dec!(0.0015), dec!(0.001)))
551 );
552 }
553
554 #[test]
555 fn a_grid_is_counted_from_zero_not_from_the_minimum() {
556 let rules = OrderRules::new(
559 ValueRule::default(),
560 ValueRule::new(dec!(0.002), dec!(0), dec!(0.002)),
561 ValueRule::default(),
562 ValueRule::default(),
563 );
564 assert_eq!(
565 rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(0.003)),
566 Err(off_grid(OrderField::Quantity, dec!(0.003), dec!(0.002)))
567 );
568 assert_eq!(
569 rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(0.004)),
570 Ok(())
571 );
572 }
573
574 #[test]
575 fn a_price_beyond_either_bound_is_refused_with_the_limit() {
576 let rules = OrderRules::new(
578 ValueRule::new(dec!(1), dec!(10), dec!(0.5)),
579 ValueRule::default(),
580 ValueRule::default(),
581 ValueRule::default(),
582 );
583 assert_eq!(
584 rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(0.5)), dec!(1)),
585 Err(bounds(OrderField::Price, Bound::Min, dec!(1), dec!(0.5)))
586 );
587 assert_eq!(
588 rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(10.5)), dec!(1)),
589 Err(bounds(OrderField::Price, Bound::Max, dec!(10), dec!(10.5)))
590 );
591 assert_eq!(
592 rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(1)), dec!(1)),
593 Ok(())
594 );
595 assert_eq!(
596 rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(10)), dec!(1)),
597 Ok(())
598 );
599 }
600
601 #[test]
602 fn a_quantity_beyond_either_bound_is_refused_and_the_bounds_are_inclusive() {
603 assert_eq!(
604 check(OrderKind::Limit, Some(dec!(50000)), dec!(100.001)),
605 Err(bounds(
606 OrderField::Quantity,
607 Bound::Max,
608 dec!(100),
609 dec!(100.001)
610 ))
611 );
612 assert_eq!(
613 check(OrderKind::Limit, Some(dec!(50000)), dec!(0)),
614 Err(bounds(
615 OrderField::Quantity,
616 Bound::Min,
617 dec!(0.001),
618 dec!(0)
619 ))
620 );
621 assert_eq!(
622 check(OrderKind::Limit, Some(dec!(50000)), dec!(100)),
623 Ok(())
624 );
625 assert_eq!(
626 check(OrderKind::Limit, Some(dec!(50000)), dec!(0.001)),
627 Ok(())
628 );
629 }
630
631 #[test]
632 fn the_market_quantity_rule_applies_to_a_market_order_only() {
633 assert_eq!(
634 check(OrderKind::Market, None, dec!(10.5)),
635 Err(bounds(
636 OrderField::Quantity,
637 Bound::Max,
638 dec!(10),
639 dec!(10.5)
640 ))
641 );
642 assert_eq!(
643 check(OrderKind::Limit, Some(dec!(50000)), dec!(10.5)),
644 Ok(())
645 );
646 }
647
648 #[test]
649 fn a_market_order_also_obeys_the_ordinary_quantity_rule() {
650 assert_eq!(
651 check(OrderKind::Market, None, dec!(0.0005)),
652 Err(off_grid(OrderField::Quantity, dec!(0.0005), dec!(0.001)))
653 );
654 }
655
656 #[test]
657 fn the_notional_of_a_limit_order_is_its_price_times_its_quantity() {
658 assert_eq!(
659 check(OrderKind::Limit, Some(dec!(1000)), dec!(0.004)),
660 Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(4)))
661 );
662 assert_eq!(
663 check(OrderKind::Limit, Some(dec!(1000)), dec!(0.005)),
664 Ok(())
665 );
666 assert_eq!(
667 check(OrderKind::Limit, Some(dec!(1000000)), dec!(10)),
668 Err(bounds(
669 OrderField::Notional,
670 Bound::Max,
671 dec!(9000000),
672 dec!(10000000)
673 ))
674 );
675 }
676
677 #[test]
678 fn a_notional_beyond_the_decimal_range_is_above_the_maximum_not_a_panic() {
679 let rules = OrderRules::new(
680 ValueRule::default(),
681 ValueRule::default(),
682 ValueRule::default(),
683 ValueRule::new(dec!(0), dec!(1), dec!(0)),
684 );
685 assert!(matches!(
686 rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(Decimal::MAX), Decimal::MAX),
687 Err(OrderError::OutOfBounds(v)) if v.bound == Bound::Max
688 ));
689 }
690
691 #[test]
692 fn a_stop_limit_checks_the_price_the_trigger_and_the_notional() {
693 let kind = |trigger_price| OrderKind::StopLimit { trigger_price };
694 assert_eq!(
695 check(kind(dec!(100.001)), Some(dec!(100)), dec!(1)),
696 Err(off_grid(
697 OrderField::TriggerPrice,
698 dec!(100.001),
699 dec!(0.01)
700 ))
701 );
702 assert_eq!(
703 check(kind(dec!(100)), Some(dec!(100.001)), dec!(1)),
704 Err(off_grid(OrderField::Price, dec!(100.001), dec!(0.01)))
705 );
706 assert_eq!(
707 check(kind(dec!(100)), Some(dec!(1)), dec!(1)),
708 Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(1)))
709 );
710 assert_eq!(check(kind(dec!(100)), Some(dec!(100)), dec!(1)), Ok(()));
711 }
712
713 #[test]
714 fn a_take_profit_limit_is_checked_like_a_stop_limit() {
715 assert_eq!(
716 check(
717 OrderKind::TakeProfitLimit {
718 trigger_price: dec!(100.001)
719 },
720 Some(dec!(100)),
721 dec!(1)
722 ),
723 Err(off_grid(
724 OrderField::TriggerPrice,
725 dec!(100.001),
726 dec!(0.01)
727 ))
728 );
729 }
730
731 #[test]
732 fn a_plain_stop_checks_its_trigger_and_never_a_notional() {
733 for kind in [
735 OrderKind::Stop {
736 trigger_price: dec!(1),
737 },
738 OrderKind::TakeProfit {
739 trigger_price: dec!(1),
740 },
741 ] {
742 assert_eq!(check(kind, None, dec!(1)), Ok(()), "{kind:?}");
743 }
744 assert_eq!(
745 check(
746 OrderKind::Stop {
747 trigger_price: dec!(1.001)
748 },
749 None,
750 dec!(1)
751 ),
752 Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
753 );
754 }
755
756 #[test]
757 fn a_market_order_never_checks_a_notional_or_a_price() {
758 assert_eq!(
760 check(OrderKind::Market, Some(dec!(0.001)), dec!(0.001)),
761 Ok(())
762 );
763 }
764
765 #[test]
766 fn a_trailing_stop_checks_its_activation_price_only() {
767 let kind = |activation_price| OrderKind::TrailingStop {
768 offset: dec!(50),
769 offset_type: TrailingOffsetType::BasisPoints,
770 activation_price,
771 };
772 assert_eq!(check(kind(None), None, dec!(1)), Ok(()));
773 assert_eq!(check(kind(Some(dec!(1))), None, dec!(1)), Ok(()));
774 assert_eq!(
775 check(kind(Some(dec!(1.001))), None, dec!(1)),
776 Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
777 );
778 }
779
780 #[test]
781 fn a_fixed_limit_trailing_stop_checks_its_limit_activation_and_notional() {
782 let kind = |activation_price| OrderKind::TrailingStopFixedLimit {
783 offset: dec!(50),
784 offset_type: TrailingOffsetType::BasisPoints,
785 activation_price,
786 };
787 assert_eq!(
788 check(kind(None), Some(dec!(100.001)), dec!(1)),
789 Err(off_grid(OrderField::Price, dec!(100.001), dec!(0.01)))
790 );
791 assert_eq!(
792 check(kind(Some(dec!(1.001))), Some(dec!(100)), dec!(1)),
793 Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
794 );
795 assert_eq!(
796 check(kind(None), Some(dec!(1)), dec!(1)),
797 Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(1)))
798 );
799 assert_eq!(check(kind(None), Some(dec!(100)), dec!(1)), Ok(()));
800 }
801
802 #[test]
805 fn an_unresolved_kind_is_checked_as_its_family() {
806 let kind = |family, trigger_price| OrderKind::ConditionalUnresolved {
807 family,
808 trigger_price,
809 };
810 let stop_limit = |trigger| kind(ConditionalFamily::StopLimit, trigger);
811 assert_eq!(
812 check(stop_limit(Some(dec!(100.001))), Some(dec!(100)), dec!(1)),
813 Err(off_grid(
814 OrderField::TriggerPrice,
815 dec!(100.001),
816 dec!(0.01)
817 ))
818 );
819 assert_eq!(
820 check(stop_limit(None), Some(dec!(100.001)), dec!(1)),
821 Err(off_grid(OrderField::Price, dec!(100.001), dec!(0.01)))
822 );
823 assert_eq!(
824 check(stop_limit(None), Some(dec!(1)), dec!(1)),
825 Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(1)))
826 );
827 assert_eq!(check(stop_limit(None), Some(dec!(100)), dec!(1)), Ok(()));
828 assert_eq!(
830 check(
831 kind(ConditionalFamily::Stop, Some(dec!(1))),
832 Some(dec!(1.001)),
833 dec!(1)
834 ),
835 Ok(())
836 );
837 }
838
839 #[test]
840 fn a_limit_order_without_a_price_skips_the_price_and_notional_checks() {
841 assert_eq!(check(OrderKind::Limit, None, dec!(1)), Ok(()));
842 }
843
844 #[test]
845 fn a_grid_break_is_reported_before_a_range_break_of_the_same_value() {
846 assert_eq!(
848 check(OrderKind::Limit, Some(dec!(50000)), dec!(0.0005)),
849 Err(off_grid(OrderField::Quantity, dec!(0.0005), dec!(0.001)))
850 );
851 }
852
853 #[test]
854 fn a_pre_send_rules_deserializes_from_snake_case_and_defaults_to_enforce() {
855 assert_eq!(PreSendRules::default(), PreSendRules::Enforce);
856 assert_eq!(
857 serde_json::from_str::<PreSendRules>("\"off\"").ok(),
858 Some(PreSendRules::Off)
859 );
860 }
861
862 #[test]
863 fn the_refusals_name_the_value_and_the_limit() {
864 let text = |result: Result<(), Error>| match result {
865 Err(OrderError::OutOfBounds(violation)) => violation.to_string(),
866 Err(OrderError::InvalidPrecision(violation)) => violation.to_string(),
867 other => panic!("expected a refusal, got {other:?}"),
868 };
869 assert_eq!(
870 text(check(OrderKind::Limit, Some(dec!(1000)), dec!(0.004))),
871 "notional 4.000 is below the minimum 5"
872 );
873 assert_eq!(
874 text(check(OrderKind::Limit, Some(dec!(1000)), dec!(101))),
875 "quantity 101 is above the maximum 100"
876 );
877 assert_eq!(
878 text(check(OrderKind::Limit, Some(dec!(1000.001)), dec!(1))),
879 "price 1000.001 is not a multiple of 0.01"
880 );
881 }
882
883 fn stepped(step: Decimal) -> ValueRule {
884 ValueRule::new(dec!(0), dec!(0), step)
885 }
886
887 #[test]
888 fn a_value_on_the_grid_rounds_to_itself_either_way() {
889 for (step, value) in [
890 (dec!(0.01), dec!(50000.12)),
891 (dec!(0.05), dec!(1.15)),
892 (dec!(0.05), dec!(-1.15)),
893 (dec!(0.001), dec!(0)),
894 ] {
895 assert_eq!(
896 stepped(step).round_down(value),
897 Some(value),
898 "{step} {value}"
899 );
900 assert_eq!(stepped(step).round_up(value), Some(value), "{step} {value}");
901 }
902 }
903
904 #[test]
905 fn a_value_off_the_grid_rounds_to_the_neighbour_below_or_above() {
906 let rule = stepped(dec!(0.01));
907 assert_eq!(rule.round_down(dec!(50000.129)), Some(dec!(50000.12)));
908 assert_eq!(rule.round_up(dec!(50000.121)), Some(dec!(50000.13)));
909 }
910
911 #[test]
912 fn a_step_that_is_not_a_power_of_ten_rounds_to_its_own_multiples() {
913 let rule = stepped(dec!(0.05));
914 assert_eq!(rule.round_down(dec!(1.07)), Some(dec!(1.05)));
915 assert_eq!(rule.round_up(dec!(1.07)), Some(dec!(1.10)));
916 assert_eq!(rule.round_down(dec!(0.04)), Some(dec!(0)));
917 assert_eq!(rule.round_up(dec!(0.01)), Some(dec!(0.05)));
918 assert_eq!(
920 rule.round_down(dec!(0.19000))
921 .map(|value| value.to_string()),
922 Some("0.15000".to_owned())
923 );
924 assert_eq!(
925 stepped(dec!(0.005))
926 .round_down(dec!(0.10))
927 .map(|value| value.to_string()),
928 Some("0.10".to_owned())
929 );
930 }
931
932 #[test]
933 fn a_negative_value_rounds_down_away_from_zero_and_up_toward_it() {
934 let rule = stepped(dec!(0.05));
935 assert_eq!(rule.round_down(dec!(-1.07)), Some(dec!(-1.10)));
936 assert_eq!(rule.round_up(dec!(-1.07)), Some(dec!(-1.05)));
937 assert_eq!(rule.round_up(dec!(-0.01)), Some(dec!(0)));
938 }
939
940 #[test]
941 fn with_no_step_a_value_comes_back_unchanged() {
942 let mut rule = ValueRule::new(dec!(1), dec!(10), dec!(0));
943 assert_eq!(rule.round_down(dec!(1.2345)), Some(dec!(1.2345)));
944 assert_eq!(rule.round_up(dec!(1.2345)), Some(dec!(1.2345)));
945 rule.step = Some(Decimal::ZERO);
947 assert_eq!(rule.round_down(dec!(1.2345)), Some(dec!(1.2345)));
948 }
949
950 #[test]
951 fn rounding_does_not_clamp_to_the_range() {
952 let rule = ValueRule::new(dec!(0.002), dec!(1), dec!(0.002));
953 assert_eq!(rule.round_down(dec!(0.001)), Some(dec!(0)));
954 assert_eq!(rule.round_up(dec!(1.001)), Some(dec!(1.002)));
955 }
956
957 #[test]
958 fn a_result_beyond_the_decimal_range_is_none_not_a_panic() {
959 let rule = stepped(dec!(2));
961 assert_eq!(rule.round_up(Decimal::MAX), None);
962 assert_eq!(rule.round_down(Decimal::MAX), Some(Decimal::MAX - dec!(1)));
963 assert_eq!(rule.round_down(Decimal::MIN), None);
964 assert_eq!(rule.round_up(Decimal::MIN), Some(Decimal::MIN + dec!(1)));
965 }
966
967 #[test]
968 fn a_result_that_needs_more_digits_than_a_decimal_holds_is_none() {
969 let rule = stepped(dec!(0.3));
972 assert_eq!(rule.round_down(Decimal::MAX), Some(Decimal::MAX));
973 assert_eq!(rule.round_down(Decimal::MAX - dec!(1)), None);
974 assert_eq!(rule.round_up(Decimal::MAX - dec!(1)), None);
975 }
976
977 fn two_quantity_steps() -> OrderRules {
979 OrderRules::new(
980 ValueRule::default(),
981 stepped(dec!(0.05)),
982 stepped(dec!(0.02)),
983 ValueRule::default(),
984 )
985 }
986
987 #[test]
988 fn a_market_quantity_rounds_to_the_grid_both_quantity_steps_share() {
989 let rules = two_quantity_steps();
990 assert_eq!(
991 rules.round_quantity_down(OrderKind::Market, dec!(0.19)),
992 Some(dec!(0.1))
993 );
994 assert_eq!(
995 rules.round_quantity_up(OrderKind::Market, dec!(0.11)),
996 Some(dec!(0.2))
997 );
998 assert_eq!(
1000 rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(0.1)),
1001 Ok(())
1002 );
1003 }
1004
1005 #[test]
1006 fn any_other_quantity_rounds_to_the_quantity_step_alone() {
1007 let rules = two_quantity_steps();
1008 assert_eq!(
1009 rules.round_quantity_down(OrderKind::Limit, dec!(0.19)),
1010 Some(dec!(0.15))
1011 );
1012 assert_eq!(
1013 rules.round_quantity_up(
1014 OrderKind::Stop {
1015 trigger_price: dec!(1)
1016 },
1017 dec!(0.11)
1018 ),
1019 Some(dec!(0.15))
1020 );
1021 }
1022
1023 #[test]
1024 fn a_market_quantity_with_one_step_off_rounds_to_the_other() {
1025 let quantity_only = OrderRules::new(
1026 ValueRule::default(),
1027 stepped(dec!(0.05)),
1028 ValueRule::default(),
1029 ValueRule::default(),
1030 );
1031 assert_eq!(
1032 quantity_only.round_quantity_down(OrderKind::Market, dec!(0.19)),
1033 Some(dec!(0.15))
1034 );
1035 let market_only = OrderRules::new(
1036 ValueRule::default(),
1037 ValueRule::default(),
1038 stepped(dec!(0.02)),
1039 ValueRule::default(),
1040 );
1041 assert_eq!(
1042 market_only.round_quantity_down(OrderKind::Market, dec!(0.19)),
1043 Some(dec!(0.18))
1044 );
1045 assert_eq!(
1046 OrderRules::default().round_quantity_up(OrderKind::Market, dec!(0.19)),
1047 Some(dec!(0.19))
1048 );
1049 }
1050
1051 #[test]
1052 fn the_shared_grid_is_the_least_common_multiple_of_the_steps() {
1053 assert_eq!(common_step(dec!(0.05), dec!(0.02)), Some(dec!(0.1)));
1054 assert_eq!(common_step(dec!(0.001), dec!(0.01)), Some(dec!(0.01)));
1055 assert_eq!(common_step(dec!(0.01), dec!(0.001)), Some(dec!(0.01)));
1056 assert_eq!(common_step(dec!(0.4), dec!(0.6)), Some(dec!(1.2)));
1057 assert_eq!(common_step(dec!(0.4), dec!(6)), Some(dec!(6)));
1058 assert_eq!(common_step(dec!(3), dec!(0.5)), Some(dec!(3)));
1059 assert_eq!(common_step(dec!(0.25), dec!(0.25)), Some(dec!(0.25)));
1060 }
1061
1062 #[test]
1063 fn steps_whose_shared_grid_a_decimal_cannot_hold_round_to_none() {
1064 let a = Decimal::from_i128_with_scale(7_922_816_251_426_433_759_354_395_033, 27);
1066 let b = Decimal::from_i128_with_scale(7_922_816_251_426_433_759_354_395_031, 27);
1067 assert_eq!(common_step(a, b), None);
1068 let rules = OrderRules::new(
1069 ValueRule::default(),
1070 stepped(a),
1071 stepped(b),
1072 ValueRule::default(),
1073 );
1074 assert_eq!(rules.round_quantity_down(OrderKind::Market, dec!(1)), None);
1075 assert_eq!(rules.round_quantity_up(OrderKind::Market, dec!(1)), None);
1076 assert_eq!(rules.round_quantity_down(OrderKind::Limit, a), Some(a));
1078 }
1079
1080 fn trailing_rules() -> OrderRules {
1082 OrderRules::default().with_trailing_offsets(
1083 ValueRule::new(dec!(10), dec!(2000), dec!(0)),
1084 ValueRule::new(dec!(20), dec!(1000), dec!(0)),
1085 )
1086 }
1087
1088 fn trailing_stop(offset: Decimal, offset_type: TrailingOffsetType) -> OrderKind {
1089 OrderKind::TrailingStop {
1090 offset,
1091 offset_type,
1092 activation_price: None,
1093 }
1094 }
1095
1096 #[test]
1097 fn a_buy_trailing_offset_is_checked_against_the_range_above_and_a_sell_against_below() {
1098 let check = |side, offset| {
1099 trailing_rules().check::<(), ()>(
1100 side,
1101 trailing_stop(offset, TrailingOffsetType::BasisPoints),
1102 None,
1103 dec!(1),
1104 )
1105 };
1106 assert_eq!(check(Side::Buy, dec!(15)), Ok(()));
1108 assert_eq!(
1109 check(Side::Sell, dec!(15)),
1110 Err(bounds(
1111 OrderField::TrailingOffset,
1112 Bound::Min,
1113 dec!(20),
1114 dec!(15)
1115 ))
1116 );
1117 assert_eq!(check(Side::Buy, dec!(1500)), Ok(()));
1119 assert_eq!(
1120 check(Side::Sell, dec!(1500)),
1121 Err(bounds(
1122 OrderField::TrailingOffset,
1123 Bound::Max,
1124 dec!(1000),
1125 dec!(1500)
1126 ))
1127 );
1128 assert_eq!(
1129 check(Side::Buy, dec!(9)),
1130 Err(bounds(
1131 OrderField::TrailingOffset,
1132 Bound::Min,
1133 dec!(10),
1134 dec!(9)
1135 ))
1136 );
1137 assert_eq!(
1138 check(Side::Buy, dec!(2001)),
1139 Err(bounds(
1140 OrderField::TrailingOffset,
1141 Bound::Max,
1142 dec!(2000),
1143 dec!(2001)
1144 ))
1145 );
1146 for (side, offset) in [
1148 (Side::Buy, dec!(10)),
1149 (Side::Buy, dec!(2000)),
1150 (Side::Sell, dec!(20)),
1151 (Side::Sell, dec!(1000)),
1152 ] {
1153 assert_eq!(check(side, offset), Ok(()), "{side:?} {offset}");
1154 }
1155 }
1156
1157 #[test]
1158 fn a_percentage_offset_is_checked_and_reported_in_percent() {
1159 let check = |offset| {
1160 trailing_rules().check::<(), ()>(
1161 Side::Buy,
1162 trailing_stop(offset, TrailingOffsetType::Percentage),
1163 None,
1164 dec!(1),
1165 )
1166 };
1167 assert_eq!(check(dec!(0.1)), Ok(()));
1169 assert_eq!(check(dec!(20)), Ok(()));
1170 assert_eq!(
1171 check(dec!(0.09)),
1172 Err(bounds(
1173 OrderField::TrailingOffset,
1174 Bound::Min,
1175 dec!(0.1),
1176 dec!(0.09)
1177 ))
1178 );
1179 assert_eq!(
1180 check(dec!(20.5)),
1181 Err(bounds(
1182 OrderField::TrailingOffset,
1183 Bound::Max,
1184 dec!(20),
1185 dec!(20.5)
1186 ))
1187 );
1188 }
1189
1190 #[test]
1191 fn an_absolute_offset_is_not_checked() {
1192 assert_eq!(
1193 trailing_rules().check::<(), ()>(
1194 Side::Buy,
1195 trailing_stop(dec!(0.0001), TrailingOffsetType::Absolute),
1196 None,
1197 dec!(1),
1198 ),
1199 Ok(())
1200 );
1201 }
1202
1203 #[test]
1204 fn every_trailing_kind_checks_its_offset() {
1205 let offset = dec!(5);
1206 for kind in [
1207 OrderKind::TrailingStopLimit {
1208 offset,
1209 offset_type: TrailingOffsetType::BasisPoints,
1210 stop_price: dec!(100),
1211 limit_offset: dec!(1),
1212 },
1213 OrderKind::TrailingStopFixedLimit {
1214 offset,
1215 offset_type: TrailingOffsetType::BasisPoints,
1216 activation_price: None,
1217 },
1218 ] {
1219 assert_eq!(
1220 trailing_rules().check::<(), ()>(Side::Sell, kind, Some(dec!(100)), dec!(1)),
1221 Err(bounds(
1222 OrderField::TrailingOffset,
1223 Bound::Min,
1224 dec!(20),
1225 offset
1226 )),
1227 "{kind:?}"
1228 );
1229 }
1230 }
1231
1232 #[test]
1233 fn a_trailing_offset_is_checked_after_the_prices_and_before_the_quantity() {
1234 let rules = rules().with_trailing_offsets(
1235 ValueRule::new(dec!(10), dec!(0), dec!(0)),
1236 ValueRule::default(),
1237 );
1238 let kind = |activation_price| OrderKind::TrailingStop {
1239 offset: dec!(1),
1240 offset_type: TrailingOffsetType::BasisPoints,
1241 activation_price,
1242 };
1243 assert_eq!(
1244 rules.check::<(), ()>(Side::Buy, kind(Some(dec!(1.001))), None, dec!(0.0005)),
1245 Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
1246 );
1247 assert_eq!(
1248 rules.check::<(), ()>(Side::Buy, kind(None), None, dec!(0.0005)),
1249 Err(bounds(
1250 OrderField::TrailingOffset,
1251 Bound::Min,
1252 dec!(10),
1253 dec!(1)
1254 ))
1255 );
1256 }
1257
1258 #[test]
1259 fn a_trailing_rules_step_is_not_used() {
1260 let rules = OrderRules::default().with_trailing_offsets(
1261 ValueRule::new(dec!(0), dec!(0), dec!(10)),
1262 ValueRule::new(dec!(0), dec!(0), dec!(10)),
1263 );
1264 for side in [Side::Buy, Side::Sell] {
1265 assert_eq!(
1266 rules.check::<(), ()>(
1267 side,
1268 trailing_stop(dec!(15), TrailingOffsetType::BasisPoints),
1269 None,
1270 dec!(1)
1271 ),
1272 Ok(()),
1273 "{side:?}"
1274 );
1275 }
1276 }
1277
1278 #[test]
1279 fn the_trailing_ranges_leave_other_kinds_alone() {
1280 let rules = OrderRules::default().with_trailing_offsets(
1281 ValueRule::new(dec!(10), dec!(20), dec!(0)),
1282 ValueRule::new(dec!(10), dec!(20), dec!(0)),
1283 );
1284 for kind in [
1285 OrderKind::Market,
1286 OrderKind::Limit,
1287 OrderKind::Stop {
1288 trigger_price: dec!(1),
1289 },
1290 OrderKind::ConditionalUnresolved {
1291 family: ConditionalFamily::Stop,
1292 trigger_price: None,
1293 },
1294 ] {
1295 assert_eq!(
1296 rules.check::<(), ()>(Side::Sell, kind, Some(dec!(1)), dec!(1)),
1297 Ok(()),
1298 "{kind:?}"
1299 );
1300 }
1301 }
1302
1303 #[test]
1304 fn the_market_notional_is_never_checked() {
1305 let rules =
1306 OrderRules::default().with_market_notional(ValueRule::new(dec!(5), dec!(10), dec!(0)));
1307 for (kind, price) in [
1308 (OrderKind::Market, Some(dec!(1))),
1309 (OrderKind::Limit, Some(dec!(1))),
1310 ] {
1311 assert_eq!(
1312 rules.check::<(), ()>(Side::Buy, kind, price, dec!(1)),
1313 Ok(()),
1314 "{kind:?}"
1315 );
1316 }
1317 }
1318
1319 #[test]
1320 fn the_new_rules_count_towards_being_on_and_new_leaves_them_off() {
1321 let on = ValueRule::new(dec!(1), dec!(0), dec!(0));
1322 let off = ValueRule::default();
1323 assert!(!OrderRules::default().with_market_notional(on).is_off());
1324 assert!(
1325 !OrderRules::default()
1326 .with_trailing_offsets(on, off)
1327 .is_off()
1328 );
1329 assert!(
1330 !OrderRules::default()
1331 .with_trailing_offsets(off, on)
1332 .is_off()
1333 );
1334 let built = rules();
1335 assert!(built.market_notional.is_off());
1336 assert!(built.trailing_offset_above.is_off());
1337 assert!(built.trailing_offset_below.is_off());
1338 let with = built
1340 .with_market_notional(on)
1341 .with_trailing_offsets(on, off);
1342 assert_eq!(with.price, built.price);
1343 assert_eq!(with.notional, built.notional);
1344 assert_eq!(with.market_notional, on);
1345 assert_eq!(with.trailing_offset_above, on);
1346 assert_eq!(with.trailing_offset_below, off);
1347 }
1348
1349 #[test]
1352 fn rules_serialised_without_the_newer_parts_load_with_them_off() {
1353 let rule = r#"{"min":"1","max":null,"step":null}"#;
1354 let json = format!(
1355 r#"{{"price":{rule},"quantity":{rule},"market_quantity":{rule},"notional":{rule}}}"#
1356 );
1357 let loaded: OrderRules = match serde_json::from_str(&json) {
1358 Ok(loaded) => loaded,
1359 Err(error) => panic!("older rules should load: {error}"),
1360 };
1361 let on = ValueRule::new(dec!(1), dec!(0), dec!(0));
1362 assert_eq!(loaded, OrderRules::new(on, on, on, on));
1363 }
1364}