nautilus_execution/
client.rs

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