nautilus_execution/client/
base.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Base execution client functionality.
17
18// Under development
19#![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 {
43    pub trader_id: TraderId,
44    pub client_id: ClientId,
45    pub venue: Venue,
46    pub oms_type: OmsType,
47    pub account_id: AccountId,
48    pub account_type: AccountType,
49    pub base_currency: Option<Currency>,
50    pub is_connected: bool,
51    clock: Rc<RefCell<dyn Clock>>,
52    cache: Rc<RefCell<Cache>>,
53}
54
55impl Debug for BaseExecutionClient {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct(stringify!(BaseExecutionClient))
58            .field("client_id", &self.client_id)
59            .finish()
60    }
61}
62
63impl BaseExecutionClient {
64    #[allow(clippy::too_many_arguments)]
65    pub const fn new(
66        trader_id: TraderId,
67        client_id: ClientId,
68        venue: Venue,
69        oms_type: OmsType,
70        account_id: AccountId,
71        account_type: AccountType,
72        base_currency: Option<Currency>,
73        clock: Rc<RefCell<dyn Clock>>,
74        cache: Rc<RefCell<Cache>>,
75    ) -> Self {
76        Self {
77            trader_id,
78            client_id,
79            venue,
80            oms_type,
81            account_id,
82            account_type,
83            base_currency,
84            is_connected: false,
85            clock,
86            cache,
87        }
88    }
89
90    pub const fn set_connected(&mut self, is_connected: bool) {
91        self.is_connected = is_connected;
92    }
93
94    pub const fn set_account_id(&mut self, account_id: AccountId) {
95        self.account_id = account_id;
96    }
97
98    #[must_use]
99    pub fn get_account(&self) -> Option<AccountAny> {
100        self.cache.borrow().account(&self.account_id).cloned()
101    }
102
103    /// Generates and publishes the account state event.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if constructing or sending the account state fails.
108    pub fn generate_account_state(
109        &self,
110        balances: Vec<AccountBalance>,
111        margins: Vec<MarginBalance>,
112        reported: bool,
113        ts_event: UnixNanos,
114        // info:  TODO: Need to double check the use case here
115    ) -> anyhow::Result<()> {
116        let account_state = AccountState::new(
117            self.account_id,
118            self.account_type,
119            balances,
120            margins,
121            reported,
122            UUID4::new(),
123            ts_event,
124            self.clock.borrow().timestamp_ns(),
125            self.base_currency,
126        );
127        self.send_account_state(account_state);
128        Ok(())
129    }
130
131    pub fn generate_order_submitted(
132        &self,
133        strategy_id: StrategyId,
134        instrument_id: InstrumentId,
135        client_order_id: ClientOrderId,
136        ts_event: UnixNanos,
137    ) {
138        let event = OrderSubmitted::new(
139            self.trader_id,
140            strategy_id,
141            instrument_id,
142            client_order_id,
143            self.account_id,
144            UUID4::new(),
145            ts_event,
146            self.clock.borrow().timestamp_ns(),
147        );
148        self.send_order_event(OrderEventAny::Submitted(event));
149    }
150
151    pub fn generate_order_rejected(
152        &self,
153        strategy_id: StrategyId,
154        instrument_id: InstrumentId,
155        client_order_id: ClientOrderId,
156        reason: &str,
157        ts_event: UnixNanos,
158    ) {
159        let event = OrderRejected::new(
160            self.trader_id,
161            strategy_id,
162            instrument_id,
163            client_order_id,
164            self.account_id,
165            reason.into(),
166            UUID4::new(),
167            ts_event,
168            self.clock.borrow().timestamp_ns(),
169            false,
170        );
171        self.send_order_event(OrderEventAny::Rejected(event));
172    }
173
174    pub fn generate_order_accepted(
175        &self,
176        strategy_id: StrategyId,
177        instrument_id: InstrumentId,
178        client_order_id: ClientOrderId,
179        venue_order_id: VenueOrderId,
180        ts_event: UnixNanos,
181    ) {
182        let event = OrderAccepted::new(
183            self.trader_id,
184            strategy_id,
185            instrument_id,
186            client_order_id,
187            venue_order_id,
188            self.account_id,
189            UUID4::new(),
190            ts_event,
191            self.clock.borrow().timestamp_ns(),
192            false,
193        );
194        self.send_order_event(OrderEventAny::Accepted(event));
195    }
196
197    pub fn generate_order_modify_rejected(
198        &self,
199        strategy_id: StrategyId,
200        instrument_id: InstrumentId,
201        client_order_id: ClientOrderId,
202        venue_order_id: VenueOrderId,
203        reason: &str,
204        ts_event: UnixNanos,
205    ) {
206        let event = OrderModifyRejected::new(
207            self.trader_id,
208            strategy_id,
209            instrument_id,
210            client_order_id,
211            reason.into(),
212            UUID4::new(),
213            ts_event,
214            self.clock.borrow().timestamp_ns(),
215            false,
216            Some(venue_order_id),
217            Some(self.account_id),
218        );
219        self.send_order_event(OrderEventAny::ModifyRejected(event));
220    }
221
222    pub fn generate_order_cancel_rejected(
223        &self,
224        strategy_id: StrategyId,
225        instrument_id: InstrumentId,
226        client_order_id: ClientOrderId,
227        venue_order_id: VenueOrderId,
228        reason: &str,
229        ts_event: UnixNanos,
230    ) {
231        let event = OrderCancelRejected::new(
232            self.trader_id,
233            strategy_id,
234            instrument_id,
235            client_order_id,
236            reason.into(),
237            UUID4::new(),
238            ts_event,
239            self.clock.borrow().timestamp_ns(),
240            false,
241            Some(venue_order_id),
242            Some(self.account_id),
243        );
244        self.send_order_event(OrderEventAny::CancelRejected(event));
245    }
246
247    #[allow(clippy::too_many_arguments)]
248    pub fn generate_order_updated(
249        &self,
250        strategy_id: StrategyId,
251        instrument_id: InstrumentId,
252        client_order_id: ClientOrderId,
253        venue_order_id: VenueOrderId,
254        quantity: Quantity,
255        price: Price,
256        trigger_price: Option<Price>,
257        ts_event: UnixNanos,
258        venue_order_id_modified: bool,
259    ) {
260        if !venue_order_id_modified {
261            let cache = self.cache.as_ref().borrow();
262            let existing_order_result = cache.venue_order_id(&client_order_id);
263            if let Some(existing_order) = existing_order_result {
264                if *existing_order != venue_order_id {
265                    log::error!(
266                        "Existing venue order id {existing_order} does not match provided venue order id {venue_order_id}"
267                    );
268                }
269            }
270        }
271
272        let event = OrderUpdated::new(
273            self.trader_id,
274            strategy_id,
275            instrument_id,
276            client_order_id,
277            quantity,
278            UUID4::new(),
279            ts_event,
280            self.clock.borrow().timestamp_ns(),
281            false,
282            Some(venue_order_id),
283            Some(self.account_id),
284            Some(price),
285            trigger_price,
286        );
287
288        self.send_order_event(OrderEventAny::Updated(event));
289    }
290
291    pub fn generate_order_canceled(
292        &self,
293        strategy_id: StrategyId,
294        instrument_id: InstrumentId,
295        client_order_id: ClientOrderId,
296        venue_order_id: VenueOrderId,
297        ts_event: UnixNanos,
298    ) {
299        let event = OrderCanceled::new(
300            self.trader_id,
301            strategy_id,
302            instrument_id,
303            client_order_id,
304            UUID4::new(),
305            ts_event,
306            self.clock.borrow().timestamp_ns(),
307            false,
308            Some(venue_order_id),
309            Some(self.account_id),
310        );
311
312        self.send_order_event(OrderEventAny::Canceled(event));
313    }
314
315    pub fn generate_order_triggered(
316        &self,
317        strategy_id: StrategyId,
318        instrument_id: InstrumentId,
319        client_order_id: ClientOrderId,
320        venue_order_id: VenueOrderId,
321        ts_event: UnixNanos,
322    ) {
323        let event = OrderTriggered::new(
324            self.trader_id,
325            strategy_id,
326            instrument_id,
327            client_order_id,
328            UUID4::new(),
329            ts_event,
330            self.clock.borrow().timestamp_ns(),
331            false,
332            Some(venue_order_id),
333            Some(self.account_id),
334        );
335
336        self.send_order_event(OrderEventAny::Triggered(event));
337    }
338
339    pub fn generate_order_expired(
340        &self,
341        strategy_id: StrategyId,
342        instrument_id: InstrumentId,
343        client_order_id: ClientOrderId,
344        venue_order_id: VenueOrderId,
345        ts_event: UnixNanos,
346    ) {
347        let event = OrderExpired::new(
348            self.trader_id,
349            strategy_id,
350            instrument_id,
351            client_order_id,
352            UUID4::new(),
353            ts_event,
354            self.clock.borrow().timestamp_ns(),
355            false,
356            Some(venue_order_id),
357            Some(self.account_id),
358        );
359
360        self.send_order_event(OrderEventAny::Expired(event));
361    }
362
363    #[allow(clippy::too_many_arguments)]
364    pub fn generate_order_filled(
365        &self,
366        strategy_id: StrategyId,
367        instrument_id: InstrumentId,
368        client_order_id: ClientOrderId,
369        venue_order_id: VenueOrderId,
370        venue_position_id: PositionId,
371        trade_id: TradeId,
372        order_side: OrderSide,
373        order_type: OrderType,
374        last_qty: Quantity,
375        last_px: Price,
376        quote_currency: Currency,
377        commission: Money,
378        liquidity_side: LiquiditySide,
379        ts_event: UnixNanos,
380    ) {
381        let event = OrderFilled::new(
382            self.trader_id,
383            strategy_id,
384            instrument_id,
385            client_order_id,
386            venue_order_id,
387            self.account_id,
388            trade_id,
389            order_side,
390            order_type,
391            last_qty,
392            last_px,
393            quote_currency,
394            liquidity_side,
395            UUID4::new(),
396            ts_event,
397            self.clock.borrow().timestamp_ns(),
398            false,
399            Some(venue_position_id),
400            Some(commission),
401        );
402
403        self.send_order_event(OrderEventAny::Filled(event));
404    }
405
406    fn send_account_state(&self, account_state: AccountState) {
407        let endpoint = "Portfolio.update_account".into();
408        msgbus::send(endpoint, &account_state as &dyn Any);
409    }
410
411    fn send_order_event(&self, event: OrderEventAny) {
412        let endpoint = "ExecEngine.process".into();
413        msgbus::send(endpoint, &event as &dyn Any);
414    }
415
416    fn send_mass_status_report(&self, report: ExecutionMassStatus) {
417        let endpoint = "ExecEngine.reconcile_mass_status".into();
418        msgbus::send(endpoint, &report as &dyn Any);
419    }
420
421    fn send_order_status_report(&self, report: OrderStatusReport) {
422        let endpoint = "ExecEngine.reconcile_report".into();
423        msgbus::send(endpoint, &report as &dyn Any);
424    }
425
426    fn send_fill_report(&self, report: FillReport) {
427        let endpoint = "ExecEngine.reconcile_report".into();
428        msgbus::send(endpoint, &report as &dyn Any);
429    }
430
431    fn send_position_report(&self, report: PositionStatusReport) {
432        let endpoint = "ExecEngine.reconcile_report".into();
433        msgbus::send(endpoint, &report as &dyn Any);
434    }
435}