use crc32fast::Hasher;
use kraken_types::Level;
use rust_decimal::Decimal;
pub const DEFAULT_PRICE_PRECISION: u8 = 1;
pub const DEFAULT_QTY_PRECISION: u8 = 8;
pub fn compute_checksum_with_precision(
bids: &[Level],
asks: &[Level],
price_precision: u8,
qty_precision: u8,
) -> u32 {
let mut hasher = Hasher::new();
let top_asks: Vec<_> = asks.iter().take(10).collect();
let top_bids: Vec<_> = bids.iter().take(10).collect();
for ask in &top_asks {
let price_str = format_for_checksum_with_precision(&ask.price, price_precision);
let qty_str = format_for_checksum_with_precision(&ask.qty, qty_precision);
hasher.update(price_str.as_bytes());
hasher.update(qty_str.as_bytes());
}
for bid in &top_bids {
let price_str = format_for_checksum_with_precision(&bid.price, price_precision);
let qty_str = format_for_checksum_with_precision(&bid.qty, qty_precision);
hasher.update(price_str.as_bytes());
hasher.update(qty_str.as_bytes());
}
hasher.finalize()
}
pub fn compute_checksum(bids: &[Level], asks: &[Level]) -> u32 {
compute_checksum_with_precision(bids, asks, DEFAULT_PRICE_PRECISION, DEFAULT_QTY_PRECISION)
}
fn format_for_checksum_with_precision(value: &Decimal, precision: u8) -> String {
use rust_decimal::prelude::ToPrimitive;
let scale = Decimal::new(1, precision as u32);
let rounded = (value / scale).round() * scale;
let formatted = if precision == 0 {
format!("{}", rounded.trunc())
} else {
let f = rounded.to_f64().unwrap_or(0.0);
format!("{:.prec$}", f, prec = precision as usize)
};
let without_decimal = formatted.replace('.', "");
let trimmed = without_decimal.trim_start_matches('0');
if trimmed.is_empty() {
"0".to_string()
} else {
trimmed.to_string()
}
}
#[allow(dead_code)]
fn format_for_checksum_legacy(value: &Decimal) -> String {
let s = value.to_string();
let without_decimal = s.replace('.', "");
let trimmed = without_decimal.trim_start_matches('0');
if trimmed.is_empty() {
"0".to_string()
} else {
trimmed.to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChecksumResult {
pub computed: u32,
pub expected: u32,
}
impl ChecksumResult {
pub fn new(computed: u32, expected: u32) -> Self {
Self { computed, expected }
}
pub fn is_valid(&self) -> bool {
self.computed == self.expected
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_format_for_checksum_legacy() {
assert_eq!(format_for_checksum_legacy(&dec!(45285.2)), "452852");
assert_eq!(format_for_checksum_legacy(&dec!(0.00100000)), "100000");
assert_eq!(format_for_checksum_legacy(&dec!(0.05005)), "5005");
assert_eq!(format_for_checksum_legacy(&dec!(1.5)), "15");
assert_eq!(format_for_checksum_legacy(&dec!(100)), "100");
assert_eq!(format_for_checksum_legacy(&dec!(0.001)), "1");
}
#[test]
fn test_format_for_checksum_with_precision() {
assert_eq!(format_for_checksum_with_precision(&dec!(88813.5), 1), "888135");
assert_eq!(format_for_checksum_with_precision(&dec!(88813.0), 1), "888130");
assert_eq!(format_for_checksum_with_precision(&dec!(0.00460208), 8), "460208");
assert_eq!(format_for_checksum_with_precision(&dec!(0.001), 8), "100000");
assert_eq!(format_for_checksum_with_precision(&dec!(2.85806499), 8), "285806499");
}
#[test]
fn test_checksum_computation() {
let asks = vec![
Level::new(dec!(100.5), dec!(1.0)),
Level::new(dec!(101.0), dec!(2.0)),
];
let bids = vec![
Level::new(dec!(100.0), dec!(1.5)),
Level::new(dec!(99.5), dec!(2.5)),
];
let checksum = compute_checksum(&bids, &asks);
assert!(checksum > 0);
let checksum2 = compute_checksum(&bids, &asks);
assert_eq!(checksum, checksum2);
}
#[test]
fn test_checksum_order_matters() {
let level1 = Level::new(dec!(100), dec!(1));
let level2 = Level::new(dec!(101), dec!(2));
let checksum1 = compute_checksum(std::slice::from_ref(&level1), std::slice::from_ref(&level2));
let checksum2 = compute_checksum(&[level2], &[level1]);
assert_ne!(checksum1, checksum2);
}
#[test]
fn test_checksum_uses_top_10() {
let mut asks: Vec<Level> = (1..=15)
.map(|i| Level::new(Decimal::from(100 + i), dec!(1)))
.collect();
let mut bids: Vec<Level> = (1..=15)
.map(|i| Level::new(Decimal::from(100 - i), dec!(1)))
.collect();
let checksum1 = compute_checksum(&bids, &asks);
asks.push(Level::new(dec!(200), dec!(1)));
bids.push(Level::new(dec!(1), dec!(1)));
let checksum2 = compute_checksum(&bids, &asks);
assert_eq!(checksum1, checksum2);
}
#[test]
fn test_checksum_result() {
let result = ChecksumResult::new(12345, 12345);
assert!(result.is_valid());
let result = ChecksumResult::new(12345, 54321);
assert!(!result.is_valid());
}
}