bot_engine/
order_manager.rs1use bot_core::{
4 now_ms, ClientOrderId, ExchangeOrderId, InstrumentId, LiveOrder, OrderSide, OrderStatus, Price,
5 Qty, TradeId,
6};
7use std::collections::{HashMap, HashSet};
8
9pub struct OrderManager {
11 orders: HashMap<ClientOrderId, LiveOrder>,
13
14 exchange_id_map: HashMap<ExchangeOrderId, ClientOrderId>,
16
17 seen_trades: HashSet<String>,
19 seen_trades_order: Vec<String>,
20 max_seen_trades: usize,
21}
22
23impl OrderManager {
24 pub fn new() -> Self {
26 Self {
27 orders: HashMap::new(),
28 exchange_id_map: HashMap::new(),
29 seen_trades: HashSet::new(),
30 seen_trades_order: Vec::new(),
31 max_seen_trades: 10000,
32 }
33 }
34
35 pub fn get(&self, client_id: &ClientOrderId) -> Option<&LiveOrder> {
37 self.orders.get(client_id)
38 }
39
40 pub fn get_mut(&mut self, client_id: &ClientOrderId) -> Option<&mut LiveOrder> {
42 self.orders.get_mut(client_id)
43 }
44
45 pub fn contains(&self, client_id: &ClientOrderId) -> bool {
47 self.orders.contains_key(client_id)
48 }
49
50 pub fn create_order(
52 &mut self,
53 client_id: ClientOrderId,
54 instrument: InstrumentId,
55 side: OrderSide,
56 price: Price,
57 qty: Qty,
58 ) -> &LiveOrder {
59 let order = LiveOrder {
60 client_id: client_id.clone(),
61 exchange_order_id: None,
62 instrument,
63 side,
64 price,
65 requested_qty: qty,
66 filled_qty: Qty::new(rust_decimal::Decimal::ZERO),
67 avg_fill_px: None,
68 status: OrderStatus::New,
69 ts_created: now_ms(),
70 ts_last_update: now_ms(),
71 };
72 self.orders.insert(client_id.clone(), order);
73 self.orders.get(&client_id).unwrap()
74 }
75
76 pub fn accept_order(
78 &mut self,
79 client_id: &ClientOrderId,
80 exchange_order_id: Option<ExchangeOrderId>,
81 ) -> bool {
82 if let Some(order) = self.orders.get_mut(client_id) {
83 order.status = OrderStatus::Accepted;
84 order.ts_last_update = now_ms();
85 if let Some(eid) = exchange_order_id {
86 order.exchange_order_id = Some(eid.clone());
87 self.exchange_id_map.insert(eid, client_id.clone());
88 }
89 true
90 } else {
91 false
92 }
93 }
94
95 pub fn reject_order(&mut self, client_id: &ClientOrderId) -> Option<LiveOrder> {
97 if let Some(mut order) = self.orders.remove(client_id) {
98 order.status = OrderStatus::Rejected;
99 order.ts_last_update = now_ms();
100 if let Some(ref eid) = order.exchange_order_id {
101 self.exchange_id_map.remove(eid);
102 }
103 Some(order)
104 } else {
105 None
106 }
107 }
108
109 pub fn apply_fill(
112 &mut self,
113 client_id: &ClientOrderId,
114 trade_id: &TradeId,
115 fill_qty: Qty,
116 fill_px: Price,
117 ) -> bool {
118 if self.seen_trades.contains(&trade_id.0) {
120 return false;
121 }
122
123 self.seen_trades.insert(trade_id.0.clone());
125 self.seen_trades_order.push(trade_id.0.clone());
126 if self.seen_trades_order.len() > self.max_seen_trades {
127 if let Some(old) = self.seen_trades_order.first().cloned() {
128 self.seen_trades.remove(&old);
129 self.seen_trades_order.remove(0);
130 }
131 }
132
133 if let Some(order) = self.orders.get_mut(client_id) {
135 order.filled_qty += fill_qty;
137
138 let old_notional = order.avg_fill_px.map(|p| p.0).unwrap_or_default()
140 * (order.filled_qty.0 - fill_qty.0);
141 let new_notional = fill_px.0 * fill_qty.0;
142 let total_qty = order.filled_qty.0;
143 if total_qty > rust_decimal::Decimal::ZERO {
144 order.avg_fill_px = Some(Price((old_notional + new_notional) / total_qty));
145 }
146
147 if order.filled_qty >= order.requested_qty {
149 order.status = OrderStatus::Filled;
150 } else {
151 order.status = OrderStatus::PartiallyFilled;
152 }
153
154 order.ts_last_update = now_ms();
155 true
156 } else {
157 false
159 }
160 }
161
162 pub fn cancel_order(&mut self, client_id: &ClientOrderId) -> Option<LiveOrder> {
164 if let Some(mut order) = self.orders.remove(client_id) {
165 order.status = OrderStatus::Canceled;
166 order.ts_last_update = now_ms();
167 if let Some(ref eid) = order.exchange_order_id {
168 self.exchange_id_map.remove(eid);
169 }
170 Some(order)
171 } else {
172 None
173 }
174 }
175
176 pub fn is_complete(&self, client_id: &ClientOrderId) -> bool {
178 self.orders
179 .get(client_id)
180 .map(|o| o.is_complete())
181 .unwrap_or(false)
182 }
183
184 pub fn remove_terminal(&mut self, client_id: &ClientOrderId) -> Option<LiveOrder> {
186 if let Some(order) = self.orders.get(client_id) {
187 if order.status.is_terminal() {
188 let order = self.orders.remove(client_id)?;
189 if let Some(ref eid) = order.exchange_order_id {
190 self.exchange_id_map.remove(eid);
191 }
192 return Some(order);
193 }
194 }
195 None
196 }
197
198 pub fn client_id_from_exchange_id(
200 &self,
201 exchange_id: &ExchangeOrderId,
202 ) -> Option<&ClientOrderId> {
203 self.exchange_id_map.get(exchange_id)
204 }
205
206 pub fn is_trade_seen(&self, trade_id: &TradeId) -> bool {
208 self.seen_trades.contains(&trade_id.0)
209 }
210
211 pub fn order_count(&self) -> usize {
213 self.orders.len()
214 }
215}
216
217impl Default for OrderManager {
218 fn default() -> Self {
219 Self::new()
220 }
221}