polyte_clob/
utils.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3use rand::Rng;
4
5use crate::types::{OrderSide, TickSize};
6
7/// Get current Unix timestamp in seconds
8pub fn current_timestamp() -> u64 {
9    SystemTime::now()
10        .duration_since(UNIX_EPOCH)
11        .unwrap_or_default()
12        .as_secs()
13}
14
15/// Calculate maker and taker amounts for an order
16pub fn calculate_order_amounts(
17    price: f64,
18    size: f64,
19    side: OrderSide,
20    tick_size: TickSize,
21) -> (String, String) {
22    const SIZE_DECIMALS: u32 = 2; // shares are in 2 decimals
23
24    let tick_decimals = tick_size.decimals();
25
26    // Round price to tick size
27    let price_rounded = round_to_decimals(price, tick_decimals);
28
29    // Round size to 2 decimals
30    let size_rounded = round_to_decimals(size, SIZE_DECIMALS);
31
32    // Calculate cost
33    let cost = price_rounded * size_rounded;
34    let cost_rounded = round_to_decimals(cost, tick_decimals);
35
36    // Convert to raw amounts (no decimals)
37    let share_amount = to_raw_amount(size_rounded, SIZE_DECIMALS);
38    let cost_amount = to_raw_amount(cost_rounded, SIZE_DECIMALS);
39
40    match side {
41        OrderSide::Buy => {
42            // BUY: maker pays USDC, receives shares
43            (cost_amount, share_amount)
44        }
45        OrderSide::Sell => {
46            // SELL: maker pays shares, receives USDC
47            (share_amount, cost_amount)
48        }
49    }
50}
51
52/// Round a float to specified decimal places
53fn round_to_decimals(value: f64, decimals: u32) -> f64 {
54    let multiplier = 10_f64.powi(decimals as i32);
55    (value * multiplier).round() / multiplier
56}
57
58/// Convert float to raw integer amount
59fn to_raw_amount(value: f64, decimals: u32) -> String {
60    let multiplier = 10_f64.powi(decimals as i32);
61    let raw = (value * multiplier).floor() as u128;
62    raw.to_string()
63}
64
65/// Generate random salt for orders
66pub fn generate_salt() -> String {
67    rand::rng().random::<u128>().to_string()
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_calculate_order_amounts_buy() {
76        let (maker, taker) =
77            calculate_order_amounts(0.52, 100.0, OrderSide::Buy, TickSize::Hundredth);
78
79        // BUY: maker = cost (5200), taker = shares (10000)
80        assert_eq!(maker, "5200");
81        assert_eq!(taker, "10000");
82    }
83
84    #[test]
85    fn test_calculate_order_amounts_sell() {
86        let (maker, taker) =
87            calculate_order_amounts(0.52, 100.0, OrderSide::Sell, TickSize::Hundredth);
88
89        // SELL: maker = shares (10000), taker = cost (5200)
90        assert_eq!(maker, "10000");
91        assert_eq!(taker, "5200");
92    }
93
94    #[test]
95    fn test_calculate_order_amounts_tenth_tick_size() {
96        let (maker, taker) = calculate_order_amounts(0.5, 50.0, OrderSide::Buy, TickSize::Tenth);
97
98        // price=0.5, size=50 => cost=25.0
99        // BUY: maker = cost (2500), taker = shares (5000)
100        assert_eq!(maker, "2500");
101        assert_eq!(taker, "5000");
102    }
103
104    #[test]
105    fn test_calculate_order_amounts_thousandth_tick_size() {
106        let (maker, taker) =
107            calculate_order_amounts(0.523, 100.0, OrderSide::Buy, TickSize::Thousandth);
108
109        // price=0.523, size=100 => cost=52.3
110        // BUY: maker = cost (5230), taker = shares (10000)
111        assert_eq!(maker, "5230");
112        assert_eq!(taker, "10000");
113    }
114
115    #[test]
116    fn test_calculate_order_amounts_ten_thousandth_tick_size() {
117        let (maker, taker) =
118            calculate_order_amounts(0.5234, 100.0, OrderSide::Buy, TickSize::TenThousandth);
119
120        // price=0.5234, size=100 => cost=52.34
121        // BUY: maker = cost (5234), taker = shares (10000)
122        assert_eq!(maker, "5234");
123        assert_eq!(taker, "10000");
124    }
125
126    #[test]
127    fn test_calculate_order_amounts_price_rounding() {
128        // Price 0.526 should round to 0.53 with Hundredth tick size
129        let (maker, taker) =
130            calculate_order_amounts(0.526, 100.0, OrderSide::Buy, TickSize::Hundredth);
131
132        // price rounds to 0.53, size=100 => cost=53.0
133        assert_eq!(maker, "5300");
134        assert_eq!(taker, "10000");
135    }
136
137    #[test]
138    fn test_calculate_order_amounts_size_rounding() {
139        // Size 100.567 should round to 100.57
140        let (maker, taker) =
141            calculate_order_amounts(0.50, 100.567, OrderSide::Buy, TickSize::Hundredth);
142
143        // price=0.50, size rounds to 100.57 => cost=50.285 rounds to 50.29
144        assert_eq!(maker, "5029");
145        assert_eq!(taker, "10057");
146    }
147
148    #[test]
149    fn test_calculate_order_amounts_minimum_price() {
150        let (maker, taker) =
151            calculate_order_amounts(0.01, 100.0, OrderSide::Buy, TickSize::Hundredth);
152
153        // price=0.01, size=100 => cost=1.0
154        assert_eq!(maker, "100");
155        assert_eq!(taker, "10000");
156    }
157
158    #[test]
159    fn test_calculate_order_amounts_maximum_price() {
160        let (maker, taker) =
161            calculate_order_amounts(0.99, 100.0, OrderSide::Buy, TickSize::Hundredth);
162
163        // price=0.99, size=100 => cost=99.0
164        assert_eq!(maker, "9900");
165        assert_eq!(taker, "10000");
166    }
167
168    #[test]
169    fn test_calculate_order_amounts_small_size() {
170        let (maker, taker) =
171            calculate_order_amounts(0.50, 0.01, OrderSide::Buy, TickSize::Hundredth);
172
173        // price=0.50, size=0.01 => cost=0.005 rounds to 0.01
174        assert_eq!(maker, "1");
175        assert_eq!(taker, "1");
176    }
177
178    #[test]
179    fn test_calculate_order_amounts_large_size() {
180        let (maker, taker) =
181            calculate_order_amounts(0.50, 10000.0, OrderSide::Buy, TickSize::Hundredth);
182
183        // price=0.50, size=10000 => cost=5000.0
184        assert_eq!(maker, "500000");
185        assert_eq!(taker, "1000000");
186    }
187
188    #[test]
189    fn test_current_timestamp_is_reasonable() {
190        let timestamp = current_timestamp();
191
192        // Should be after 2024-01-01 (1704067200)
193        assert!(
194            timestamp > 1704067200,
195            "Timestamp should be after 2024-01-01"
196        );
197
198        // Should be before 2100-01-01 (4102444800)
199        assert!(
200            timestamp < 4102444800,
201            "Timestamp should be before 2100-01-01"
202        );
203    }
204
205    #[test]
206    fn test_current_timestamp_increases() {
207        let t1 = current_timestamp();
208        let t2 = current_timestamp();
209
210        // Second call should be >= first (same second or later)
211        assert!(t2 >= t1);
212    }
213
214    #[test]
215    fn test_generate_salt_is_numeric() {
216        let salt = generate_salt();
217
218        // Should parse as u128
219        assert!(
220            salt.parse::<u128>().is_ok(),
221            "Salt should be a valid u128 string"
222        );
223    }
224
225    #[test]
226    fn test_generate_salt_uniqueness() {
227        let salt1 = generate_salt();
228        let salt2 = generate_salt();
229        let salt3 = generate_salt();
230
231        // All salts should be different (statistically guaranteed)
232        assert_ne!(salt1, salt2, "Salts should be unique");
233        assert_ne!(salt2, salt3, "Salts should be unique");
234        assert_ne!(salt1, salt3, "Salts should be unique");
235    }
236
237    #[test]
238    fn test_generate_salt_not_empty() {
239        let salt = generate_salt();
240        assert!(!salt.is_empty(), "Salt should not be empty");
241    }
242
243    #[test]
244    fn test_round_to_decimals() {
245        // Test through calculate_order_amounts behavior
246        // 0.555 with Hundredth should round to 0.56
247        let (maker, _) = calculate_order_amounts(0.555, 100.0, OrderSide::Buy, TickSize::Hundredth);
248        assert_eq!(maker, "5600"); // 0.56 * 100 = 56.0 => 5600
249
250        // 0.554 with Hundredth should round to 0.55
251        let (maker, _) = calculate_order_amounts(0.554, 100.0, OrderSide::Buy, TickSize::Hundredth);
252        assert_eq!(maker, "5500"); // 0.55 * 100 = 55.0 => 5500
253    }
254
255    #[test]
256    fn test_symmetry_buy_sell() {
257        // For same price/size, buy and sell should have swapped maker/taker
258        let (buy_maker, buy_taker) =
259            calculate_order_amounts(0.60, 50.0, OrderSide::Buy, TickSize::Hundredth);
260        let (sell_maker, sell_taker) =
261            calculate_order_amounts(0.60, 50.0, OrderSide::Sell, TickSize::Hundredth);
262
263        assert_eq!(buy_maker, sell_taker, "Buy maker should equal sell taker");
264        assert_eq!(buy_taker, sell_maker, "Buy taker should equal sell maker");
265    }
266}