rusty_oms/
risk_manager.rs

1//! Risk management module for order validation and risk checks
2//!
3//! This module provides pre-trade risk controls to ensure orders meet
4//! configured risk limits before being sent to exchanges.
5
6use rust_decimal::Decimal;
7use rusty_model::trading_order::Order;
8use thiserror::Error;
9
10/// Error types for risk validation failures
11#[derive(Error, Debug)]
12pub enum RiskError {
13    /// Order quantity exceeds the configured maximum
14    #[error("Order quantity exceeds maximum allowed")]
15    QuantityTooLarge,
16    /// Order price is outside the configured range
17    #[error("Order price outside allowed range")]
18    PriceOutOfRange,
19}
20
21/// Risk manager for validating orders against configured limits
22pub struct RiskManager {
23    /// The maximum allowed quantity for an order.
24    max_order_quantity: Decimal,
25    /// The minimum allowed price for an order.
26    min_price: Decimal,
27    /// The maximum allowed price for an order.
28    max_price: Decimal,
29}
30
31impl RiskManager {
32    /// Create a new risk manager with specified limits
33    #[must_use]
34    pub const fn new(max_order_quantity: Decimal, min_price: Decimal, max_price: Decimal) -> Self {
35        Self {
36            max_order_quantity,
37            min_price,
38            max_price,
39        }
40    }
41
42    /// Validate an order against configured risk limits
43    ///
44    /// # Errors
45    ///
46    /// Returns `RiskError::QuantityTooLarge` if the order quantity exceeds the maximum.
47    /// Returns `RiskError::PriceOutOfRange` if the price is outside the allowed range.
48    pub fn validate_order(&self, order: &Order) -> Result<(), RiskError> {
49        // Check order quantity
50        if order.quantity > self.max_order_quantity {
51            return Err(RiskError::QuantityTooLarge);
52        }
53
54        // Check price for limit orders (only if price is specified)
55        if let Some(price) = order.price
56            && (price < self.min_price || price > self.max_price)
57        {
58            return Err(RiskError::PriceOutOfRange);
59        }
60
61        Ok(())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use rust_decimal_macros::dec;
69    use rusty_model::{
70        enums::{OrderSide, OrderType},
71        venues::Venue,
72    };
73
74    fn create_test_order(quantity: Decimal, price: Decimal) -> Order {
75        Order::new(
76            Venue::Test,
77            "BTCUSDT",
78            OrderSide::Buy,
79            OrderType::Limit,
80            quantity,
81            Some(price),
82            rusty_model::types::ClientId::new("test_client"),
83        )
84    }
85
86    #[test]
87    fn test_risk_manager_new() {
88        let max_quantity = dec!(10.0);
89        let min_price = dec!(1000.0);
90        let max_price = dec!(100000.0);
91
92        let risk_manager = RiskManager::new(max_quantity, min_price, max_price);
93
94        assert_eq!(risk_manager.max_order_quantity, max_quantity);
95        assert_eq!(risk_manager.min_price, min_price);
96        assert_eq!(risk_manager.max_price, max_price);
97    }
98
99    #[test]
100    fn test_validate_order_valid() {
101        let risk_manager = RiskManager::new(dec!(10.0), dec!(1000.0), dec!(100000.0));
102
103        // Valid order within limits
104        let order = create_test_order(dec!(5.0), dec!(50000.0));
105        let result = risk_manager.validate_order(&order);
106        assert!(result.is_ok());
107    }
108
109    #[test]
110    fn test_validate_order_quantity_too_large() {
111        let risk_manager = RiskManager::new(dec!(10.0), dec!(1000.0), dec!(100000.0));
112
113        // Order with quantity exceeding maximum
114        let order = create_test_order(dec!(15.0), dec!(50000.0));
115        let result = risk_manager.validate_order(&order);
116        assert!(result.is_err());
117        match result {
118            Err(RiskError::QuantityTooLarge) => (),
119            _ => panic!("Expected QuantityTooLarge error"),
120        }
121    }
122
123    #[test]
124    fn test_validate_order_price_too_low() {
125        let risk_manager = RiskManager::new(dec!(10.0), dec!(1000.0), dec!(100000.0));
126
127        // Order with price below minimum
128        let order = create_test_order(dec!(5.0), dec!(500.0));
129        let result = risk_manager.validate_order(&order);
130        assert!(result.is_err());
131        match result {
132            Err(RiskError::PriceOutOfRange) => (),
133            _ => panic!("Expected PriceOutOfRange error"),
134        }
135    }
136
137    #[test]
138    fn test_validate_order_price_too_high() {
139        let risk_manager = RiskManager::new(dec!(10.0), dec!(1000.0), dec!(100000.0));
140
141        // Order with price above maximum
142        let order = create_test_order(dec!(5.0), dec!(150000.0));
143        let result = risk_manager.validate_order(&order);
144        assert!(result.is_err());
145        match result {
146            Err(RiskError::PriceOutOfRange) => (),
147            _ => panic!("Expected PriceOutOfRange error"),
148        }
149    }
150
151    #[test]
152    fn test_validate_order_edge_cases() {
153        let risk_manager = RiskManager::new(dec!(10.0), dec!(1000.0), dec!(100000.0));
154
155        // Order with quantity exactly at maximum (should be valid)
156        let order = create_test_order(dec!(10.0), dec!(50000.0));
157        let result = risk_manager.validate_order(&order);
158        assert!(result.is_ok());
159
160        // Order with price exactly at minimum (should be valid)
161        let order = create_test_order(dec!(5.0), dec!(1000.0));
162        let result = risk_manager.validate_order(&order);
163        assert!(result.is_ok());
164
165        // Order with price exactly at maximum (should be valid)
166        let order = create_test_order(dec!(5.0), dec!(100000.0));
167        let result = risk_manager.validate_order(&order);
168        assert!(result.is_ok());
169    }
170}