1use af_move_type::MoveType;
2use af_utilities::{Balance9, IFixed};
3use num_traits::Zero as _;
4
5use crate::clearing_house::ClearingHouse;
6use crate::market::MarketParams;
7
8#[derive(thiserror::Error, Debug)]
9#[non_exhaustive]
10pub enum Error {
11 #[error("Overflow when converting types")]
12 Overflow,
13 #[error("Not enough precision to represent price")]
14 Precision,
15}
16
17pub trait OrderBookUnits {
19 fn price_to_ifixed(&self, price: u64) -> IFixed {
20 let price_ifixed = IFixed::from(price);
21 let lot_size_ifixed = IFixed::from(self.lot_size());
22 let tick_size_ifixed = IFixed::from(self.tick_size());
23 price_ifixed * tick_size_ifixed / lot_size_ifixed
24 }
25
26 fn ifixed_to_price(&self, ifixed: IFixed) -> Result<u64, Error> {
32 if ifixed.is_zero() {
33 return Ok(0);
34 }
35 let price_ifixed =
38 (ifixed * IFixed::from(self.lot_size())) / IFixed::from(self.tick_size());
39 let price: u64 = price_ifixed
40 .integer()
41 .uabs()
42 .try_into()
43 .map_err(|_| Error::Overflow)?;
44 if price == 0 {
45 return Err(Error::Precision);
46 }
47 Ok(price)
48 }
49
50 fn lots_to_ifixed(&self, lots: u64) -> IFixed {
51 let ifixed_lots: IFixed = lots.into();
52 let ifixed_lot_size: IFixed = Balance9::from_inner(self.lot_size()).into();
53 ifixed_lots * ifixed_lot_size
54 }
55
56 fn ifixed_to_lots(&self, ifixed: IFixed) -> Result<u64, Error> {
57 let balance: Balance9 = ifixed.try_into().map_err(|_| Error::Overflow)?;
58 Ok(balance.into_inner() / self.lot_size())
59 }
60
61 fn lot_size(&self) -> u64;
64 fn tick_size(&self) -> u64;
65}
66
67impl OrderBookUnits for MarketParams {
68 fn lot_size(&self) -> u64 {
69 self.lot_size
70 }
71
72 fn tick_size(&self) -> u64 {
73 self.tick_size
74 }
75}
76
77impl<T: MoveType> OrderBookUnits for ClearingHouse<T> {
78 fn lot_size(&self) -> u64 {
79 self.market_params.lot_size
80 }
81
82 fn tick_size(&self) -> u64 {
83 self.market_params.tick_size
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 impl OrderBookUnits for (u64, u64) {
92 fn lot_size(&self) -> u64 {
93 self.0
94 }
95
96 fn tick_size(&self) -> u64 {
97 self.1
98 }
99 }
100
101 #[test]
102 fn orderbook_units() {
103 let mut units = (10_000_000, 1_000_000);
104 let mut ifixed: IFixed;
105
106 ifixed = u64::MAX.into();
107 ifixed += IFixed::from_inner(1.into());
108 insta::assert_snapshot!(ifixed, @"18446744073709551615.000000000000000001");
109 let err = units.ifixed_to_lots(ifixed).unwrap_err();
110 insta::assert_snapshot!(err, @"Overflow when converting types");
111
112 ifixed = IFixed::from_inner(1.into());
114 insta::assert_snapshot!(ifixed, @"0.000000000000000001");
115 let ok = units.ifixed_to_lots(ifixed).unwrap();
116 assert_eq!(ok, 0);
117
118 ifixed = 0.001.try_into().unwrap();
119 insta::assert_snapshot!(ifixed, @"0.001");
120 let err = units.ifixed_to_price(ifixed).unwrap_err();
121 insta::assert_snapshot!(err, @"Not enough precision to represent price");
122
123 ifixed = 0.0.try_into().unwrap();
124 insta::assert_snapshot!(ifixed, @"0.0");
125 let ok = units.ifixed_to_price(ifixed).unwrap();
126 assert_eq!(ok, 0);
127
128 ifixed = 0.1.try_into().unwrap();
129 insta::assert_snapshot!(ifixed, @"0.1");
130 let ok = units.ifixed_to_price(ifixed).unwrap();
131 assert_eq!(ok, 1);
132
133 ifixed = 0.15.try_into().unwrap();
135 insta::assert_snapshot!(ifixed, @"0.15");
136 let ok = units.ifixed_to_price(ifixed).unwrap();
137 assert_eq!(ok, 1);
138
139 ifixed = units.price_to_ifixed(0);
140 insta::assert_snapshot!(ifixed, @"0.0");
141
142 units = (1, u64::MAX);
144 let ok = units.price_to_ifixed(u64::MAX);
145 insta::assert_snapshot!(ok, @"340282366920938463426481119284349108225.0");
146
147 units = (u64::MAX, 1);
149 let ok = units.lots_to_ifixed(u64::MAX);
150 insta::assert_snapshot!(ok, @"340282366920938463426481119284.349108225");
151
152 units = (100000, 1000);
153 let min_amount = units.lots_to_ifixed(1);
154 insta::assert_snapshot!(min_amount, @"0.0001");
155 let price_precision = units.price_to_ifixed(1);
156 insta::assert_snapshot!(price_precision, @"0.01");
157 }
158}