nautilus_model/orders/
stubs.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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};
19use rust_decimal_macros::dec;
20
21use super::{
22    any::OrderAny, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder, market::MarketOrder,
23    market_if_touched::MarketIfTouchedOrder, market_to_limit::MarketToLimitOrder,
24    stop_limit::StopLimitOrder, stop_market::StopMarketOrder,
25    trailing_stop_limit::TrailingStopLimitOrder, trailing_stop_market::TrailingStopMarketOrder,
26};
27use crate::{
28    enums::{LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
29    events::{OrderAccepted, OrderCanceled, OrderEventAny, OrderFilled, OrderSubmitted},
30    identifiers::{
31        AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, Venue,
32        VenueOrderId,
33    },
34    instruments::{Instrument, InstrumentAny},
35    orders::{Order, OrderTestBuilder},
36    stubs::TestDefault,
37    types::{Money, Price, Quantity},
38};
39
40impl TestDefault for LimitOrder {
41    /// Creates a new test default [`LimitOrder`] instance.
42    fn test_default() -> Self {
43        Self::new(
44            TraderId::test_default(),
45            StrategyId::test_default(),
46            InstrumentId::test_default(),
47            ClientOrderId::test_default(),
48            OrderSide::Buy,
49            Quantity::from(100_000),
50            Price::from("1.00000"),
51            TimeInForce::Gtc,
52            None,
53            false,
54            false,
55            false,
56            None,
57            None,
58            None,
59            None,
60            None,
61            None,
62            None,
63            None,
64            None,
65            None,
66            None,
67            UUID4::default(),
68            UnixNanos::default(),
69        )
70    }
71}
72
73impl TestDefault for LimitIfTouchedOrder {
74    /// Creates a new test default [`LimitIfTouchedOrder`] instance.
75    fn test_default() -> Self {
76        Self::new(
77            TraderId::test_default(),
78            StrategyId::test_default(),
79            InstrumentId::test_default(),
80            ClientOrderId::test_default(),
81            OrderSide::Buy,
82            Quantity::from(100_000),
83            Price::from("1.00000"),
84            Price::from("1.00000"),
85            TriggerType::BidAsk,
86            TimeInForce::Gtc,
87            None,
88            false,
89            false,
90            false,
91            None,
92            None,
93            None,
94            None,
95            None,
96            None,
97            None,
98            None,
99            None,
100            None,
101            None,
102            UUID4::default(),
103            UnixNanos::default(),
104        )
105    }
106}
107
108impl TestDefault for MarketOrder {
109    /// Creates a new test default [`MarketOrder`] instance.
110    fn test_default() -> Self {
111        Self::new(
112            TraderId::test_default(),
113            StrategyId::test_default(),
114            InstrumentId::test_default(),
115            ClientOrderId::test_default(),
116            OrderSide::Buy,
117            Quantity::from(100_000),
118            TimeInForce::Day,
119            UUID4::default(),
120            UnixNanos::default(),
121            false,
122            false,
123            None,
124            None,
125            None,
126            None,
127            None,
128            None,
129            None,
130            None,
131        )
132    }
133}
134
135impl TestDefault for MarketIfTouchedOrder {
136    /// Creates a new test default [`MarketIfTouchedOrder`] instance.
137    fn test_default() -> Self {
138        Self::new(
139            TraderId::test_default(),
140            StrategyId::test_default(),
141            InstrumentId::test_default(),
142            ClientOrderId::test_default(),
143            OrderSide::Buy,
144            Quantity::from(100_000),
145            Price::from("1.00000"),
146            TriggerType::BidAsk,
147            TimeInForce::Gtc,
148            None,
149            false,
150            false,
151            None,
152            None,
153            None,
154            None,
155            None,
156            None,
157            None,
158            None,
159            None,
160            None,
161            UUID4::default(),
162            UnixNanos::default(),
163        )
164    }
165}
166
167impl TestDefault for MarketToLimitOrder {
168    /// Creates a new test default [`MarketToLimitOrder`] instance.
169    fn test_default() -> Self {
170        Self::new(
171            TraderId::test_default(),
172            StrategyId::test_default(),
173            InstrumentId::test_default(),
174            ClientOrderId::test_default(),
175            OrderSide::Buy,
176            Quantity::from(100_000),
177            TimeInForce::Gtc,
178            None,
179            false,
180            false,
181            false,
182            None,
183            None,
184            None,
185            None,
186            None,
187            None,
188            None,
189            None,
190            None,
191            UUID4::default(),
192            UnixNanos::default(),
193        )
194    }
195}
196
197impl TestDefault for StopLimitOrder {
198    /// Creates a new test default [`StopLimitOrder`] instance.
199    fn test_default() -> Self {
200        Self::new(
201            TraderId::test_default(),
202            StrategyId::test_default(),
203            InstrumentId::test_default(),
204            ClientOrderId::test_default(),
205            OrderSide::Buy,
206            Quantity::from(100_000),
207            Price::from("1.00000"),
208            Price::from("1.00000"),
209            TriggerType::BidAsk,
210            TimeInForce::Gtc,
211            None,
212            false,
213            false,
214            false,
215            None,
216            None,
217            None,
218            None,
219            None,
220            None,
221            None,
222            None,
223            None,
224            None,
225            None,
226            UUID4::default(),
227            UnixNanos::default(),
228        )
229    }
230}
231
232impl TestDefault for StopMarketOrder {
233    /// Creates a new test default [`StopMarketOrder`] instance.
234    fn test_default() -> Self {
235        Self::new(
236            TraderId::test_default(),
237            StrategyId::test_default(),
238            InstrumentId::test_default(),
239            ClientOrderId::test_default(),
240            OrderSide::Buy,
241            Quantity::from(100_000),
242            Price::from("1.00000"),
243            TriggerType::BidAsk,
244            TimeInForce::Gtc,
245            None,
246            false,
247            false,
248            None,
249            None,
250            None,
251            None,
252            None,
253            None,
254            None,
255            None,
256            None,
257            None,
258            None,
259            UUID4::default(),
260            UnixNanos::default(),
261        )
262    }
263}
264
265impl TestDefault for TrailingStopLimitOrder {
266    /// Creates a new test default [`TrailingStopLimitOrder`] instance.
267    fn test_default() -> Self {
268        Self::new(
269            TraderId::test_default(),
270            StrategyId::test_default(),
271            InstrumentId::test_default(),
272            ClientOrderId::test_default(),
273            OrderSide::Buy,
274            Quantity::from(100_000),
275            Price::from("1.00000"),
276            Price::from("1.00000"),
277            TriggerType::BidAsk,
278            dec!(0.001),
279            dec!(0.001),
280            TrailingOffsetType::Price,
281            TimeInForce::Gtc,
282            None,
283            false,
284            false,
285            false,
286            None,
287            None,
288            None,
289            None,
290            None,
291            None,
292            None,
293            None,
294            None,
295            None,
296            None,
297            UUID4::default(),
298            UnixNanos::default(),
299        )
300    }
301}
302
303impl TestDefault for TrailingStopMarketOrder {
304    /// Creates a new test default [`TrailingStopMarketOrder`] instance.
305    fn test_default() -> Self {
306        Self::new(
307            TraderId::test_default(),
308            StrategyId::test_default(),
309            InstrumentId::test_default(),
310            ClientOrderId::test_default(),
311            OrderSide::Buy,
312            Quantity::from(100_000),
313            Price::from("1.00000"),
314            TriggerType::BidAsk,
315            dec!(0.001),
316            TrailingOffsetType::Price,
317            TimeInForce::Gtc,
318            None,
319            false,
320            false,
321            None,
322            None,
323            None,
324            None,
325            None,
326            None,
327            None,
328            None,
329            None,
330            None,
331            None,
332            UUID4::default(),
333            UnixNanos::default(),
334        )
335    }
336}
337
338#[derive(Debug)]
339pub struct TestOrderEventStubs;
340
341impl TestOrderEventStubs {
342    pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
343        let event = OrderSubmitted::new(
344            order.trader_id(),
345            order.strategy_id(),
346            order.instrument_id(),
347            order.client_order_id(),
348            account_id,
349            UUID4::new(),
350            UnixNanos::default(),
351            UnixNanos::default(),
352        );
353        OrderEventAny::Submitted(event)
354    }
355
356    pub fn accepted(
357        order: &OrderAny,
358        account_id: AccountId,
359        venue_order_id: VenueOrderId,
360    ) -> OrderEventAny {
361        let event = OrderAccepted::new(
362            order.trader_id(),
363            order.strategy_id(),
364            order.instrument_id(),
365            order.client_order_id(),
366            venue_order_id,
367            account_id,
368            UUID4::new(),
369            UnixNanos::default(),
370            UnixNanos::default(),
371            false,
372        );
373        OrderEventAny::Accepted(event)
374    }
375
376    pub fn canceled(
377        order: &OrderAny,
378        account_id: AccountId,
379        venue_order_id: Option<VenueOrderId>,
380    ) -> OrderEventAny {
381        let event = OrderCanceled::new(
382            order.trader_id(),
383            order.strategy_id(),
384            order.instrument_id(),
385            order.client_order_id(),
386            UUID4::new(),
387            UnixNanos::default(),
388            UnixNanos::default(),
389            false, // reconciliation
390            venue_order_id,
391            Some(account_id),
392        );
393        OrderEventAny::Canceled(event)
394    }
395
396    /// # Panics
397    ///
398    /// Panics if parsing the fallback price string fails or unwrapping default values fails.
399    #[allow(clippy::too_many_arguments)]
400    pub fn filled(
401        order: &OrderAny,
402        instrument: &InstrumentAny,
403        trade_id: Option<TradeId>,
404        position_id: Option<PositionId>,
405        last_px: Option<Price>,
406        last_qty: Option<Quantity>,
407        liquidity_side: Option<LiquiditySide>,
408        commission: Option<Money>,
409        ts_filled_ns: Option<UnixNanos>,
410        account_id: Option<AccountId>,
411    ) -> OrderEventAny {
412        let venue_order_id = order
413            .venue_order_id()
414            .unwrap_or_else(VenueOrderId::test_default);
415        let account_id = account_id
416            .or(order.account_id())
417            .unwrap_or(AccountId::from("SIM-001"));
418        let trade_id = trade_id.unwrap_or(TradeId::new(
419            order.client_order_id().as_str().replace('O', "E").as_str(),
420        ));
421        let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
422        let event = UUID4::new();
423        let position_id = position_id
424            .or_else(|| order.position_id())
425            .unwrap_or(PositionId::new("1"));
426        let commission = commission.unwrap_or(Money::from("2 USD"));
427        let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
428        let last_qty = last_qty.unwrap_or(order.quantity());
429        let event = OrderFilled::new(
430            order.trader_id(),
431            order.strategy_id(),
432            instrument.id(),
433            order.client_order_id(),
434            venue_order_id,
435            account_id,
436            trade_id,
437            order.order_side(),
438            order.order_type(),
439            last_qty,
440            last_px,
441            instrument.quote_currency(),
442            liquidity_side,
443            event,
444            ts_filled_ns.unwrap_or_default(),
445            UnixNanos::default(),
446            false,
447            Some(position_id),
448            Some(commission),
449        );
450        OrderEventAny::Filled(event)
451    }
452}
453
454#[derive(Debug)]
455pub struct TestOrderStubs;
456
457impl TestOrderStubs {
458    /// # Panics
459    ///
460    /// Panics if applying the accepted event via `new_order.apply(...)` fails.
461    pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
462        let mut new_order = order.clone();
463        let accepted_event = TestOrderEventStubs::accepted(
464            &new_order,
465            AccountId::from("SIM-001"),
466            VenueOrderId::from("V-001"),
467        );
468        new_order.apply(accepted_event).unwrap();
469        new_order
470    }
471
472    /// # Panics
473    ///
474    /// Panics if applying the filled event via `accepted_order.apply(...)` fails.
475    pub fn make_filled_order(
476        order: &OrderAny,
477        instrument: &InstrumentAny,
478        liquidity_side: LiquiditySide,
479    ) -> OrderAny {
480        let mut accepted_order = Self::make_accepted_order(order);
481        let fill = TestOrderEventStubs::filled(
482            &accepted_order,
483            instrument,
484            None,
485            None,
486            None,
487            None,
488            Some(liquidity_side),
489            None,
490            None,
491            None,
492        );
493        accepted_order.apply(fill).unwrap();
494        accepted_order
495    }
496}
497
498#[derive(Debug)]
499pub struct TestOrdersGenerator {
500    order_type: OrderType,
501    venue_instruments: HashMap<Venue, u32>,
502    orders_per_instrument: u32,
503}
504
505impl TestOrdersGenerator {
506    pub fn new(order_type: OrderType) -> Self {
507        Self {
508            order_type,
509            venue_instruments: HashMap::new(),
510            orders_per_instrument: 5,
511        }
512    }
513
514    pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
515        self.orders_per_instrument = total_orders;
516    }
517
518    pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
519        self.venue_instruments.insert(venue, total_instruments);
520    }
521
522    fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
523        let client_order_id =
524            ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
525        OrderTestBuilder::new(self.order_type)
526            .quantity(Quantity::from("1"))
527            .price(Price::from("1"))
528            .instrument_id(instrument_id)
529            .client_order_id(client_order_id)
530            .build()
531    }
532
533    pub fn build(&self) -> Vec<OrderAny> {
534        let mut orders = Vec::new();
535        for (venue, total_instruments) in &self.venue_instruments {
536            for i in 0..*total_instruments {
537                let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
538                for order_index in 0..self.orders_per_instrument {
539                    let order = self.generate_order(instrument_id, order_index);
540                    orders.push(order);
541                }
542            }
543        }
544        orders
545    }
546}
547
548pub fn create_order_list_sample(
549    total_venues: u8,
550    total_instruments: u32,
551    orders_per_instrument: u32,
552) -> Vec<OrderAny> {
553    // Create Limit orders list from order generator with spec:
554    // x venues * x instruments * x orders per instrument
555    let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
556    for i in 0..total_venues {
557        let venue = Venue::from(format!("VENUE-{i}"));
558        order_generator.add_venue_and_total_instruments(venue, total_instruments);
559    }
560    order_generator.set_orders_per_instrument(orders_per_instrument);
561
562    order_generator.build()
563}