use std::time::{SystemTime, UNIX_EPOCH};
use rand::Rng;
use crate::types::{OrderSide, TickSize};
pub fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub fn calculate_order_amounts(
price: f64,
size: f64,
side: OrderSide,
tick_size: TickSize,
) -> (String, String) {
const SIZE_DECIMALS: u32 = 2;
let tick_decimals = tick_size.decimals();
let price_rounded = round_to_decimals(price, tick_decimals);
let size_rounded = round_to_decimals(size, SIZE_DECIMALS);
let cost = price_rounded * size_rounded;
let cost_rounded = round_to_decimals(cost, tick_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)
}
}
}
fn round_to_decimals(value: f64, decimals: u32) -> f64 {
let multiplier = 10_f64.powi(decimals as i32);
(value * multiplier).round() / multiplier
}
fn to_raw_amount(value: f64, decimals: u32) -> String {
let multiplier = 10_f64.powi(decimals as i32);
let raw = (value * multiplier).floor() as u128;
raw.to_string()
}
pub fn generate_salt() -> String {
rand::rng().random::<u128>().to_string()
}
#[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, "5200");
assert_eq!(taker, "10000");
}
#[test]
fn test_calculate_order_amounts_sell() {
let (maker, taker) =
calculate_order_amounts(0.52, 100.0, OrderSide::Sell, TickSize::Hundredth);
assert_eq!(maker, "10000");
assert_eq!(taker, "5200");
}
#[test]
fn test_calculate_order_amounts_tenth_tick_size() {
let (maker, taker) = calculate_order_amounts(0.5, 50.0, OrderSide::Buy, TickSize::Tenth);
assert_eq!(maker, "2500");
assert_eq!(taker, "5000");
}
#[test]
fn test_calculate_order_amounts_thousandth_tick_size() {
let (maker, taker) =
calculate_order_amounts(0.523, 100.0, OrderSide::Buy, TickSize::Thousandth);
assert_eq!(maker, "5230");
assert_eq!(taker, "10000");
}
#[test]
fn test_calculate_order_amounts_ten_thousandth_tick_size() {
let (maker, taker) =
calculate_order_amounts(0.5234, 100.0, OrderSide::Buy, TickSize::TenThousandth);
assert_eq!(maker, "5234");
assert_eq!(taker, "10000");
}
#[test]
fn test_calculate_order_amounts_price_rounding() {
let (maker, taker) =
calculate_order_amounts(0.526, 100.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "5300");
assert_eq!(taker, "10000");
}
#[test]
fn test_calculate_order_amounts_size_rounding() {
let (maker, taker) =
calculate_order_amounts(0.50, 100.567, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "5029");
assert_eq!(taker, "10057");
}
#[test]
fn test_calculate_order_amounts_minimum_price() {
let (maker, taker) =
calculate_order_amounts(0.01, 100.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "100");
assert_eq!(taker, "10000");
}
#[test]
fn test_calculate_order_amounts_maximum_price() {
let (maker, taker) =
calculate_order_amounts(0.99, 100.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "9900");
assert_eq!(taker, "10000");
}
#[test]
fn test_calculate_order_amounts_small_size() {
let (maker, taker) =
calculate_order_amounts(0.50, 0.01, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "1");
assert_eq!(taker, "1");
}
#[test]
fn test_calculate_order_amounts_large_size() {
let (maker, taker) =
calculate_order_amounts(0.50, 10000.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "500000");
assert_eq!(taker, "1000000");
}
#[test]
fn test_current_timestamp_is_reasonable() {
let timestamp = current_timestamp();
assert!(
timestamp > 1704067200,
"Timestamp should be after 2024-01-01"
);
assert!(
timestamp < 4102444800,
"Timestamp should be before 2100-01-01"
);
}
#[test]
fn test_current_timestamp_increases() {
let t1 = current_timestamp();
let t2 = current_timestamp();
assert!(t2 >= t1);
}
#[test]
fn test_generate_salt_is_numeric() {
let salt = generate_salt();
assert!(
salt.parse::<u128>().is_ok(),
"Salt should be a valid u128 string"
);
}
#[test]
fn test_generate_salt_uniqueness() {
let salt1 = generate_salt();
let salt2 = generate_salt();
let salt3 = generate_salt();
assert_ne!(salt1, salt2, "Salts should be unique");
assert_ne!(salt2, salt3, "Salts should be unique");
assert_ne!(salt1, salt3, "Salts should be unique");
}
#[test]
fn test_generate_salt_not_empty() {
let salt = generate_salt();
assert!(!salt.is_empty(), "Salt should not be empty");
}
#[test]
fn test_round_to_decimals() {
let (maker, _) = calculate_order_amounts(0.555, 100.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "5600");
let (maker, _) = calculate_order_amounts(0.554, 100.0, OrderSide::Buy, TickSize::Hundredth);
assert_eq!(maker, "5500"); }
#[test]
fn test_symmetry_buy_sell() {
let (buy_maker, buy_taker) =
calculate_order_amounts(0.60, 50.0, OrderSide::Buy, TickSize::Hundredth);
let (sell_maker, sell_taker) =
calculate_order_amounts(0.60, 50.0, OrderSide::Sell, TickSize::Hundredth);
assert_eq!(buy_maker, sell_taker, "Buy maker should equal sell taker");
assert_eq!(buy_taker, sell_maker, "Buy taker should equal sell maker");
}
}