1#![allow(dead_code)]
20#![allow(unused_variables)]
21
22use std::{any::Any, cell::RefCell, fmt::Debug, rc::Rc};
23
24use nautilus_common::{cache::Cache, clock::Clock, msgbus};
25use nautilus_core::{UUID4, UnixNanos};
26use nautilus_model::{
27 accounts::AccountAny,
28 enums::{AccountType, LiquiditySide, OmsType, OrderSide, OrderType},
29 events::{
30 AccountState, OrderAccepted, OrderCancelRejected, OrderCanceled, OrderEventAny,
31 OrderExpired, OrderFilled, OrderModifyRejected, OrderRejected, OrderSubmitted,
32 OrderTriggered, OrderUpdated,
33 },
34 identifiers::{
35 AccountId, ClientId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId,
36 TraderId, Venue, VenueOrderId,
37 },
38 reports::{ExecutionMassStatus, FillReport, OrderStatusReport, PositionStatusReport},
39 types::{AccountBalance, Currency, MarginBalance, Money, Price, Quantity},
40};
41
42pub struct BaseExecutionClient {
49 pub trader_id: TraderId,
50 pub client_id: ClientId,
51 pub venue: Venue,
52 pub oms_type: OmsType,
53 pub account_id: AccountId,
54 pub account_type: AccountType,
55 pub base_currency: Option<Currency>,
56 pub is_connected: bool,
57 clock: Rc<RefCell<dyn Clock>>,
58 cache: Rc<RefCell<Cache>>,
59}
60
61impl Debug for BaseExecutionClient {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct(stringify!(BaseExecutionClient))
64 .field("client_id", &self.client_id)
65 .finish()
66 }
67}
68
69impl BaseExecutionClient {
70 #[allow(clippy::too_many_arguments)]
72 pub const fn new(
73 trader_id: TraderId,
74 client_id: ClientId,
75 venue: Venue,
76 oms_type: OmsType,
77 account_id: AccountId,
78 account_type: AccountType,
79 base_currency: Option<Currency>,
80 clock: Rc<RefCell<dyn Clock>>,
81 cache: Rc<RefCell<Cache>>,
82 ) -> Self {
83 Self {
84 trader_id,
85 client_id,
86 venue,
87 oms_type,
88 account_id,
89 account_type,
90 base_currency,
91 is_connected: false,
92 clock,
93 cache,
94 }
95 }
96
97 pub const fn set_connected(&mut self, is_connected: bool) {
99 self.is_connected = is_connected;
100 }
101
102 pub const fn set_account_id(&mut self, account_id: AccountId) {
104 self.account_id = account_id;
105 }
106
107 #[must_use]
108 pub fn get_account(&self) -> Option<AccountAny> {
110 self.cache.borrow().account(&self.account_id).cloned()
111 }
112
113 pub fn generate_account_state(
119 &self,
120 balances: Vec<AccountBalance>,
121 margins: Vec<MarginBalance>,
122 reported: bool,
123 ts_event: UnixNanos,
124 ) -> anyhow::Result<()> {
126 let account_state = AccountState::new(
127 self.account_id,
128 self.account_type,
129 balances,
130 margins,
131 reported,
132 UUID4::new(),
133 ts_event,
134 self.clock.borrow().timestamp_ns(),
135 self.base_currency,
136 );
137 self.send_account_state(account_state);
138 Ok(())
139 }
140
141 pub fn generate_order_submitted(
142 &self,
143 strategy_id: StrategyId,
144 instrument_id: InstrumentId,
145 client_order_id: ClientOrderId,
146 ts_event: UnixNanos,
147 ) {
148 let event = OrderSubmitted::new(
149 self.trader_id,
150 strategy_id,
151 instrument_id,
152 client_order_id,
153 self.account_id,
154 UUID4::new(),
155 ts_event,
156 self.clock.borrow().timestamp_ns(),
157 );
158 self.send_order_event(OrderEventAny::Submitted(event));
159 }
160
161 pub fn generate_order_rejected(
162 &self,
163 strategy_id: StrategyId,
164 instrument_id: InstrumentId,
165 client_order_id: ClientOrderId,
166 reason: &str,
167 ts_event: UnixNanos,
168 due_post_only: bool,
169 ) {
170 let event = OrderRejected::new(
171 self.trader_id,
172 strategy_id,
173 instrument_id,
174 client_order_id,
175 self.account_id,
176 reason.into(),
177 UUID4::new(),
178 ts_event,
179 self.clock.borrow().timestamp_ns(),
180 false,
181 due_post_only,
182 );
183 self.send_order_event(OrderEventAny::Rejected(event));
184 }
185
186 pub fn generate_order_accepted(
187 &self,
188 strategy_id: StrategyId,
189 instrument_id: InstrumentId,
190 client_order_id: ClientOrderId,
191 venue_order_id: VenueOrderId,
192 ts_event: UnixNanos,
193 ) {
194 let event = OrderAccepted::new(
195 self.trader_id,
196 strategy_id,
197 instrument_id,
198 client_order_id,
199 venue_order_id,
200 self.account_id,
201 UUID4::new(),
202 ts_event,
203 self.clock.borrow().timestamp_ns(),
204 false,
205 );
206 self.send_order_event(OrderEventAny::Accepted(event));
207 }
208
209 pub fn generate_order_modify_rejected(
210 &self,
211 strategy_id: StrategyId,
212 instrument_id: InstrumentId,
213 client_order_id: ClientOrderId,
214 venue_order_id: VenueOrderId,
215 reason: &str,
216 ts_event: UnixNanos,
217 ) {
218 let event = OrderModifyRejected::new(
219 self.trader_id,
220 strategy_id,
221 instrument_id,
222 client_order_id,
223 reason.into(),
224 UUID4::new(),
225 ts_event,
226 self.clock.borrow().timestamp_ns(),
227 false,
228 Some(venue_order_id),
229 Some(self.account_id),
230 );
231 self.send_order_event(OrderEventAny::ModifyRejected(event));
232 }
233
234 pub fn generate_order_cancel_rejected(
235 &self,
236 strategy_id: StrategyId,
237 instrument_id: InstrumentId,
238 client_order_id: ClientOrderId,
239 venue_order_id: VenueOrderId,
240 reason: &str,
241 ts_event: UnixNanos,
242 ) {
243 let event = OrderCancelRejected::new(
244 self.trader_id,
245 strategy_id,
246 instrument_id,
247 client_order_id,
248 reason.into(),
249 UUID4::new(),
250 ts_event,
251 self.clock.borrow().timestamp_ns(),
252 false,
253 Some(venue_order_id),
254 Some(self.account_id),
255 );
256 self.send_order_event(OrderEventAny::CancelRejected(event));
257 }
258
259 #[allow(clippy::too_many_arguments)]
260 pub fn generate_order_updated(
261 &self,
262 strategy_id: StrategyId,
263 instrument_id: InstrumentId,
264 client_order_id: ClientOrderId,
265 venue_order_id: VenueOrderId,
266 quantity: Quantity,
267 price: Price,
268 trigger_price: Option<Price>,
269 ts_event: UnixNanos,
270 venue_order_id_modified: bool,
271 ) {
272 if !venue_order_id_modified {
273 let cache = self.cache.as_ref().borrow();
274 let existing_order_result = cache.venue_order_id(&client_order_id);
275 if let Some(existing_order) = existing_order_result
276 && *existing_order != venue_order_id
277 {
278 log::error!(
279 "Existing venue order id {existing_order} does not match provided venue order id {venue_order_id}"
280 );
281 }
282 }
283
284 let event = OrderUpdated::new(
285 self.trader_id,
286 strategy_id,
287 instrument_id,
288 client_order_id,
289 quantity,
290 UUID4::new(),
291 ts_event,
292 self.clock.borrow().timestamp_ns(),
293 false,
294 Some(venue_order_id),
295 Some(self.account_id),
296 Some(price),
297 trigger_price,
298 );
299
300 self.send_order_event(OrderEventAny::Updated(event));
301 }
302
303 pub fn generate_order_canceled(
304 &self,
305 strategy_id: StrategyId,
306 instrument_id: InstrumentId,
307 client_order_id: ClientOrderId,
308 venue_order_id: VenueOrderId,
309 ts_event: UnixNanos,
310 ) {
311 let event = OrderCanceled::new(
312 self.trader_id,
313 strategy_id,
314 instrument_id,
315 client_order_id,
316 UUID4::new(),
317 ts_event,
318 self.clock.borrow().timestamp_ns(),
319 false,
320 Some(venue_order_id),
321 Some(self.account_id),
322 );
323
324 self.send_order_event(OrderEventAny::Canceled(event));
325 }
326
327 pub fn generate_order_triggered(
328 &self,
329 strategy_id: StrategyId,
330 instrument_id: InstrumentId,
331 client_order_id: ClientOrderId,
332 venue_order_id: VenueOrderId,
333 ts_event: UnixNanos,
334 ) {
335 let event = OrderTriggered::new(
336 self.trader_id,
337 strategy_id,
338 instrument_id,
339 client_order_id,
340 UUID4::new(),
341 ts_event,
342 self.clock.borrow().timestamp_ns(),
343 false,
344 Some(venue_order_id),
345 Some(self.account_id),
346 );
347
348 self.send_order_event(OrderEventAny::Triggered(event));
349 }
350
351 pub fn generate_order_expired(
352 &self,
353 strategy_id: StrategyId,
354 instrument_id: InstrumentId,
355 client_order_id: ClientOrderId,
356 venue_order_id: VenueOrderId,
357 ts_event: UnixNanos,
358 ) {
359 let event = OrderExpired::new(
360 self.trader_id,
361 strategy_id,
362 instrument_id,
363 client_order_id,
364 UUID4::new(),
365 ts_event,
366 self.clock.borrow().timestamp_ns(),
367 false,
368 Some(venue_order_id),
369 Some(self.account_id),
370 );
371
372 self.send_order_event(OrderEventAny::Expired(event));
373 }
374
375 #[allow(clippy::too_many_arguments)]
376 pub fn generate_order_filled(
377 &self,
378 strategy_id: StrategyId,
379 instrument_id: InstrumentId,
380 client_order_id: ClientOrderId,
381 venue_order_id: VenueOrderId,
382 venue_position_id: PositionId,
383 trade_id: TradeId,
384 order_side: OrderSide,
385 order_type: OrderType,
386 last_qty: Quantity,
387 last_px: Price,
388 quote_currency: Currency,
389 commission: Money,
390 liquidity_side: LiquiditySide,
391 ts_event: UnixNanos,
392 ) {
393 let event = OrderFilled::new(
394 self.trader_id,
395 strategy_id,
396 instrument_id,
397 client_order_id,
398 venue_order_id,
399 self.account_id,
400 trade_id,
401 order_side,
402 order_type,
403 last_qty,
404 last_px,
405 quote_currency,
406 liquidity_side,
407 UUID4::new(),
408 ts_event,
409 self.clock.borrow().timestamp_ns(),
410 false,
411 Some(venue_position_id),
412 Some(commission),
413 );
414
415 self.send_order_event(OrderEventAny::Filled(event));
416 }
417
418 fn send_account_state(&self, account_state: AccountState) {
419 let endpoint = "Portfolio.update_account".into();
420 msgbus::send_any(endpoint, &account_state as &dyn Any);
421 }
422
423 fn send_order_event(&self, event: OrderEventAny) {
424 let endpoint = "ExecEngine.process".into();
425 msgbus::send_any(endpoint, &event as &dyn Any);
426 }
427
428 fn send_mass_status_report(&self, report: ExecutionMassStatus) {
429 let endpoint = "ExecEngine.reconcile_execution_mass_status".into();
430 msgbus::send_any(endpoint, &report as &dyn Any);
431 }
432
433 fn send_order_status_report(&self, report: OrderStatusReport) {
434 let endpoint = "ExecEngine.reconcile_execution_report".into();
435 msgbus::send_any(endpoint, &report as &dyn Any);
436 }
437
438 fn send_fill_report(&self, report: FillReport) {
439 let endpoint = "ExecEngine.reconcile_execution_report".into();
440 msgbus::send_any(endpoint, &report as &dyn Any);
441 }
442
443 fn send_position_report(&self, report: PositionStatusReport) {
444 let endpoint = "ExecEngine.reconcile_execution_report".into();
445 msgbus::send_any(endpoint, &report as &dyn Any);
446 }
447}