1use dashmap::DashMap;
25use nautilus_common::cache::fifo::{FifoCache, FifoCacheMap};
26use nautilus_core::{UUID4, UnixNanos};
27use nautilus_live::ExecutionEventEmitter;
28use nautilus_model::{
29 enums::{OrderSide, OrderType},
30 events::{OrderAccepted, OrderCanceled, OrderEventAny},
31 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, VenueOrderId},
32 reports::OrderStatusReport,
33 types::{Price, Quantity},
34};
35use parking_lot::Mutex;
36
37#[derive(Debug, Clone, Copy)]
39pub enum PendingOperation {
40 Place,
41 Cancel,
42 Modify,
43}
44
45#[derive(Debug, Clone)]
51pub struct PendingRequest {
52 pub client_order_id: ClientOrderId,
53 pub venue_order_id: Option<VenueOrderId>,
54 pub operation: PendingOperation,
55}
56
57#[derive(Debug, Clone)]
62enum CancelReplaceOutcome {
63 Pending,
64 Canceled(Box<OrderStatusReport>),
69 Rejected,
71}
72
73#[derive(Debug, Clone)]
78pub struct OrderIdentity {
79 pub instrument_id: InstrumentId,
80 pub strategy_id: StrategyId,
81 pub order_side: OrderSide,
82 pub order_type: OrderType,
83 pub price: Option<Price>,
84 pub quantity: Quantity,
85 pub venue_position_id: Option<PositionId>,
86}
87
88#[derive(Debug, Clone, Copy)]
89struct AlgoOrderIds {
90 algo: VenueOrderId,
91 current: VenueOrderId,
92}
93
94#[derive(Debug)]
100pub struct WsDispatchState {
101 pub order_identities: DashMap<ClientOrderId, OrderIdentity>,
102 pub pending_requests: DashMap<String, PendingRequest>,
103 algo_order_ids: DashMap<ClientOrderId, AlgoOrderIds>,
104 order_updates: DashMap<ClientOrderId, OrderUpdate>,
105 replacements: DashMap<ClientOrderId, PendingReplacement>,
106 emitted_accepted: Mutex<FifoCache<ClientOrderId, 10_000>>,
107 filled_orders: Mutex<FifoCache<ClientOrderId, 10_000>>,
108 pub cancel_replace_request_ids: DashMap<String, String>,
110 cancel_replace_outcomes: Mutex<FifoCacheMap<String, CancelReplaceOutcome, 10_000>>,
111}
112
113impl Default for WsDispatchState {
114 fn default() -> Self {
115 Self {
116 order_identities: DashMap::new(),
117 pending_requests: DashMap::new(),
118 algo_order_ids: DashMap::new(),
119 order_updates: DashMap::new(),
120 replacements: DashMap::new(),
121 emitted_accepted: Mutex::new(FifoCache::new()),
122 filled_orders: Mutex::new(FifoCache::new()),
123 cancel_replace_request_ids: DashMap::new(),
124 cancel_replace_outcomes: Mutex::new(FifoCacheMap::new()),
125 }
126 }
127}
128
129impl WsDispatchState {
130 pub fn has_emitted_accepted(&self, cid: &ClientOrderId) -> bool {
131 self.emitted_accepted.lock().contains(cid)
132 }
133
134 pub fn insert_accepted(&self, cid: ClientOrderId) {
136 self.emitted_accepted.lock().add(cid);
137 }
138
139 pub fn has_filled(&self, cid: &ClientOrderId) -> bool {
140 self.filled_orders.lock().contains(cid)
141 }
142
143 pub fn insert_filled(&self, cid: ClientOrderId) {
145 self.filled_orders.lock().add(cid);
146 }
147
148 pub fn insert_cancel_replace(&self, cancel_id: String) {
150 self.cancel_replace_outcomes
151 .lock()
152 .insert(cancel_id, CancelReplaceOutcome::Pending);
153 }
154
155 pub fn has_cancel_replace(&self, cancel_id: &str) -> bool {
157 self.cancel_replace_outcomes
158 .lock()
159 .contains_key(&cancel_id.to_string())
160 }
161
162 pub fn on_cancel_replace_canceled(&self, cancel_id: &str, report: OrderStatusReport) -> bool {
169 let mut outcomes = self.cancel_replace_outcomes.lock();
170 let Some(outcome) = outcomes.get_mut(&cancel_id.to_string()) else {
171 return false;
172 };
173
174 match outcome {
175 CancelReplaceOutcome::Pending => {
176 *outcome = CancelReplaceOutcome::Canceled(Box::new(report));
177 true
178 }
179 CancelReplaceOutcome::Canceled(_) => true,
180 CancelReplaceOutcome::Rejected => false,
181 }
182 }
183
184 pub fn on_cancel_replace_rejected(&self, cancel_id: &str) -> Option<OrderStatusReport> {
189 let mut outcomes = self.cancel_replace_outcomes.lock();
190 let outcome = outcomes.get_mut(&cancel_id.to_string())?;
191
192 match outcome {
193 CancelReplaceOutcome::Pending => {
194 *outcome = CancelReplaceOutcome::Rejected;
195 None
196 }
197 CancelReplaceOutcome::Canceled(report) => Some((**report).clone()),
198 CancelReplaceOutcome::Rejected => None,
199 }
200 }
201
202 pub fn insert_algo_order_id(&self, cid: ClientOrderId, venue_order_id: VenueOrderId) {
203 self.algo_order_ids.entry(cid).or_insert(AlgoOrderIds {
204 algo: venue_order_id,
205 current: venue_order_id,
206 });
207 }
208
209 pub fn promote_algo_order_id(
214 &self,
215 cid: ClientOrderId,
216 venue_order_id: VenueOrderId,
217 ) -> Option<bool> {
218 let mut ids = self.algo_order_ids.get_mut(&cid)?;
219 let changed = ids.current != venue_order_id;
220 ids.current = venue_order_id;
221 Some(changed)
222 }
223
224 pub fn promoted_algo_order_id(&self, cid: &ClientOrderId) -> Option<VenueOrderId> {
226 self.algo_order_ids
227 .get(cid)
228 .and_then(|ids| (ids.current != ids.algo).then_some(ids.current))
229 }
230
231 pub(crate) fn record_order_update(
232 &self,
233 cid: ClientOrderId,
234 venue_order_id: VenueOrderId,
235 quantity: Quantity,
236 price: Price,
237 trigger_price: Option<Price>,
238 ) -> bool {
239 let update = OrderUpdate {
240 venue_order_id,
241 quantity,
242 price,
243 trigger_price,
244 };
245 let changed = self
246 .order_updates
247 .insert(cid, update)
248 .is_none_or(|previous| previous != update);
249 self.replacements
250 .remove_if(&cid, |_, pending| pending.venue_order_id != venue_order_id);
251 changed
252 }
253
254 pub(crate) fn begin_replace(&self, cid: ClientOrderId, venue_order_id: VenueOrderId) {
255 self.replacements.insert(
256 cid,
257 PendingReplacement {
258 venue_order_id,
259 canceled: None,
260 },
261 );
262 }
263
264 pub(crate) fn defer_replace_cancel(&self, canceled: OrderCanceled) -> bool {
265 let cid = canceled.client_order_id;
266
267 if self
268 .order_updates
269 .get(&cid)
270 .is_some_and(|update| Some(update.venue_order_id) != canceled.venue_order_id)
271 {
272 return true;
273 }
274
275 if let Some(mut pending) = self.replacements.get_mut(&cid)
276 && Some(pending.venue_order_id) == canceled.venue_order_id
277 {
278 pending.canceled = Some(canceled);
279 return true;
280 }
281 false
282 }
283
284 pub(crate) fn reject_replace(&self, cid: ClientOrderId) -> Option<OrderCanceled> {
285 self.replacements
286 .remove(&cid)
287 .and_then(|(_, pending)| pending.canceled)
288 }
289
290 pub fn cleanup_terminal(&self, cid: ClientOrderId) {
292 self.order_identities.remove(&cid);
293 self.algo_order_ids.remove(&cid);
294 self.order_updates.remove(&cid);
295 self.replacements.remove(&cid);
296 self.emitted_accepted.lock().remove(&cid);
297 self.filled_orders.lock().remove(&cid);
298 }
299}
300
301#[derive(Debug)]
302struct PendingReplacement {
303 venue_order_id: VenueOrderId,
304 canceled: Option<OrderCanceled>,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308struct OrderUpdate {
309 venue_order_id: VenueOrderId,
310 quantity: Quantity,
311 price: Price,
312 trigger_price: Option<Price>,
313}
314
315pub fn ensure_accepted_emitted(
319 client_order_id: ClientOrderId,
320 account_id: AccountId,
321 venue_order_id: VenueOrderId,
322 identity: &OrderIdentity,
323 emitter: &ExecutionEventEmitter,
324 state: &WsDispatchState,
325 ts_init: UnixNanos,
326) {
327 if state.has_emitted_accepted(&client_order_id) {
328 return;
329 }
330 state.insert_accepted(client_order_id);
331 let accepted = OrderAccepted::new(
332 emitter.trader_id(),
333 identity.strategy_id,
334 identity.instrument_id,
335 client_order_id,
336 venue_order_id,
337 account_id,
338 UUID4::new(),
339 ts_init,
340 ts_init,
341 false,
342 );
343 emitter.send_order_event(OrderEventAny::Accepted(accepted));
344}