Skip to main content

nautilus_model/orders/
builder.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
16// TODO: Under development
17#![allow(dead_code)]
18
19use indexmap::IndexMap;
20use nautilus_core::{UUID4, UnixNanos, correctness::FAILED};
21use rust_decimal::Decimal;
22use ustr::Ustr;
23
24use crate::{
25    enums::{ContingencyType, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
26    events::{OrderEventAny, order::spec::OrderSubmittedSpec},
27    identifiers::{
28        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
29    },
30    orders::{
31        Order, OrderAny, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder,
32        market::MarketOrder, market_if_touched::MarketIfTouchedOrder,
33        market_to_limit::MarketToLimitOrder, stop_limit::StopLimitOrder,
34        stop_market::StopMarketOrder, trailing_stop_limit::TrailingStopLimitOrder,
35        trailing_stop_market::TrailingStopMarketOrder,
36    },
37    stubs::TestDefault,
38    types::{Price, Quantity},
39};
40
41#[derive(Debug)]
42pub struct OrderTestBuilder {
43    kind: OrderType,
44    trader_id: Option<TraderId>,
45    strategy_id: Option<StrategyId>,
46    instrument_id: Option<InstrumentId>,
47    client_order_id: Option<ClientOrderId>,
48    side: Option<OrderSide>,
49    quantity: Option<Quantity>,
50    price: Option<Price>,
51    activation_price: Option<Price>,
52    trigger_price: Option<Price>,
53    trigger_type: Option<TriggerType>,
54    limit_offset: Option<Decimal>,
55    trailing_offset: Option<Decimal>,
56    trailing_offset_type: Option<TrailingOffsetType>,
57    time_in_force: Option<TimeInForce>,
58    expire_time: Option<UnixNanos>,
59    reduce_only: Option<bool>,
60    post_only: Option<bool>,
61    quote_quantity: Option<bool>,
62    display_qty: Option<Quantity>,
63    emulation_trigger: Option<TriggerType>,
64    trigger_instrument_id: Option<InstrumentId>,
65    order_list_id: Option<OrderListId>,
66    linked_order_ids: Option<Vec<ClientOrderId>>,
67    parent_order_id: Option<ClientOrderId>,
68    exec_algorithm_id: Option<ExecAlgorithmId>,
69    exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
70    exec_spawn_id: Option<ClientOrderId>,
71    tags: Option<Vec<Ustr>>,
72    init_id: Option<UUID4>,
73    ts_init: Option<UnixNanos>,
74    contingency_type: Option<ContingencyType>,
75    submitted: bool,
76}
77
78impl OrderTestBuilder {
79    /// Creates a new [`OrderTestBuilder`] instance.
80    #[must_use]
81    pub fn new(kind: OrderType) -> Self {
82        Self {
83            kind,
84            trader_id: None,
85            strategy_id: None,
86            instrument_id: None,
87            client_order_id: None,
88            side: None,
89            quantity: None,
90            price: None,
91            activation_price: None,
92            trigger_price: None,
93            trigger_type: None,
94            limit_offset: None,
95            trailing_offset: None,
96            trailing_offset_type: None,
97            time_in_force: None,
98            contingency_type: None,
99            expire_time: None,
100            reduce_only: None,
101            post_only: None,
102            quote_quantity: None,
103            display_qty: None,
104            emulation_trigger: None,
105            trigger_instrument_id: None,
106            linked_order_ids: None,
107            order_list_id: None,
108            parent_order_id: None,
109            exec_algorithm_id: None,
110            exec_algorithm_params: None,
111            exec_spawn_id: None,
112            init_id: None,
113            ts_init: None,
114            tags: None,
115            submitted: false,
116        }
117    }
118
119    pub fn submit(&mut self, submit: bool) -> &mut Self {
120        self.submitted = submit;
121        self
122    }
123
124    pub fn kind(&mut self, kind: OrderType) -> &mut Self {
125        self.kind = kind;
126        self
127    }
128
129    pub fn trader_id(&mut self, trader_id: TraderId) -> &mut Self {
130        self.trader_id = Some(trader_id);
131        self
132    }
133
134    fn get_trader_id(&self) -> TraderId {
135        self.trader_id.unwrap_or_else(TraderId::test_default)
136    }
137
138    // ----------- StrategyId ----------
139    pub fn strategy_id(&mut self, strategy_id: StrategyId) -> &mut Self {
140        self.strategy_id = Some(strategy_id);
141        self
142    }
143
144    fn get_strategy_id(&self) -> StrategyId {
145        self.strategy_id.unwrap_or_else(StrategyId::test_default)
146    }
147
148    // ----------- InstrumentId ----------
149    pub fn instrument_id(&mut self, instrument_id: InstrumentId) -> &mut Self {
150        self.instrument_id = Some(instrument_id);
151        self
152    }
153
154    fn get_instrument_id(&self) -> InstrumentId {
155        self.instrument_id.expect("Instrument ID not set")
156    }
157
158    // ----------- ClientOrderId ----------
159    pub fn client_order_id(&mut self, client_order_id: ClientOrderId) -> &mut Self {
160        self.client_order_id = Some(client_order_id);
161        self
162    }
163
164    fn get_client_order_id(&self) -> ClientOrderId {
165        self.client_order_id
166            .unwrap_or_else(ClientOrderId::test_default)
167    }
168
169    // ----------- OrderSide ----------
170    pub fn side(&mut self, side: OrderSide) -> &mut Self {
171        self.side = Some(side);
172        self
173    }
174
175    fn get_side(&self) -> OrderSide {
176        self.side.unwrap_or(OrderSide::Buy)
177    }
178
179    // ----------- Quantity ----------
180    pub fn quantity(&mut self, quantity: Quantity) -> &mut Self {
181        self.quantity = Some(quantity);
182        self
183    }
184
185    fn get_quantity(&self) -> Quantity {
186        self.quantity.expect("Order quantity not set")
187    }
188
189    // ----------- Price ----------
190    pub fn price(&mut self, price: Price) -> &mut Self {
191        self.price = Some(price);
192        self
193    }
194
195    fn get_price(&self) -> Price {
196        self.price.expect("Price not set")
197    }
198
199    // ----------- TriggerPrice ----------
200    pub fn trigger_price(&mut self, trigger_price: Price) -> &mut Self {
201        self.trigger_price = Some(trigger_price);
202        self
203    }
204
205    fn get_trigger_price(&self) -> Price {
206        self.trigger_price.expect("Trigger price not set")
207    }
208
209    // ----------- ActivationPrice ----------
210    pub fn activation_price(&mut self, activation_price: Price) -> &mut Self {
211        self.activation_price = Some(activation_price);
212        self
213    }
214
215    fn get_activation_price(&self) -> Option<Price> {
216        self.activation_price
217    }
218
219    // ----------- TriggerType ----------
220    pub fn trigger_type(&mut self, trigger_type: TriggerType) -> &mut Self {
221        self.trigger_type = Some(trigger_type);
222        self
223    }
224
225    fn get_trigger_type(&self) -> TriggerType {
226        self.trigger_type.unwrap_or(TriggerType::Default)
227    }
228
229    // ----------- LimitOffset ----------
230    pub fn limit_offset(&mut self, limit_offset: Decimal) -> &mut Self {
231        self.limit_offset = Some(limit_offset);
232        self
233    }
234
235    fn get_limit_offset(&self) -> Decimal {
236        self.limit_offset.expect("Limit offset not set")
237    }
238
239    // ----------- TrailingOffset ----------
240    pub fn trailing_offset(&mut self, trailing_offset: Decimal) -> &mut Self {
241        self.trailing_offset = Some(trailing_offset);
242        self
243    }
244
245    fn get_trailing_offset(&self) -> Decimal {
246        self.trailing_offset.expect("Trailing offset not set")
247    }
248
249    // ----------- TrailingOffsetType ----------
250    pub fn trailing_offset_type(&mut self, trailing_offset_type: TrailingOffsetType) -> &mut Self {
251        self.trailing_offset_type = Some(trailing_offset_type);
252        self
253    }
254
255    fn get_trailing_offset_type(&self) -> TrailingOffsetType {
256        self.trailing_offset_type
257            .expect("Trailing offset type not set")
258    }
259
260    // ----------- TimeInForce ----------
261    pub fn time_in_force(&mut self, time_in_force: TimeInForce) -> &mut Self {
262        self.time_in_force = Some(time_in_force);
263        self
264    }
265
266    fn get_time_in_force(&self) -> TimeInForce {
267        self.time_in_force.unwrap_or(TimeInForce::Gtc)
268    }
269
270    // ----------- ExpireTime ----------
271    pub fn expire_time(&mut self, expire_time: UnixNanos) -> &mut Self {
272        self.expire_time = Some(expire_time);
273        self
274    }
275
276    fn get_expire_time(&self) -> Option<UnixNanos> {
277        self.expire_time
278    }
279
280    // ----------- DisplayQty ----------
281    pub fn display_qty(&mut self, display_qty: Quantity) -> &mut Self {
282        self.display_qty = Some(display_qty);
283        self
284    }
285
286    fn get_display_qty(&self) -> Option<Quantity> {
287        self.display_qty
288    }
289
290    // ----------- EmulationTrigger ----------
291    pub fn emulation_trigger(&mut self, emulation_trigger: TriggerType) -> &mut Self {
292        self.emulation_trigger = Some(emulation_trigger);
293        self
294    }
295
296    fn get_emulation_trigger(&self) -> Option<TriggerType> {
297        self.emulation_trigger
298    }
299
300    // ----------- TriggerInstrumentId ----------
301    pub fn trigger_instrument_id(&mut self, trigger_instrument_id: InstrumentId) -> &mut Self {
302        self.trigger_instrument_id = Some(trigger_instrument_id);
303        self
304    }
305
306    fn get_trigger_instrument_id(&self) -> Option<InstrumentId> {
307        self.trigger_instrument_id
308    }
309
310    // ----------- OrderListId ----------
311    pub fn order_list_id(&mut self, order_list_id: OrderListId) -> &mut Self {
312        self.order_list_id = Some(order_list_id);
313        self
314    }
315
316    fn get_order_list_id(&self) -> Option<OrderListId> {
317        self.order_list_id
318    }
319
320    // ----------- LinkedOrderIds ----------
321    pub fn linked_order_ids(&mut self, linked_order_ids: Vec<ClientOrderId>) -> &mut Self {
322        self.linked_order_ids = Some(linked_order_ids);
323        self
324    }
325
326    fn get_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
327        self.linked_order_ids.clone()
328    }
329
330    // ----------- ParentOrderId ----------
331    pub fn parent_order_id(&mut self, parent_order_id: ClientOrderId) -> &mut Self {
332        self.parent_order_id = Some(parent_order_id);
333        self
334    }
335
336    fn get_parent_order_id(&self) -> Option<ClientOrderId> {
337        self.parent_order_id
338    }
339
340    // ----------- ExecAlgorithmId ----------
341    pub fn exec_algorithm_id(&mut self, exec_algorithm_id: ExecAlgorithmId) -> &mut Self {
342        self.exec_algorithm_id = Some(exec_algorithm_id);
343        self
344    }
345
346    fn get_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
347        self.exec_algorithm_id
348    }
349
350    // ----------- ExecAlgorithmParams ----------
351    pub fn exec_algorithm_params(
352        &mut self,
353        exec_algorithm_params: IndexMap<Ustr, Ustr>,
354    ) -> &mut Self {
355        self.exec_algorithm_params = Some(exec_algorithm_params);
356        self
357    }
358
359    fn get_exec_algorithm_params(&self) -> Option<IndexMap<Ustr, Ustr>> {
360        self.exec_algorithm_params.clone()
361    }
362
363    // ----------- ExecSpawnId ----------
364    pub fn exec_spawn_id(&mut self, exec_spawn_id: ClientOrderId) -> &mut Self {
365        self.exec_spawn_id = Some(exec_spawn_id);
366        self
367    }
368
369    fn get_exec_spawn_id(&self) -> Option<ClientOrderId> {
370        self.exec_spawn_id
371    }
372
373    // ----------- Tags ----------
374    pub fn tags(&mut self, tags: Vec<Ustr>) -> &mut Self {
375        self.tags = Some(tags);
376        self
377    }
378
379    fn get_tags(&self) -> Option<Vec<Ustr>> {
380        self.tags.clone()
381    }
382
383    // ----------- InitId ----------
384    pub fn init_id(&mut self, init_id: UUID4) -> &mut Self {
385        self.init_id = Some(init_id);
386        self
387    }
388
389    fn get_init_id(&self) -> UUID4 {
390        self.init_id.unwrap_or_default()
391    }
392
393    // ----------- TsInit ----------
394    pub fn ts_init(&mut self, ts_init: UnixNanos) -> &mut Self {
395        self.ts_init = Some(ts_init);
396        self
397    }
398
399    fn get_ts_init(&self) -> UnixNanos {
400        self.ts_init.unwrap_or_default()
401    }
402
403    // ----------- ReduceOnly ----------
404    pub fn reduce_only(&mut self, reduce_only: bool) -> &mut Self {
405        self.reduce_only = Some(reduce_only);
406        self
407    }
408
409    fn get_reduce_only(&self) -> bool {
410        self.reduce_only.unwrap_or(false)
411    }
412
413    // ----------- PostOnly ----------
414    pub fn post_only(&mut self, post_only: bool) -> &mut Self {
415        self.post_only = Some(post_only);
416        self
417    }
418
419    fn get_post_only(&self) -> bool {
420        self.post_only.unwrap_or(false)
421    }
422
423    // ----------- QuoteQuantity ----------
424    pub fn quote_quantity(&mut self, quote_quantity: bool) -> &mut Self {
425        self.quote_quantity = Some(quote_quantity);
426        self
427    }
428
429    fn get_quote_quantity(&self) -> bool {
430        self.quote_quantity.unwrap_or(false)
431    }
432
433    // ----------- ContingencyType ----------
434    pub fn contingency_type(&mut self, contingency_type: ContingencyType) -> &mut Self {
435        self.contingency_type = Some(contingency_type);
436        self
437    }
438
439    fn get_contingency_type(&self) -> Option<ContingencyType> {
440        self.contingency_type
441    }
442
443    /// Builds the order, consuming the provided parameters.
444    ///
445    /// # Panics
446    ///
447    /// Panics if required fields (instrument ID, quantity, price, offsets, etc.) are not set,
448    /// or if internal calls to `.expect(...)` or `.unwrap()` fail during order construction.
449    #[must_use]
450    pub fn build(&self) -> OrderAny {
451        let mut order = match self.kind {
452            OrderType::Market => OrderAny::Market(MarketOrder::new(
453                self.get_trader_id(),
454                self.get_strategy_id(),
455                self.get_instrument_id(),
456                self.get_client_order_id(),
457                self.get_side(),
458                self.get_quantity(),
459                self.get_time_in_force(),
460                self.get_init_id(),
461                self.get_ts_init(),
462                self.get_reduce_only(),
463                self.get_quote_quantity(),
464                self.get_contingency_type(),
465                self.get_order_list_id(),
466                self.get_linked_order_ids(),
467                self.get_parent_order_id(),
468                self.get_exec_algorithm_id(),
469                self.get_exec_algorithm_params(),
470                self.get_exec_spawn_id(),
471                self.get_tags(),
472            )),
473            OrderType::Limit => OrderAny::Limit(LimitOrder::new(
474                self.get_trader_id(),
475                self.get_strategy_id(),
476                self.get_instrument_id(),
477                self.get_client_order_id(),
478                self.get_side(),
479                self.get_quantity(),
480                self.get_price(),
481                self.get_time_in_force(),
482                self.get_expire_time(),
483                self.get_post_only(),
484                self.get_reduce_only(),
485                self.get_quote_quantity(),
486                self.get_display_qty(),
487                self.get_emulation_trigger(),
488                self.get_trigger_instrument_id(),
489                self.get_contingency_type(),
490                self.get_order_list_id(),
491                self.get_linked_order_ids(),
492                self.get_parent_order_id(),
493                self.get_exec_algorithm_id(),
494                self.get_exec_algorithm_params(),
495                self.get_exec_spawn_id(),
496                self.get_tags(),
497                self.get_init_id(),
498                self.get_ts_init(),
499            )),
500            OrderType::StopMarket => OrderAny::StopMarket(StopMarketOrder::new(
501                self.get_trader_id(),
502                self.get_strategy_id(),
503                self.get_instrument_id(),
504                self.get_client_order_id(),
505                self.get_side(),
506                self.get_quantity(),
507                self.get_trigger_price(),
508                self.get_trigger_type(),
509                self.get_time_in_force(),
510                self.get_expire_time(),
511                self.get_reduce_only(),
512                self.get_quote_quantity(),
513                self.get_display_qty(),
514                self.get_emulation_trigger(),
515                self.get_trigger_instrument_id(),
516                self.get_contingency_type(),
517                self.get_order_list_id(),
518                self.get_linked_order_ids(),
519                self.get_parent_order_id(),
520                self.get_exec_algorithm_id(),
521                self.get_exec_algorithm_params(),
522                self.get_exec_spawn_id(),
523                self.get_tags(),
524                self.get_init_id(),
525                self.get_ts_init(),
526            )),
527            OrderType::StopLimit => OrderAny::StopLimit(StopLimitOrder::new(
528                self.get_trader_id(),
529                self.get_strategy_id(),
530                self.get_instrument_id(),
531                self.get_client_order_id(),
532                self.get_side(),
533                self.get_quantity(),
534                self.get_price(),
535                self.get_trigger_price(),
536                self.get_trigger_type(),
537                self.get_time_in_force(),
538                self.get_expire_time(),
539                self.get_post_only(),
540                self.get_reduce_only(),
541                self.get_quote_quantity(),
542                self.get_display_qty(),
543                self.get_emulation_trigger(),
544                self.get_trigger_instrument_id(),
545                self.get_contingency_type(),
546                self.get_order_list_id(),
547                self.get_linked_order_ids(),
548                self.get_parent_order_id(),
549                self.get_exec_algorithm_id(),
550                self.get_exec_algorithm_params(),
551                self.get_exec_spawn_id(),
552                self.get_tags(),
553                self.get_init_id(),
554                self.get_ts_init(),
555            )),
556            OrderType::MarketToLimit => OrderAny::MarketToLimit(MarketToLimitOrder::new(
557                self.get_trader_id(),
558                self.get_strategy_id(),
559                self.get_instrument_id(),
560                self.get_client_order_id(),
561                self.get_side(),
562                self.get_quantity(),
563                self.get_time_in_force(),
564                self.get_expire_time(),
565                self.get_post_only(),
566                self.get_reduce_only(),
567                self.get_quote_quantity(),
568                self.get_display_qty(),
569                self.get_contingency_type(),
570                self.get_order_list_id(),
571                self.get_linked_order_ids(),
572                self.get_parent_order_id(),
573                self.get_exec_algorithm_id(),
574                self.get_exec_algorithm_params(),
575                self.get_exec_spawn_id(),
576                self.get_tags(),
577                self.get_init_id(),
578                self.get_ts_init(),
579            )),
580            OrderType::MarketIfTouched => OrderAny::MarketIfTouched(MarketIfTouchedOrder::new(
581                self.get_trader_id(),
582                self.get_strategy_id(),
583                self.get_instrument_id(),
584                self.get_client_order_id(),
585                self.get_side(),
586                self.get_quantity(),
587                self.get_trigger_price(),
588                self.get_trigger_type(),
589                self.get_time_in_force(),
590                self.get_expire_time(),
591                self.get_reduce_only(),
592                self.get_quote_quantity(),
593                self.get_emulation_trigger(),
594                self.get_trigger_instrument_id(),
595                self.get_contingency_type(),
596                self.get_order_list_id(),
597                self.get_linked_order_ids(),
598                self.get_parent_order_id(),
599                self.get_exec_algorithm_id(),
600                self.get_exec_algorithm_params(),
601                self.get_exec_spawn_id(),
602                self.get_tags(),
603                self.get_init_id(),
604                self.get_ts_init(),
605            )),
606            OrderType::LimitIfTouched => OrderAny::LimitIfTouched(LimitIfTouchedOrder::new(
607                self.get_trader_id(),
608                self.get_strategy_id(),
609                self.get_instrument_id(),
610                self.get_client_order_id(),
611                self.get_side(),
612                self.get_quantity(),
613                self.get_price(),
614                self.get_trigger_price(),
615                self.get_trigger_type(),
616                self.get_time_in_force(),
617                self.get_expire_time(),
618                self.get_post_only(),
619                self.get_reduce_only(),
620                self.get_quote_quantity(),
621                self.get_display_qty(),
622                self.get_emulation_trigger(),
623                self.get_trigger_instrument_id(),
624                self.get_contingency_type(),
625                self.get_order_list_id(),
626                self.get_linked_order_ids(),
627                self.get_parent_order_id(),
628                self.get_exec_algorithm_id(),
629                self.get_exec_algorithm_params(),
630                self.get_exec_spawn_id(),
631                self.get_tags(),
632                self.get_init_id(),
633                self.get_ts_init(),
634            )),
635            OrderType::TrailingStopMarket => OrderAny::TrailingStopMarket(
636                // `new_checked` (not `new`) so the trigger may be left unset for the
637                // activate-at-market path where it materializes on the first trail update.
638                TrailingStopMarketOrder::new_checked(
639                    self.get_trader_id(),
640                    self.get_strategy_id(),
641                    self.get_instrument_id(),
642                    self.get_client_order_id(),
643                    self.get_side(),
644                    self.get_quantity(),
645                    self.get_activation_price(),
646                    self.trigger_price,
647                    self.get_trigger_type(),
648                    self.get_trailing_offset(),
649                    self.get_trailing_offset_type(),
650                    self.get_time_in_force(),
651                    self.get_expire_time(),
652                    self.get_reduce_only(),
653                    self.get_quote_quantity(),
654                    self.get_display_qty(),
655                    self.get_emulation_trigger(),
656                    self.get_trigger_instrument_id(),
657                    self.get_contingency_type(),
658                    self.get_order_list_id(),
659                    self.get_linked_order_ids(),
660                    self.get_parent_order_id(),
661                    self.get_exec_algorithm_id(),
662                    self.get_exec_algorithm_params(),
663                    self.get_exec_spawn_id(),
664                    self.get_tags(),
665                    self.get_init_id(),
666                    self.get_ts_init(),
667                )
668                .unwrap_or_else(|e| panic!("{FAILED}: {e}")),
669            ),
670            OrderType::TrailingStopLimit => OrderAny::TrailingStopLimit(
671                TrailingStopLimitOrder::new_checked(
672                    self.get_trader_id(),
673                    self.get_strategy_id(),
674                    self.get_instrument_id(),
675                    self.get_client_order_id(),
676                    self.get_side(),
677                    self.get_quantity(),
678                    self.get_activation_price(),
679                    self.price,
680                    self.trigger_price,
681                    self.get_trigger_type(),
682                    self.get_limit_offset(),
683                    self.get_trailing_offset(),
684                    self.get_trailing_offset_type(),
685                    self.get_time_in_force(),
686                    self.get_expire_time(),
687                    self.get_post_only(),
688                    self.get_reduce_only(),
689                    self.get_quote_quantity(),
690                    self.get_display_qty(),
691                    self.get_emulation_trigger(),
692                    self.get_trigger_instrument_id(),
693                    self.get_contingency_type(),
694                    self.get_order_list_id(),
695                    self.get_linked_order_ids(),
696                    self.get_parent_order_id(),
697                    self.get_exec_algorithm_id(),
698                    self.get_exec_algorithm_params(),
699                    self.get_exec_spawn_id(),
700                    self.get_tags(),
701                    self.get_init_id(),
702                    self.get_ts_init(),
703                )
704                .unwrap_or_else(|e| panic!("{FAILED}: {e}")),
705            ),
706        };
707
708        if self.submitted {
709            let submit_event = OrderSubmittedSpec::builder()
710                .trader_id(order.trader_id())
711                .strategy_id(order.strategy_id())
712                .instrument_id(order.instrument_id())
713                .client_order_id(order.client_order_id())
714                .account_id(AccountId::from("ACCOUNT-001"))
715                .build();
716            order.apply(OrderEventAny::Submitted(submit_event)).unwrap();
717        }
718
719        order
720    }
721}
722
723#[cfg(test)]
724mod tests {
725    use rstest::rstest;
726
727    use super::*;
728    use crate::orders::Order;
729
730    #[rstest]
731    fn preserves_an_absent_contingency_type() {
732        let order = OrderTestBuilder::new(OrderType::Limit)
733            .instrument_id(InstrumentId::test_default())
734            .quantity(Quantity::from(1))
735            .price(Price::from("1"))
736            .build();
737
738        assert_eq!(order.contingency_type(), None);
739        assert!(!order.is_contingency());
740    }
741
742    #[rstest]
743    fn preserves_a_configured_contingency_type() {
744        let order = OrderTestBuilder::new(OrderType::Limit)
745            .instrument_id(InstrumentId::test_default())
746            .quantity(Quantity::from(1))
747            .price(Price::from("1"))
748            .contingency_type(ContingencyType::Oto)
749            .linked_order_ids(vec![ClientOrderId::from("O-LINKED")])
750            .build();
751
752        assert_eq!(order.contingency_type(), Some(ContingencyType::Oto));
753        assert!(order.is_contingency());
754    }
755
756    #[rstest]
757    fn submits_to_the_account_issuer() {
758        let order = OrderTestBuilder::new(OrderType::Market)
759            .instrument_id(InstrumentId::test_default())
760            .quantity(Quantity::from(1))
761            .submit(true)
762            .build();
763
764        assert_eq!(order.account_id(), Some(AccountId::from("ACCOUNT-001")));
765    }
766}