1use crate::limits::RiskLimits;
4use dashmap::DashMap;
5use fx_utils::{OrderId, Quantity, Result, Side};
6use std::sync::Arc;
7
8#[derive(Debug, Clone)]
10pub struct Position {
11 pub instrument: String,
12 pub quantity: i64, }
14
15pub struct RiskEngine {
17 limits: Arc<RiskLimits>,
18 positions: DashMap<String, Position>,
19 open_orders: DashMap<OrderId, Quantity>,
20}
21
22impl RiskEngine {
23 pub fn new(limits: RiskLimits) -> Self {
24 Self {
25 limits: Arc::new(limits),
26 positions: DashMap::new(),
27 open_orders: DashMap::new(),
28 }
29 }
30
31 pub fn check_order(
32 &self,
33 instrument: &str,
34 side: Side,
35 quantity: Quantity,
36 order_id: OrderId,
37 ) -> Result<()> {
38 if quantity.0 > self.limits.max_order_size.0 {
40 return Err(fx_utils::Error::InvalidInput(format!(
41 "Order size {} exceeds limit {}",
42 quantity.0, self.limits.max_order_size.0
43 )));
44 }
45
46 if self.open_orders.len() >= self.limits.max_open_orders {
48 return Err(fx_utils::Error::InvalidInput(
49 "Maximum open orders limit reached".to_string(),
50 ));
51 }
52
53 let current_position = self
55 .positions
56 .get(instrument)
57 .map(|p| p.quantity)
58 .unwrap_or(0);
59
60 let new_position = match side {
61 Side::Buy => current_position + quantity.0 as i64,
62 Side::Sell => current_position - quantity.0 as i64,
63 };
64
65 #[allow(clippy::cast_abs_to_unsigned)]
66 #[allow(clippy::cast_abs_to_unsigned)]
67 if new_position.abs() as u64 > self.limits.max_position_size.0 {
68 return Err(fx_utils::Error::InvalidInput(format!(
69 "Position limit would be exceeded: {}",
70 new_position
71 )));
72 }
73
74 self.open_orders.insert(order_id, quantity);
76
77 Ok(())
78 }
79
80 pub fn update_position(&self, instrument: &str, side: Side, quantity: Quantity) {
81 let mut position = self
82 .positions
83 .entry(instrument.to_string())
84 .or_insert_with(|| Position {
85 instrument: instrument.to_string(),
86 quantity: 0,
87 });
88
89 match side {
90 Side::Buy => position.quantity += quantity.0 as i64,
91 Side::Sell => position.quantity -= quantity.0 as i64,
92 }
93 }
94
95 pub fn remove_order(&self, order_id: OrderId) {
96 self.open_orders.remove(&order_id);
97 }
98
99 pub fn get_position(&self, instrument: &str) -> i64 {
101 self.positions
102 .get(instrument)
103 .map(|p| p.quantity)
104 .unwrap_or(0)
105 }
106
107 pub fn positions(&self) -> &DashMap<String, Position> {
109 &self.positions
110 }
111
112 pub fn open_orders(&self) -> &DashMap<OrderId, Quantity> {
114 &self.open_orders
115 }
116
117 pub fn limits(&self) -> &Arc<RiskLimits> {
119 &self.limits
120 }
121}