use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FeeSchedule {
pub maker_fee_bps: i32,
pub taker_fee_bps: i32,
}
impl FeeSchedule {
#[must_use = "FeeSchedule does nothing unless used"]
pub fn new(maker_fee_bps: i32, taker_fee_bps: i32) -> Self {
Self {
maker_fee_bps,
taker_fee_bps,
}
}
#[must_use = "Fee calculation result must be used"]
#[inline]
pub fn calculate_fee(&self, notional: u128, is_maker: bool) -> i128 {
let bps = if is_maker {
self.maker_fee_bps
} else {
self.taker_fee_bps
};
(notional as i128)
.checked_mul(bps as i128)
.map(|product| product / 10_000)
.unwrap_or(i128::MAX) }
#[must_use]
#[inline]
pub fn has_maker_rebate(&self) -> bool {
self.maker_fee_bps < 0
}
#[must_use]
#[inline]
pub fn is_zero_fee(&self) -> bool {
self.maker_fee_bps == 0 && self.taker_fee_bps == 0
}
#[must_use]
pub fn zero_fee() -> Self {
Self::new(0, 0)
}
#[must_use]
pub fn taker_only(taker_fee_bps: i32) -> Self {
Self::new(0, taker_fee_bps)
}
#[must_use]
pub fn with_maker_rebate(maker_rebate_bps: i32, taker_fee_bps: i32) -> Self {
Self::new(-maker_rebate_bps.abs(), taker_fee_bps)
}
}
impl Default for FeeSchedule {
fn default() -> Self {
Self::zero_fee()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fee_schedule_creation() {
let schedule = FeeSchedule::new(-2, 5);
assert_eq!(schedule.maker_fee_bps, -2);
assert_eq!(schedule.taker_fee_bps, 5);
}
#[test]
fn test_zero_fee() {
let schedule = FeeSchedule::zero_fee();
assert!(schedule.is_zero_fee());
assert_eq!(schedule.maker_fee_bps, 0);
assert_eq!(schedule.taker_fee_bps, 0);
}
#[test]
fn test_taker_only() {
let schedule = FeeSchedule::taker_only(10);
assert_eq!(schedule.maker_fee_bps, 0);
assert_eq!(schedule.taker_fee_bps, 10);
}
#[test]
fn test_maker_rebate() {
let schedule = FeeSchedule::with_maker_rebate(3, 7);
assert_eq!(schedule.maker_fee_bps, -3);
assert_eq!(schedule.taker_fee_bps, 7);
assert!(schedule.has_maker_rebate());
}
#[test]
fn test_calculate_taker_fee() {
let schedule = FeeSchedule::new(-2, 5);
let notional = 100_000_000;
let fee = schedule.calculate_fee(notional, false);
assert_eq!(fee, 50_000); }
#[test]
fn test_calculate_maker_rebate() {
let schedule = FeeSchedule::new(-2, 5);
let notional = 100_000_000;
let rebate = schedule.calculate_fee(notional, true);
assert_eq!(rebate, -20_000); }
#[test]
fn test_zero_fee_calculation() {
let schedule = FeeSchedule::zero_fee();
let notional = 100_000_000;
assert_eq!(schedule.calculate_fee(notional, true), 0);
assert_eq!(schedule.calculate_fee(notional, false), 0);
}
#[test]
fn test_large_notional() {
let schedule = FeeSchedule::new(1, 1);
let notional = u128::MAX / 10_000 - 1;
let fee = schedule.calculate_fee(notional, false);
assert!(fee > 0);
assert!(fee < i128::MAX);
}
#[test]
fn test_edge_cases() {
let schedule = FeeSchedule::new(-10_000, 10_000); let notional = 10_000;
let maker_fee = schedule.calculate_fee(notional, true);
let taker_fee = schedule.calculate_fee(notional, false);
assert_eq!(maker_fee, -10_000); assert_eq!(taker_fee, 10_000); }
#[test]
fn test_serialization() {
let schedule = FeeSchedule::new(-2, 5);
let json = serde_json::to_string(&schedule).unwrap();
let deserialized: FeeSchedule = serde_json::from_str(&json).unwrap();
assert_eq!(schedule, deserialized);
}
#[test]
fn test_default() {
let schedule = FeeSchedule::default();
assert!(schedule.is_zero_fee());
}
}