1use crate::Decimal;
8
9macro_rules! impl_decimal_financial {
10 ($backing:ty) => {
11 impl<const D: u8> Decimal<$backing, D> {
12 #[inline(always)]
35 pub const fn midpoint(self, other: Self) -> Self {
36 let a = self.value;
40 let b = other.value;
41 Self {
42 value: (a & b) + ((a ^ b) >> 1),
43 }
44 }
45
46 #[inline(always)]
50 pub const fn spread(self, other: Self) -> Option<Self> {
51 if self.value < other.value {
52 None
53 } else {
54 match self.value.checked_sub(other.value) {
55 Some(v) => Some(Self { value: v }),
56 None => None,
57 }
58 }
59 }
60
61 #[inline(always)]
77 pub const fn round_to_tick(self, tick: Self) -> Option<Self> {
78 assert!(tick.value > 0, "tick must be positive");
79 let remainder = self.value % tick.value;
80 if remainder == 0 {
81 return Some(self);
82 }
83 let half_tick = tick.value / 2;
84 let base = self.value - remainder;
85
86 if remainder > half_tick {
87 match base.checked_add(tick.value) {
88 Some(v) => Some(Self { value: v }),
89 None => None,
90 }
91 } else if remainder < -half_tick {
92 match base.checked_sub(tick.value) {
93 Some(v) => Some(Self { value: v }),
94 None => None,
95 }
96 } else if remainder == half_tick || remainder == -half_tick {
97 let quotient = self.value / tick.value;
98 if quotient % 2 != 0 {
99 if remainder > 0 {
100 match base.checked_add(tick.value) {
101 Some(v) => Some(Self { value: v }),
102 None => None,
103 }
104 } else {
105 match base.checked_sub(tick.value) {
106 Some(v) => Some(Self { value: v }),
107 None => None,
108 }
109 }
110 } else {
111 Some(Self { value: base })
112 }
113 } else {
114 Some(Self { value: base })
115 }
116 }
117
118 #[inline(always)]
122 pub const fn floor_to_tick(self, tick: Self) -> Option<Self> {
123 assert!(tick.value > 0, "tick must be positive");
124 let remainder = self.value % tick.value;
125 if remainder >= 0 {
126 Some(Self {
127 value: self.value - remainder,
128 })
129 } else {
130 match (self.value - remainder).checked_sub(tick.value) {
131 Some(v) => Some(Self { value: v }),
132 None => None,
133 }
134 }
135 }
136
137 #[inline(always)]
141 pub const fn ceil_to_tick(self, tick: Self) -> Option<Self> {
142 assert!(tick.value > 0, "tick must be positive");
143 let remainder = self.value % tick.value;
144 if remainder > 0 {
145 match (self.value - remainder).checked_add(tick.value) {
146 Some(v) => Some(Self { value: v }),
147 None => None,
148 }
149 } else if remainder < 0 {
150 Some(Self {
151 value: self.value - remainder,
152 })
153 } else {
154 Some(self)
155 }
156 }
157
158 #[inline(always)]
166 pub const fn halve(self) -> Self {
167 Self {
168 value: self.value / 2,
169 }
170 }
171
172 #[inline(always)]
174 pub const fn div10(self) -> Self {
175 Self {
176 value: self.value / 10,
177 }
178 }
179
180 #[inline(always)]
182 pub const fn div100(self) -> Self {
183 Self {
184 value: self.value / 100,
185 }
186 }
187
188 #[inline]
198 pub const fn approx_eq(self, other: Self, tolerance: Self) -> bool {
199 let (diff, overflow) = if self.value >= other.value {
200 self.value.overflowing_sub(other.value)
201 } else {
202 other.value.overflowing_sub(self.value)
203 };
204 !overflow && diff <= tolerance.value
205 }
206
207 #[inline]
209 pub const fn clamp_price(self, min: Self, max: Self) -> Self {
210 if self.value < min.value {
211 min
212 } else if self.value > max.value {
213 max
214 } else {
215 self
216 }
217 }
218
219 #[inline(always)]
227 pub const fn is_tick_aligned(self, tick: Self) -> bool {
228 assert!(tick.value > 0, "tick must be positive");
229 self.value % tick.value == 0
230 }
231
232 #[inline(always)]
241 pub const fn round_bps(self, n: u32) -> Option<Self> {
242 const { assert!(D >= 4, "round_bps requires D >= 4") };
243 if n == 0 {
244 return None;
245 }
246 let bp_raw = Self::SCALE / 10000;
247 let Some(tick_wide) = (bp_raw as i128).checked_mul(n as i128) else {
248 return None;
249 };
250 if tick_wide > <$backing>::MAX as i128 || tick_wide <= 0 {
251 return None;
252 }
253 self.round_to_tick(Self {
254 value: tick_wide as $backing,
255 })
256 }
257
258 #[inline(always)]
267 pub const fn floor_bps(self, n: u32) -> Option<Self> {
268 const { assert!(D >= 4, "floor_bps requires D >= 4") };
269 if n == 0 {
270 return None;
271 }
272 let bp_raw = Self::SCALE / 10000;
273 let Some(tick_wide) = (bp_raw as i128).checked_mul(n as i128) else {
274 return None;
275 };
276 if tick_wide > <$backing>::MAX as i128 || tick_wide <= 0 {
277 return None;
278 }
279 self.floor_to_tick(Self {
280 value: tick_wide as $backing,
281 })
282 }
283
284 #[inline(always)]
293 pub const fn ceil_bps(self, n: u32) -> Option<Self> {
294 const { assert!(D >= 4, "ceil_bps requires D >= 4") };
295 if n == 0 {
296 return None;
297 }
298 let bp_raw = Self::SCALE / 10000;
299 let Some(tick_wide) = (bp_raw as i128).checked_mul(n as i128) else {
300 return None;
301 };
302 if tick_wide > <$backing>::MAX as i128 || tick_wide <= 0 {
303 return None;
304 }
305 self.ceil_to_tick(Self {
306 value: tick_wide as $backing,
307 })
308 }
309 }
310 };
311}
312
313impl_decimal_financial!(i32);
314impl_decimal_financial!(i64);
315impl_decimal_financial!(i128);
316
317impl<const D: u8> Decimal<i32, D> {
324 #[inline]
328 pub const fn percent_of(self, percent: Self) -> Option<Self> {
329 let product = (self.value as i64) * (percent.value as i64);
330 let scale_100 = (Self::SCALE as i64) * 100;
331 let result = product / scale_100;
332 if result > i32::MAX as i64 || result < i32::MIN as i64 {
333 None
334 } else {
335 Some(Self {
336 value: result as i32,
337 })
338 }
339 }
340
341 #[inline]
343 pub const fn to_bps(self) -> Option<Self> {
344 self.mul_int(10_000)
345 }
346
347 #[inline]
349 pub const fn from_bps(bps: i32) -> Option<Self> {
350 let scaled = bps as i64 * Self::SCALE as i64 / 10_000;
351 if scaled > i32::MAX as i64 || scaled < i32::MIN as i64 {
352 None
353 } else {
354 Some(Self {
355 value: scaled as i32,
356 })
357 }
358 }
359
360 #[inline]
362 pub const fn mul_div(self, mul: Self, div: Self) -> Option<Self> {
363 if div.value == 0 {
364 return None;
365 }
366 let product = (self.value as i64) * (mul.value as i64);
367 let result = product / (div.value as i64);
368 if result > i32::MAX as i64 || result < i32::MIN as i64 {
369 None
370 } else {
371 Some(Self {
372 value: result as i32,
373 })
374 }
375 }
376}
377
378impl<const D: u8> Decimal<i64, D> {
381 #[inline]
385 pub const fn percent_of(self, percent: Self) -> Option<Self> {
386 let product = (self.value as i128) * (percent.value as i128);
387 let scale_100 = (Self::SCALE as i128) * 100;
388 let result = product / scale_100;
389 if result > i64::MAX as i128 || result < i64::MIN as i128 {
390 None
391 } else {
392 Some(Self {
393 value: result as i64,
394 })
395 }
396 }
397
398 #[inline]
400 pub const fn to_bps(self) -> Option<Self> {
401 self.mul_int(10_000)
402 }
403
404 #[inline]
406 pub const fn from_bps(bps: i64) -> Option<Self> {
407 let scaled = (bps as i128) * (Self::SCALE as i128);
408 let value = scaled / 10_000;
409 if value > i64::MAX as i128 || value < i64::MIN as i128 {
410 None
411 } else {
412 Some(Self {
413 value: value as i64,
414 })
415 }
416 }
417
418 #[inline]
423 pub const fn mul_div(self, mul: Self, div: Self) -> Option<Self> {
424 if div.value == 0 {
425 return None;
426 }
427 let product = (self.value as i128) * (mul.value as i128);
428 let result = product / (div.value as i128);
429 if result > i64::MAX as i128 || result < i64::MIN as i128 {
430 None
431 } else {
432 Some(Self {
433 value: result as i64,
434 })
435 }
436 }
437}
438
439macro_rules! impl_financial_widening {
444 ($backing:ty, $wider:ty) => {
445 impl<const D: u8> Decimal<$backing, D> {
446 #[inline]
448 pub const fn bps_of(self, bps: i32) -> Option<Self> {
449 let product = (self.value as $wider) * (bps as $wider);
450 let result = product / 10000;
451 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
452 None
453 } else {
454 Some(Self {
455 value: result as $backing,
456 })
457 }
458 }
459
460 #[inline]
462 pub const fn pct_of(self, pct: i32) -> Option<Self> {
463 let product = (self.value as $wider) * (pct as $wider);
464 let result = product / 100;
465 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
466 None
467 } else {
468 Some(Self {
469 value: result as $backing,
470 })
471 }
472 }
473
474 #[inline]
476 pub const fn shift_bps(self, bps: i32) -> Option<Self> {
477 let factor = 10000_i64 + bps as i64;
478 let product = (self.value as $wider) * (factor as $wider);
479 let result = product / 10000;
480 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
481 None
482 } else {
483 Some(Self {
484 value: result as $backing,
485 })
486 }
487 }
488
489 #[inline]
491 pub const fn shift_pct(self, pct: i32) -> Option<Self> {
492 let factor = 100_i64 + pct as i64;
493 let product = (self.value as $wider) * (factor as $wider);
494 let result = product / 100;
495 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
496 None
497 } else {
498 Some(Self {
499 value: result as $backing,
500 })
501 }
502 }
503
504 #[inline]
510 pub const fn bps_diff_by(self, other: Self, divisor: Self) -> Option<Self> {
511 if divisor.value == 0 {
512 return None;
513 }
514 let diff = (self.value as $wider) - (other.value as $wider);
515 let diff_scaled = diff * (Self::SCALE as $wider);
516 let divisor_w = divisor.value as $wider;
517 let q = diff_scaled / divisor_w;
518 let r = diff_scaled % divisor_w;
519 let Some(main) = q.checked_mul(10000) else {
520 return None;
521 };
522 let frac = r * 10000 / divisor_w;
523 let Some(result) = main.checked_add(frac) else {
524 return None;
525 };
526 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
527 None
528 } else {
529 Some(Self {
530 value: result as $backing,
531 })
532 }
533 }
534
535 #[inline]
537 pub const fn bps_diff(self, other: Self) -> Option<Self> {
538 self.bps_diff_by(other, other)
539 }
540
541 #[inline]
544 pub const fn pct_diff_by(self, other: Self, divisor: Self) -> Option<Self> {
545 if divisor.value == 0 {
546 return None;
547 }
548 let diff = (self.value as $wider) - (other.value as $wider);
549 let diff_scaled = diff * (Self::SCALE as $wider);
550 let divisor_w = divisor.value as $wider;
551 let q = diff_scaled / divisor_w;
552 let r = diff_scaled % divisor_w;
553 let Some(main) = q.checked_mul(100) else {
554 return None;
555 };
556 let frac = r * 100 / divisor_w;
557 let Some(result) = main.checked_add(frac) else {
558 return None;
559 };
560 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
561 None
562 } else {
563 Some(Self {
564 value: result as $backing,
565 })
566 }
567 }
568
569 #[inline]
571 pub const fn pct_diff(self, other: Self) -> Option<Self> {
572 self.pct_diff_by(other, other)
573 }
574
575 #[inline]
577 pub const fn within_bps(self, other: Self, bps: i32) -> bool {
578 if bps < 0 {
579 return false;
580 }
581 let diff = if self.value >= other.value {
582 (self.value as $wider) - (other.value as $wider)
583 } else {
584 (other.value as $wider) - (self.value as $wider)
585 };
586 let other_abs = (other.value as $wider).abs();
587 let threshold = other_abs * (bps as $wider) / 10000;
588 diff <= threshold
589 }
590
591 #[inline]
595 pub const fn within_ticks(self, other: Self, n: i64, tick: Self) -> bool {
596 assert!(tick.value > 0, "tick must be positive");
597 let diff = if self.value >= other.value {
598 (self.value as $wider) - (other.value as $wider)
599 } else {
600 (other.value as $wider) - (self.value as $wider)
601 };
602 let Some(threshold) = (n as $wider).checked_mul(tick.value as $wider) else {
603 return n > 0;
604 };
605 diff <= threshold
606 }
607
608 #[inline]
612 pub const fn add_ticks(self, n: i64, tick: Self) -> Option<Self> {
613 assert!(tick.value > 0, "tick must be positive");
614 let Some(offset) = (n as $wider).checked_mul(tick.value as $wider) else {
615 return None;
616 };
617 let Some(result) = (self.value as $wider).checked_add(offset) else {
618 return None;
619 };
620 if result > <$backing>::MAX as $wider || result < <$backing>::MIN as $wider {
621 None
622 } else {
623 Some(Self {
624 value: result as $backing,
625 })
626 }
627 }
628
629 #[inline]
638 pub const fn tick_diff(self, other: Self, tick: Self) -> Option<i64> {
639 assert!(tick.value > 0, "tick must be positive");
640 let diff = (self.value as $wider) - (other.value as $wider);
641 let ticks = diff / (tick.value as $wider);
642 if ticks > i64::MAX as $wider || ticks < i64::MIN as $wider {
643 None
644 } else {
645 Some(ticks as i64)
646 }
647 }
648 }
649 };
650}
651
652impl_financial_widening!(i32, i64);
653impl_financial_widening!(i64, i128);
654
655impl<const D: u8> Decimal<i128, D> {
658 #[inline]
660 pub const fn to_bps(self) -> Option<Self> {
661 self.mul_int(10_000)
662 }
663
664 #[inline]
666 pub const fn from_bps(bps: i128) -> Option<Self> {
667 match (bps).checked_mul(Self::SCALE) {
668 Some(scaled) => Some(Self {
669 value: scaled / 10_000,
670 }),
671 None => None,
672 }
673 }
674
675 #[inline]
681 pub fn mul_div(self, mul: Self, div: Self) -> Option<Self> {
682 if div.value == 0 {
683 return None;
684 }
685 let product = self.checked_mul(mul)?;
686 product.checked_div(div)
687 }
688
689 #[inline]
693 pub const fn bps_of(self, bps: i32) -> Option<Self> {
694 let q = self.value / 10000;
695 let r = self.value % 10000;
696 let Some(main) = q.checked_mul(bps as i128) else {
697 return None;
698 };
699 let frac = r * (bps as i128) / 10000;
700 match main.checked_add(frac) {
701 Some(v) => Some(Self { value: v }),
702 None => None,
703 }
704 }
705
706 #[inline]
710 pub const fn pct_of(self, pct: i32) -> Option<Self> {
711 let q = self.value / 100;
712 let r = self.value % 100;
713 let Some(main) = q.checked_mul(pct as i128) else {
714 return None;
715 };
716 let frac = r * (pct as i128) / 100;
717 match main.checked_add(frac) {
718 Some(v) => Some(Self { value: v }),
719 None => None,
720 }
721 }
722
723 #[inline]
725 pub const fn shift_bps(self, bps: i32) -> Option<Self> {
726 let factor = 10000_i64 + bps as i64;
727 let q = self.value / 10000;
728 let r = self.value % 10000;
729 let Some(main) = q.checked_mul(factor as i128) else {
730 return None;
731 };
732 let frac = r * (factor as i128) / 10000;
733 match main.checked_add(frac) {
734 Some(v) => Some(Self { value: v }),
735 None => None,
736 }
737 }
738
739 #[inline]
741 pub const fn shift_pct(self, pct: i32) -> Option<Self> {
742 let factor = 100_i64 + pct as i64;
743 let q = self.value / 100;
744 let r = self.value % 100;
745 let Some(main) = q.checked_mul(factor as i128) else {
746 return None;
747 };
748 let frac = r * (factor as i128) / 100;
749 match main.checked_add(frac) {
750 Some(v) => Some(Self { value: v }),
751 None => None,
752 }
753 }
754
755 #[inline]
762 pub const fn bps_diff_by(self, other: Self, divisor: Self) -> Option<Self> {
763 if divisor.value == 0 {
764 return None;
765 }
766 let Some(diff) = self.value.checked_sub(other.value) else {
767 return None;
768 };
769 let q = diff / divisor.value;
770 let r = diff % divisor.value;
771
772 let main = if q == 0 {
773 0
774 } else {
775 let Some(qs) = q.checked_mul(Self::SCALE) else {
776 return None;
777 };
778 let Some(qs10k) = qs.checked_mul(10000) else {
779 return None;
780 };
781 qs10k
782 };
783
784 let Some(rs) = r.checked_mul(Self::SCALE) else {
785 return None;
786 };
787 let rs_q = rs / divisor.value;
788 let rs_r = rs % divisor.value;
789 let Some(frac_main) = rs_q.checked_mul(10000) else {
790 return None;
791 };
792 let frac_sub = match rs_r.checked_mul(10000) {
796 Some(v) => v / divisor.value,
797 None => 0,
798 };
799 let Some(frac) = frac_main.checked_add(frac_sub) else {
800 return None;
801 };
802
803 match main.checked_add(frac) {
804 Some(v) => Some(Self { value: v }),
805 None => None,
806 }
807 }
808
809 #[inline]
811 pub const fn bps_diff(self, other: Self) -> Option<Self> {
812 self.bps_diff_by(other, other)
813 }
814
815 #[inline]
818 pub const fn pct_diff_by(self, other: Self, divisor: Self) -> Option<Self> {
819 if divisor.value == 0 {
820 return None;
821 }
822 let Some(diff) = self.value.checked_sub(other.value) else {
823 return None;
824 };
825 let q = diff / divisor.value;
826 let r = diff % divisor.value;
827
828 let main = if q == 0 {
829 0
830 } else {
831 let Some(qs) = q.checked_mul(Self::SCALE) else {
832 return None;
833 };
834 let Some(qs100) = qs.checked_mul(100) else {
835 return None;
836 };
837 qs100
838 };
839
840 let Some(rs) = r.checked_mul(Self::SCALE) else {
841 return None;
842 };
843 let rs_q = rs / divisor.value;
844 let rs_r = rs % divisor.value;
845 let Some(frac_main) = rs_q.checked_mul(100) else {
846 return None;
847 };
848 let frac_sub = match rs_r.checked_mul(100) {
851 Some(v) => v / divisor.value,
852 None => 0,
853 };
854 let Some(frac) = frac_main.checked_add(frac_sub) else {
855 return None;
856 };
857
858 match main.checked_add(frac) {
859 Some(v) => Some(Self { value: v }),
860 None => None,
861 }
862 }
863
864 #[inline]
866 pub const fn pct_diff(self, other: Self) -> Option<Self> {
867 self.pct_diff_by(other, other)
868 }
869
870 #[inline]
872 pub const fn within_bps(self, other: Self, bps: i32) -> bool {
873 if bps < 0 {
874 return false;
875 }
876 let abs_diff = if self.value >= other.value {
877 self.value.checked_sub(other.value)
878 } else {
879 other.value.checked_sub(self.value)
880 };
881 let Some(diff) = abs_diff else {
882 return false;
883 };
884 let other_abs = if other.value >= 0 {
885 Some(other.value)
886 } else {
887 other.value.checked_neg()
888 };
889 let Some(other_abs) = other_abs else {
890 return false;
891 };
892 match other_abs.checked_mul(bps as i128) {
893 Some(product) => diff <= product / 10000,
894 None => true,
895 }
896 }
897
898 #[inline]
902 pub const fn within_ticks(self, other: Self, n: i64, tick: Self) -> bool {
903 assert!(tick.value > 0, "tick must be positive");
904 let abs_diff = if self.value >= other.value {
905 self.value.checked_sub(other.value)
906 } else {
907 other.value.checked_sub(self.value)
908 };
909 let Some(diff) = abs_diff else {
910 return false;
911 };
912 if n <= 0 {
913 return diff == 0 && n == 0;
914 }
915 match (n as i128).checked_mul(tick.value) {
916 Some(threshold) => diff <= threshold,
917 None => true,
918 }
919 }
920
921 #[inline]
925 pub const fn add_ticks(self, n: i64, tick: Self) -> Option<Self> {
926 assert!(tick.value > 0, "tick must be positive");
927 let Some(offset) = (n as i128).checked_mul(tick.value) else {
928 return None;
929 };
930 match self.value.checked_add(offset) {
931 Some(v) => Some(Self { value: v }),
932 None => None,
933 }
934 }
935
936 #[inline]
945 pub const fn tick_diff(self, other: Self, tick: Self) -> Option<i64> {
946 assert!(tick.value > 0, "tick must be positive");
947 let Some(diff) = self.value.checked_sub(other.value) else {
948 return None;
949 };
950 let ticks = diff / tick.value;
951 if ticks > i64::MAX as i128 || ticks < i64::MIN as i128 {
952 None
953 } else {
954 Some(ticks as i64)
955 }
956 }
957}