use crate::Decimal;
use crate::types::error::{MMError, MMResult};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RiskLimits {
pub max_position: Decimal,
pub max_notional: Decimal,
pub scaling_factor: Decimal,
}
impl RiskLimits {
pub fn new(
max_position: Decimal,
max_notional: Decimal,
scaling_factor: Decimal,
) -> MMResult<Self> {
if max_position <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"max_position must be positive".to_string(),
));
}
if max_notional <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"max_notional must be positive".to_string(),
));
}
if scaling_factor < Decimal::ZERO || scaling_factor > Decimal::ONE {
return Err(MMError::InvalidConfiguration(
"scaling_factor must be between 0.0 and 1.0".to_string(),
));
}
Ok(Self {
max_position,
max_notional,
scaling_factor,
})
}
pub fn check_order(
&self,
current_position: Decimal,
order_size: Decimal,
price: Decimal,
) -> MMResult<bool> {
if price <= Decimal::ZERO {
return Err(MMError::InvalidMarketState(
"price must be positive".to_string(),
));
}
let new_position = current_position + order_size;
if new_position.abs() > self.max_position {
return Ok(false);
}
let new_notional = new_position.abs() * price;
if new_notional > self.max_notional {
return Ok(false);
}
Ok(true)
}
#[must_use]
pub fn scale_order_size(&self, current_position: Decimal, desired_size: Decimal) -> Decimal {
if desired_size <= Decimal::ZERO {
return Decimal::ZERO;
}
let position_ratio = current_position.abs() / self.max_position;
if position_ratio >= Decimal::ONE {
return Decimal::ZERO;
}
let scale_multiplier = Decimal::ONE - (position_ratio * self.scaling_factor);
let scale_multiplier = scale_multiplier.max(Decimal::ZERO);
desired_size * scale_multiplier
}
#[must_use]
pub fn is_position_limit_breached(&self, position: Decimal) -> bool {
position.abs() > self.max_position
}
#[must_use]
pub fn is_notional_limit_breached(&self, position: Decimal, price: Decimal) -> bool {
let notional = position.abs() * price;
notional > self.max_notional
}
#[must_use]
pub fn remaining_position_capacity(&self, current_position: Decimal) -> Decimal {
let remaining = self.max_position - current_position.abs();
remaining.max(Decimal::ZERO)
}
#[must_use]
pub fn position_utilization(&self, current_position: Decimal) -> Decimal {
current_position.abs() / self.max_position
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
#[test]
fn test_new_valid_limits() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5));
assert!(limits.is_ok());
let limits = limits.unwrap();
assert_eq!(limits.max_position, dec!(100.0));
assert_eq!(limits.max_notional, dec!(10000.0));
assert_eq!(limits.scaling_factor, dec!(0.5));
}
#[test]
fn test_new_zero_scaling_factor() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.0));
assert!(limits.is_ok());
}
#[test]
fn test_new_one_scaling_factor() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(1.0));
assert!(limits.is_ok());
}
#[test]
fn test_new_invalid_max_position() {
let limits = RiskLimits::new(dec!(0.0), dec!(10000.0), dec!(0.5));
assert!(limits.is_err());
assert!(matches!(
limits.unwrap_err(),
MMError::InvalidConfiguration(_)
));
let limits = RiskLimits::new(dec!(-100.0), dec!(10000.0), dec!(0.5));
assert!(limits.is_err());
}
#[test]
fn test_new_invalid_max_notional() {
let limits = RiskLimits::new(dec!(100.0), dec!(0.0), dec!(0.5));
assert!(limits.is_err());
let limits = RiskLimits::new(dec!(100.0), dec!(-10000.0), dec!(0.5));
assert!(limits.is_err());
}
#[test]
fn test_new_invalid_scaling_factor() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(-0.1));
assert!(limits.is_err());
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(1.1));
assert!(limits.is_err());
}
#[test]
fn test_check_order_within_limits() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert!(
limits
.check_order(dec!(50.0), dec!(10.0), dec!(100.0))
.unwrap()
);
assert!(
limits
.check_order(dec!(50.0), dec!(-10.0), dec!(100.0))
.unwrap()
);
assert!(
limits
.check_order(dec!(0.0), dec!(50.0), dec!(100.0))
.unwrap()
);
}
#[test]
fn test_check_order_exceeds_position_limit() {
let limits = RiskLimits::new(dec!(100.0), dec!(100000.0), dec!(0.5)).unwrap();
assert!(
!limits
.check_order(dec!(95.0), dec!(10.0), dec!(100.0))
.unwrap()
);
assert!(
!limits
.check_order(dec!(-95.0), dec!(-10.0), dec!(100.0))
.unwrap()
);
}
#[test]
fn test_check_order_exceeds_notional_limit() {
let limits = RiskLimits::new(dec!(100.0), dec!(5000.0), dec!(0.5)).unwrap();
assert!(
!limits
.check_order(dec!(50.0), dec!(10.0), dec!(100.0))
.unwrap()
);
}
#[test]
fn test_check_order_exactly_at_limit() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert!(
limits
.check_order(dec!(90.0), dec!(10.0), dec!(100.0))
.unwrap()
);
assert!(
limits
.check_order(dec!(0.0), dec!(100.0), dec!(100.0))
.unwrap()
);
}
#[test]
fn test_check_order_invalid_price() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert!(
limits
.check_order(dec!(50.0), dec!(10.0), dec!(0.0))
.is_err()
);
assert!(
limits
.check_order(dec!(50.0), dec!(10.0), dec!(-100.0))
.is_err()
);
}
#[test]
fn test_check_order_reducing_position() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert!(
limits
.check_order(dec!(150.0), dec!(-50.0), dec!(100.0))
.unwrap()
);
assert!(
limits
.check_order(dec!(-150.0), dec!(50.0), dec!(100.0))
.unwrap()
);
}
#[test]
fn test_scale_order_size_at_zero_position() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert_eq!(limits.scale_order_size(dec!(0.0), dec!(10.0)), dec!(10.0));
}
#[test]
fn test_scale_order_size_at_half_position() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(1.0)).unwrap();
assert_eq!(limits.scale_order_size(dec!(50.0), dec!(10.0)), dec!(5.0));
}
#[test]
fn test_scale_order_size_at_limit() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert_eq!(limits.scale_order_size(dec!(100.0), dec!(10.0)), dec!(0.0));
}
#[test]
fn test_scale_order_size_beyond_limit() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert_eq!(limits.scale_order_size(dec!(150.0), dec!(10.0)), dec!(0.0));
}
#[test]
fn test_scale_order_size_negative_position() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(1.0)).unwrap();
assert_eq!(limits.scale_order_size(dec!(-50.0), dec!(10.0)), dec!(5.0));
}
#[test]
fn test_scale_order_size_zero_scaling_factor() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.0)).unwrap();
assert_eq!(limits.scale_order_size(dec!(50.0), dec!(10.0)), dec!(10.0));
assert_eq!(limits.scale_order_size(dec!(99.0), dec!(10.0)), dec!(10.0));
assert_eq!(limits.scale_order_size(dec!(100.0), dec!(10.0)), dec!(0.0));
}
#[test]
fn test_scale_order_size_zero_desired() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert_eq!(limits.scale_order_size(dec!(50.0), dec!(0.0)), dec!(0.0));
assert_eq!(limits.scale_order_size(dec!(50.0), dec!(-10.0)), dec!(0.0));
}
#[test]
fn test_is_position_limit_breached() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert!(!limits.is_position_limit_breached(dec!(0.0)));
assert!(!limits.is_position_limit_breached(dec!(50.0)));
assert!(!limits.is_position_limit_breached(dec!(100.0)));
assert!(limits.is_position_limit_breached(dec!(100.1)));
assert!(limits.is_position_limit_breached(dec!(-100.1)));
}
#[test]
fn test_is_notional_limit_breached() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert!(!limits.is_notional_limit_breached(dec!(50.0), dec!(100.0)));
assert!(!limits.is_notional_limit_breached(dec!(100.0), dec!(100.0)));
assert!(limits.is_notional_limit_breached(dec!(100.0), dec!(101.0)));
assert!(limits.is_notional_limit_breached(dec!(-100.0), dec!(101.0)));
}
#[test]
fn test_remaining_position_capacity() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert_eq!(limits.remaining_position_capacity(dec!(0.0)), dec!(100.0));
assert_eq!(limits.remaining_position_capacity(dec!(60.0)), dec!(40.0));
assert_eq!(limits.remaining_position_capacity(dec!(-60.0)), dec!(40.0));
assert_eq!(limits.remaining_position_capacity(dec!(100.0)), dec!(0.0));
assert_eq!(limits.remaining_position_capacity(dec!(150.0)), dec!(0.0));
}
#[test]
fn test_position_utilization() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
assert_eq!(limits.position_utilization(dec!(0.0)), dec!(0.0));
assert_eq!(limits.position_utilization(dec!(50.0)), dec!(0.5));
assert_eq!(limits.position_utilization(dec!(-50.0)), dec!(0.5));
assert_eq!(limits.position_utilization(dec!(100.0)), dec!(1.0));
assert_eq!(limits.position_utilization(dec!(150.0)), dec!(1.5));
}
#[cfg(feature = "serde")]
#[test]
fn test_serialization() {
let limits = RiskLimits::new(dec!(100.0), dec!(10000.0), dec!(0.5)).unwrap();
let json = serde_json::to_string(&limits).unwrap();
let deserialized: RiskLimits = serde_json::from_str(&json).unwrap();
assert_eq!(limits, deserialized);
}
}