1use crate::error::DrmError;
4
5pub fn round_to_tick_size(price: f64, tick_size: f64) -> Result<f64, DrmError> {
22 if tick_size <= 0.0 {
23 return Err(DrmError::InvalidInput(
24 "tick_size must be positive".to_string(),
25 ));
26 }
27
28 Ok((price / tick_size).round() * tick_size)
29}
30
31pub fn is_valid_price(price: f64, tick_size: f64) -> Result<bool, DrmError> {
48 if tick_size <= 0.0 {
49 return Err(DrmError::InvalidInput(
50 "tick_size must be positive".to_string(),
51 ));
52 }
53
54 let rounded = round_to_tick_size(price, tick_size)?;
55 Ok((price - rounded).abs() < (tick_size / 10.0))
56}
57
58pub fn clamp_price(
69 price: f64,
70 min_price: f64,
71 max_price: f64,
72 tick_size: f64,
73) -> Result<f64, DrmError> {
74 let clamped = price.clamp(min_price, max_price);
75 round_to_tick_size(clamped, tick_size)
76}
77
78pub fn mid_price(best_bid: Option<f64>, best_ask: Option<f64>) -> Option<f64> {
87 match (best_bid, best_ask) {
88 (Some(bid), Some(ask)) => Some((bid + ask) / 2.0),
89 _ => None,
90 }
91}
92
93pub fn spread_bps(best_bid: Option<f64>, best_ask: Option<f64>) -> Option<f64> {
102 match (best_bid, best_ask) {
103 (Some(bid), Some(ask)) if bid > 0.0 => {
104 let mid = (bid + ask) / 2.0;
105 Some((ask - bid) / mid * 10000.0)
106 }
107 _ => None,
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_round_to_tick_size() {
117 assert!((round_to_tick_size(0.1234, 0.01).unwrap() - 0.12).abs() < 1e-10);
118 assert!((round_to_tick_size(0.1256, 0.01).unwrap() - 0.13).abs() < 1e-10);
119 assert!((round_to_tick_size(0.5, 0.1).unwrap() - 0.5).abs() < 1e-10);
120 assert!((round_to_tick_size(0.55, 0.1).unwrap() - 0.6).abs() < 1e-10);
121 }
122
123 #[test]
124 fn test_round_to_tick_size_invalid() {
125 assert!(round_to_tick_size(0.5, 0.0).is_err());
126 assert!(round_to_tick_size(0.5, -0.01).is_err());
127 }
128
129 #[test]
130 fn test_is_valid_price() {
131 assert!(is_valid_price(0.12, 0.01).unwrap());
132 assert!(is_valid_price(0.50, 0.01).unwrap());
133 assert!(!is_valid_price(0.123, 0.01).unwrap());
134 assert!(!is_valid_price(0.1234, 0.01).unwrap());
135 }
136
137 #[test]
138 fn test_clamp_price() {
139 assert!((clamp_price(0.15, 0.10, 0.90, 0.01).unwrap() - 0.15).abs() < 1e-10);
140 assert!((clamp_price(0.05, 0.10, 0.90, 0.01).unwrap() - 0.10).abs() < 1e-10);
141 assert!((clamp_price(0.95, 0.10, 0.90, 0.01).unwrap() - 0.90).abs() < 1e-10);
142 }
143
144 #[test]
145 fn test_mid_price() {
146 assert!((mid_price(Some(0.40), Some(0.60)).unwrap() - 0.50).abs() < 1e-10);
147 assert!(mid_price(None, Some(0.60)).is_none());
148 assert!(mid_price(Some(0.40), None).is_none());
149 }
150
151 #[test]
152 fn test_spread_bps() {
153 let spread = spread_bps(Some(0.40), Some(0.60)).unwrap();
156 assert!((spread - 4000.0).abs() < 1e-10);
157 }
158}