nautilus_model/orders/
stubs.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
16use std::{collections::HashMap, str::FromStr};
17
18use nautilus_core::{UUID4, UnixNanos};
19
20use super::any::OrderAny;
21use crate::{
22    enums::{LiquiditySide, OrderType},
23    events::{OrderAccepted, OrderCanceled, OrderEventAny, OrderFilled, OrderSubmitted},
24    identifiers::{
25        AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, Venue, VenueOrderId,
26    },
27    instruments::{Instrument, InstrumentAny},
28    orders::{Order, OrderTestBuilder},
29    types::{Money, Price, Quantity},
30};
31
32// Test Event Stubs
33#[derive(Debug)]
34pub struct TestOrderEventStubs;
35
36impl TestOrderEventStubs {
37    pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
38        let event = OrderSubmitted::new(
39            order.trader_id(),
40            order.strategy_id(),
41            order.instrument_id(),
42            order.client_order_id(),
43            account_id,
44            UUID4::new(),
45            UnixNanos::default(),
46            UnixNanos::default(),
47        );
48        OrderEventAny::Submitted(event)
49    }
50
51    pub fn accepted(
52        order: &OrderAny,
53        account_id: AccountId,
54        venue_order_id: VenueOrderId,
55    ) -> OrderEventAny {
56        let event = OrderAccepted::new(
57            order.trader_id(),
58            order.strategy_id(),
59            order.instrument_id(),
60            order.client_order_id(),
61            venue_order_id,
62            account_id,
63            UUID4::new(),
64            UnixNanos::default(),
65            UnixNanos::default(),
66            false,
67        );
68        OrderEventAny::Accepted(event)
69    }
70
71    pub fn canceled(
72        order: &OrderAny,
73        account_id: AccountId,
74        venue_order_id: Option<VenueOrderId>,
75    ) -> OrderEventAny {
76        let event = OrderCanceled::new(
77            order.trader_id(),
78            order.strategy_id(),
79            order.instrument_id(),
80            order.client_order_id(),
81            UUID4::new(),
82            UnixNanos::default(),
83            UnixNanos::default(),
84            false, // reconciliation
85            venue_order_id,
86            Some(account_id),
87        );
88        OrderEventAny::Canceled(event)
89    }
90
91    /// # Panics
92    ///
93    /// Panics if parsing the fallback price string fails or unwrapping default values fails.
94    #[allow(clippy::too_many_arguments)]
95    pub fn filled(
96        order: &OrderAny,
97        instrument: &InstrumentAny,
98        trade_id: Option<TradeId>,
99        position_id: Option<PositionId>,
100        last_px: Option<Price>,
101        last_qty: Option<Quantity>,
102        liquidity_side: Option<LiquiditySide>,
103        commission: Option<Money>,
104        ts_filled_ns: Option<UnixNanos>,
105        account_id: Option<AccountId>,
106    ) -> OrderEventAny {
107        let venue_order_id = order.venue_order_id().unwrap_or_default();
108        let account_id = account_id
109            .or(order.account_id())
110            .unwrap_or(AccountId::from("SIM-001"));
111        let trade_id = trade_id.unwrap_or(TradeId::new(
112            order.client_order_id().as_str().replace('O', "E").as_str(),
113        ));
114        let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
115        let event = UUID4::new();
116        let position_id = position_id
117            .or_else(|| order.position_id())
118            .unwrap_or(PositionId::new("1"));
119        let commission = commission.unwrap_or(Money::from("2 USD"));
120        let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
121        let last_qty = last_qty.unwrap_or(order.quantity());
122        let event = OrderFilled::new(
123            order.trader_id(),
124            order.strategy_id(),
125            instrument.id(),
126            order.client_order_id(),
127            venue_order_id,
128            account_id,
129            trade_id,
130            order.order_side(),
131            order.order_type(),
132            last_qty,
133            last_px,
134            instrument.quote_currency(),
135            liquidity_side,
136            event,
137            ts_filled_ns.unwrap_or_default(),
138            UnixNanos::default(),
139            false,
140            Some(position_id),
141            Some(commission),
142        );
143        OrderEventAny::Filled(event)
144    }
145}
146
147#[derive(Debug)]
148pub struct TestOrderStubs;
149
150impl TestOrderStubs {
151    /// # Panics
152    ///
153    /// Panics if applying the accepted event via `new_order.apply(...)` fails.
154    pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
155        let mut new_order = order.clone();
156        let accepted_event = TestOrderEventStubs::accepted(
157            &new_order,
158            AccountId::from("SIM-001"),
159            VenueOrderId::from("V-001"),
160        );
161        new_order.apply(accepted_event).unwrap();
162        new_order
163    }
164
165    /// # Panics
166    ///
167    /// Panics if applying the filled event via `accepted_order.apply(...)` fails.
168    pub fn make_filled_order(
169        order: &OrderAny,
170        instrument: &InstrumentAny,
171        liquidity_side: LiquiditySide,
172    ) -> OrderAny {
173        let mut accepted_order = TestOrderStubs::make_accepted_order(order);
174        let fill = TestOrderEventStubs::filled(
175            &accepted_order,
176            instrument,
177            None,
178            None,
179            None,
180            None,
181            Some(liquidity_side),
182            None,
183            None,
184            None,
185        );
186        accepted_order.apply(fill).unwrap();
187        accepted_order
188    }
189}
190
191#[derive(Debug)]
192pub struct TestOrdersGenerator {
193    order_type: OrderType,
194    venue_instruments: HashMap<Venue, u32>,
195    orders_per_instrument: u32,
196}
197
198impl TestOrdersGenerator {
199    pub fn new(order_type: OrderType) -> Self {
200        Self {
201            order_type,
202            venue_instruments: HashMap::new(),
203            orders_per_instrument: 5,
204        }
205    }
206
207    pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
208        self.orders_per_instrument = total_orders;
209    }
210
211    pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
212        self.venue_instruments.insert(venue, total_instruments);
213    }
214
215    fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
216        let client_order_id =
217            ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
218        OrderTestBuilder::new(self.order_type)
219            .quantity(Quantity::from("1"))
220            .price(Price::from("1"))
221            .instrument_id(instrument_id)
222            .client_order_id(client_order_id)
223            .build()
224    }
225
226    pub fn build(&self) -> Vec<OrderAny> {
227        let mut orders = Vec::new();
228        for (venue, total_instruments) in self.venue_instruments.iter() {
229            for i in 0..*total_instruments {
230                let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
231                for order_index in 0..self.orders_per_instrument {
232                    let order = self.generate_order(instrument_id, order_index);
233                    orders.push(order);
234                }
235            }
236        }
237        orders
238    }
239}
240
241pub fn create_order_list_sample(
242    total_venues: u8,
243    total_instruments: u32,
244    orders_per_instrument: u32,
245) -> Vec<OrderAny> {
246    // Create Limit orders list from order generator with spec:
247    // x venues * x instruments * x orders per instrument
248    let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
249    for i in 0..total_venues {
250        let venue = Venue::from(format!("VENUE-{i}"));
251        order_generator.add_venue_and_total_instruments(venue, total_instruments);
252    }
253    order_generator.set_orders_per_instrument(orders_per_instrument);
254
255    order_generator.build()
256}