use rand::Rng;
use rust_decimal::prelude::ToPrimitive;
use crate::{
api::markets::OrderLevel,
types::{OrderSide, TickSize},
};
pub fn calculate_order_amounts(
price: f64,
size: f64,
side: OrderSide,
tick_size: TickSize,
) -> (String, String) {
const SIZE_DECIMALS: u32 = 6;
let tick_decimals = tick_size.decimals();
let price_rounded = round_bankers(price, tick_decimals);
let size_rounded = round_bankers(size, SIZE_DECIMALS);
let cost = price_rounded * size_rounded;
let cost_rounded = round_bankers(cost, SIZE_DECIMALS);
let share_amount = to_raw_amount(size_rounded, SIZE_DECIMALS);
let cost_amount = to_raw_amount(cost_rounded, SIZE_DECIMALS);
match side {
OrderSide::Buy => (cost_amount, share_amount),
OrderSide::Sell => (share_amount, cost_amount),
}
}
pub fn calculate_market_order_amounts(
amount: f64,
price: f64,
side: OrderSide,
tick_size: TickSize,
) -> (String, String) {
const SIZE_DECIMALS: u32 = 6;
let tick_decimals = tick_size.decimals();
let price_rounded = round_bankers(price, tick_decimals);
let amount_rounded = round_bankers(amount, SIZE_DECIMALS);
if price_rounded == 0.0 {
return ("0".to_string(), "0".to_string());
}
match side {
OrderSide::Buy => {
let maker_amount = amount_rounded;
let taker_amount_raw = maker_amount / price_rounded;
let taker_amount = round_to_zero(taker_amount_raw, SIZE_DECIMALS);
(
to_raw_amount(maker_amount, SIZE_DECIMALS),
to_raw_amount(taker_amount, SIZE_DECIMALS),
)
}
OrderSide::Sell => {
let maker_amount = round_to_zero(amount, SIZE_DECIMALS); let taker_amount_raw = maker_amount * price_rounded;
let taker_amount = round_bankers(taker_amount_raw, SIZE_DECIMALS);
(
to_raw_amount(maker_amount, SIZE_DECIMALS),
to_raw_amount(taker_amount, SIZE_DECIMALS),
)
}
}
}
pub fn calculate_market_price(levels: &[OrderLevel], amount: f64, side: OrderSide) -> Option<f64> {
if levels.is_empty() {
return None;
}
let mut sum = 0.0;
for level in levels {
let p = level.price.to_f64()?;
let s = level.size.to_f64()?;
match side {
OrderSide::Buy => {
sum += p * s;
}
OrderSide::Sell => {
sum += s;
}
}
if sum >= amount {
return Some(p);
}
}
None
}
fn to_raw_amount(val: f64, decimals: u32) -> String {
let factor = 10f64.powi(decimals as i32);
let raw = (val * factor).round();
format!("{:.0}", raw)
}
pub fn generate_salt() -> String {
rand::rng().random::<u64>().to_string()
}
fn round_bankers(val: f64, decimals: u32) -> f64 {
let factor = 10f64.powi(decimals as i32);
let v = val * factor;
let r = v.round();
let diff = (v - r).abs();
if (diff - 0.5).abs() < 1e-10 {
if r % 2.0 != 0.0 {
if v > 0.0 {
return (r - 1.0) / factor;
} else {
return (r + 1.0) / factor;
}
}
}
r / factor
}
fn round_to_zero(val: f64, decimals: u32) -> f64 {
let factor = 10f64.powi(decimals as i32);
(val * factor).trunc() / factor
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_order_amounts_buy() {
let (maker, taker) =
calculate_order_amounts(0.52, 100.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "52000000");
assert_eq!(taker, "100000000");
}
#[test]
fn test_calculate_order_amounts_sell() {
let (maker, taker) =
calculate_order_amounts(0.52, 100.0, OrderSide::Sell, TickSize::Hundredth);
assert_eq!(maker, "100000000");
assert_eq!(taker, "52000000");
}
#[test]
fn test_round_bankers() {
assert_eq!(round_bankers(0.5, 0), 0.0);
assert_eq!(round_bankers(1.5, 0), 2.0);
assert_eq!(round_bankers(2.5, 0), 2.0);
assert_eq!(round_bankers(3.5, 0), 4.0);
}
#[test]
fn test_calculate_market_order_amounts_buy() {
let (maker, taker) =
calculate_market_order_amounts(100.0, 0.50, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "100000000");
assert_eq!(taker, "200000000");
}
#[test]
fn test_calculate_market_price_buy_simple() {
use rust_decimal_macros::dec;
let levels = vec![OrderLevel {
price: dec!(0.50),
size: dec!(1000),
}];
let price = calculate_market_price(&levels, 100.0, OrderSide::Buy);
assert_eq!(price, Some(0.50));
}
#[test]
fn test_calculate_market_price_insufficient_liquidity() {
use rust_decimal_macros::dec;
let levels = vec![OrderLevel {
price: dec!(0.50),
size: dec!(10),
}];
let price = calculate_market_price(&levels, 1000.0, OrderSide::Buy);
assert_eq!(
price, None,
"Should return None when liquidity is insufficient"
);
}
#[test]
fn test_calculate_market_price_empty_levels() {
let price = calculate_market_price(&[], 100.0, OrderSide::Buy);
assert_eq!(price, None);
}
#[test]
fn test_calculate_market_price_sell_insufficient() {
use rust_decimal_macros::dec;
let levels = vec![OrderLevel {
price: dec!(0.50),
size: dec!(10),
}];
let price = calculate_market_price(&levels, 100.0, OrderSide::Sell);
assert_eq!(
price, None,
"Should return None when sell liquidity is insufficient"
);
}
#[test]
fn test_generate_salt_u64_range() {
let salt = generate_salt();
let _parsed: u64 = salt.parse().expect("Salt should parse as u64");
let salt2 = generate_salt();
assert_ne!(salt, salt2, "Two random salts should differ");
}
#[test]
fn test_calculate_market_order_amounts_sell() {
let (maker, taker) =
calculate_market_order_amounts(100.0, 0.50, OrderSide::Sell, TickSize::Hundredth);
assert_eq!(maker, "100000000"); assert_eq!(taker, "50000000"); }
#[test]
fn test_calculate_market_order_amounts_zero_price() {
let (maker, taker) =
calculate_market_order_amounts(100.0, 0.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "0");
assert_eq!(taker, "0");
}
#[test]
fn test_calculate_market_order_amounts_sell_zero_price() {
let (maker, taker) =
calculate_market_order_amounts(100.0, 0.0, OrderSide::Sell, TickSize::Hundredth);
assert_eq!(maker, "0");
assert_eq!(taker, "0");
}
#[test]
fn test_calculate_market_order_amounts_tenth_tick() {
let (maker, taker) =
calculate_market_order_amounts(100.0, 0.5, OrderSide::Buy, TickSize::Tenth);
assert_eq!(maker, "100000000");
assert_eq!(taker, "200000000");
}
#[test]
fn test_calculate_market_order_amounts_thousandth_tick() {
let (maker, taker) =
calculate_market_order_amounts(100.0, 0.555, OrderSide::Buy, TickSize::Thousandth);
assert_eq!(maker, "100000000");
let taker_val: u64 = taker.parse().unwrap();
assert!(taker_val > 180_000_000); }
#[test]
fn test_calculate_order_amounts_tenth_tick() {
let (maker, taker) = calculate_order_amounts(0.5, 100.0, OrderSide::Buy, TickSize::Tenth);
assert_eq!(maker, "50000000");
assert_eq!(taker, "100000000");
}
#[test]
fn test_calculate_order_amounts_thousandth_tick() {
let (maker, taker) =
calculate_order_amounts(0.555, 100.0, OrderSide::Buy, TickSize::Thousandth);
assert_eq!(maker, "55500000");
assert_eq!(taker, "100000000");
}
#[test]
fn test_calculate_market_price_sell_simple() {
use rust_decimal_macros::dec;
let levels = vec![OrderLevel {
price: dec!(0.50),
size: dec!(200),
}];
let price = calculate_market_price(&levels, 100.0, OrderSide::Sell);
assert_eq!(price, Some(0.50));
}
#[test]
fn test_calculate_market_price_buy_multiple_levels() {
use rust_decimal_macros::dec;
let levels = vec![
OrderLevel {
price: dec!(0.50),
size: dec!(100),
}, OrderLevel {
price: dec!(0.55),
size: dec!(100),
}, OrderLevel {
price: dec!(0.60),
size: dec!(100),
}, ];
let price = calculate_market_price(&levels, 100.0, OrderSide::Buy);
assert_eq!(price, Some(0.55));
}
#[test]
fn test_round_to_zero() {
assert_eq!(round_to_zero(1.999999, 6), 1.999999);
assert_eq!(round_to_zero(1.9999999, 6), 1.999999);
assert_eq!(round_to_zero(-1.9999999, 6), -1.999999);
assert_eq!(round_to_zero(0.0, 6), 0.0);
}
#[test]
fn test_round_bankers_decimals() {
assert_eq!(round_bankers(1.235, 2), 1.24); assert_eq!(round_bankers(1.245, 2), 1.24); assert_eq!(round_bankers(1.265, 2), 1.26); }
#[test]
fn test_round_bankers_negative() {
assert_eq!(round_bankers(-0.5, 0), 0.0);
assert_eq!(round_bankers(-1.5, 0), -2.0);
assert_eq!(round_bankers(-2.5, 0), -2.0);
}
#[test]
fn test_to_raw_amount() {
assert_eq!(to_raw_amount(1.0, 6), "1000000");
assert_eq!(to_raw_amount(0.5, 6), "500000");
assert_eq!(to_raw_amount(0.0, 6), "0");
assert_eq!(to_raw_amount(123.456789, 6), "123456789");
}
#[test]
fn test_calculate_market_order_amounts_negative_price_treated_as_zero() {
let (maker, taker) =
calculate_market_order_amounts(100.0, -0.5, OrderSide::Buy, TickSize::Hundredth);
let taker_val: i64 = taker.parse().unwrap();
assert!(
taker_val < 0,
"Negative price produces negative taker amount"
);
assert_eq!(maker, "100000000");
}
#[test]
fn test_calculate_order_amounts_small_fractional_size() {
let (maker, taker) =
calculate_order_amounts(0.50, 0.000001, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(taker, "1"); assert_eq!(maker, "0"); }
#[test]
fn test_calculate_market_price_exact_boundary() {
use rust_decimal_macros::dec;
let levels = vec![
OrderLevel {
price: dec!(0.50),
size: dec!(100),
},
OrderLevel {
price: dec!(0.60),
size: dec!(100),
},
];
let price = calculate_market_price(&levels, 50.0, OrderSide::Buy);
assert_eq!(price, Some(0.50));
}
}