use crate::error::OpenPxError;
pub fn round_to_tick_size(price: f64, tick_size: f64) -> Result<f64, OpenPxError> {
if tick_size <= 0.0 {
return Err(OpenPxError::InvalidInput(
"tick_size must be positive".to_string(),
));
}
Ok((price / tick_size).round() * tick_size)
}
pub fn is_valid_price(price: f64, tick_size: f64) -> Result<bool, OpenPxError> {
if tick_size <= 0.0 {
return Err(OpenPxError::InvalidInput(
"tick_size must be positive".to_string(),
));
}
let rounded = round_to_tick_size(price, tick_size)?;
Ok((price - rounded).abs() < (tick_size / 10.0))
}
pub fn clamp_price(
price: f64,
min_price: f64,
max_price: f64,
tick_size: f64,
) -> Result<f64, OpenPxError> {
let clamped = price.clamp(min_price, max_price);
round_to_tick_size(clamped, tick_size)
}
pub fn mid_price(best_bid: Option<f64>, best_ask: Option<f64>) -> Option<f64> {
match (best_bid, best_ask) {
(Some(bid), Some(ask)) => Some((bid + ask) / 2.0),
_ => None,
}
}
pub fn spread_bps(best_bid: Option<f64>, best_ask: Option<f64>) -> Option<f64> {
match (best_bid, best_ask) {
(Some(bid), Some(ask)) if bid > 0.0 => {
let mid = (bid + ask) / 2.0;
Some((ask - bid) / mid * 10000.0)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_to_tick_size() {
assert!((round_to_tick_size(0.1234, 0.01).unwrap() - 0.12).abs() < 1e-10);
assert!((round_to_tick_size(0.1256, 0.01).unwrap() - 0.13).abs() < 1e-10);
assert!((round_to_tick_size(0.5, 0.1).unwrap() - 0.5).abs() < 1e-10);
assert!((round_to_tick_size(0.55, 0.1).unwrap() - 0.6).abs() < 1e-10);
}
#[test]
fn test_round_to_tick_size_invalid() {
assert!(round_to_tick_size(0.5, 0.0).is_err());
assert!(round_to_tick_size(0.5, -0.01).is_err());
}
#[test]
fn test_is_valid_price() {
assert!(is_valid_price(0.12, 0.01).unwrap());
assert!(is_valid_price(0.50, 0.01).unwrap());
assert!(!is_valid_price(0.123, 0.01).unwrap());
assert!(!is_valid_price(0.1234, 0.01).unwrap());
}
#[test]
fn test_clamp_price() {
assert!((clamp_price(0.15, 0.10, 0.90, 0.01).unwrap() - 0.15).abs() < 1e-10);
assert!((clamp_price(0.05, 0.10, 0.90, 0.01).unwrap() - 0.10).abs() < 1e-10);
assert!((clamp_price(0.95, 0.10, 0.90, 0.01).unwrap() - 0.90).abs() < 1e-10);
}
#[test]
fn test_mid_price() {
assert!((mid_price(Some(0.40), Some(0.60)).unwrap() - 0.50).abs() < 1e-10);
assert!(mid_price(None, Some(0.60)).is_none());
assert!(mid_price(Some(0.40), None).is_none());
}
#[test]
fn test_spread_bps() {
let spread = spread_bps(Some(0.40), Some(0.60)).unwrap();
assert!((spread - 4000.0).abs() < 1e-10);
}
}