1use std::{
17 fmt::Display,
18 ops::{Deref, DerefMut},
19};
20
21use indexmap::IndexMap;
22use nautilus_core::{UUID4, UnixNanos, correctness::FAILED};
23use rust_decimal::Decimal;
24use serde::{Deserialize, Serialize};
25use ustr::Ustr;
26
27use super::{Order, OrderAny, OrderCore};
28use crate::{
29 enums::{
30 ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide,
31 TimeInForce, TrailingOffsetType, TriggerType,
32 },
33 events::{OrderEventAny, OrderInitialized, OrderUpdated},
34 identifiers::{
35 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
36 StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
37 },
38 orders::{OrderError, check_display_qty, check_time_in_force},
39 types::{Currency, Money, Price, Quantity, quantity::check_positive_quantity},
40};
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
43#[cfg_attr(
44 feature = "python",
45 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
46)]
47#[cfg_attr(
48 feature = "python",
49 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
50)]
51pub struct StopMarketOrder {
52 pub trigger_price: Price,
53 pub trigger_type: TriggerType,
54 pub expire_time: Option<UnixNanos>,
55 pub display_qty: Option<Quantity>,
56 pub trigger_instrument_id: Option<InstrumentId>,
57 pub is_triggered: bool,
58 pub ts_triggered: Option<UnixNanos>,
59 pub protection_price: Option<Price>,
60 core: OrderCore,
61}
62
63impl StopMarketOrder {
64 #[expect(clippy::too_many_arguments)]
73 pub fn new_checked(
74 trader_id: TraderId,
75 strategy_id: StrategyId,
76 instrument_id: InstrumentId,
77 client_order_id: ClientOrderId,
78 order_side: OrderSide,
79 quantity: Quantity,
80 trigger_price: Price,
81 trigger_type: TriggerType,
82 time_in_force: TimeInForce,
83 expire_time: Option<UnixNanos>,
84 reduce_only: bool,
85 quote_quantity: bool,
86 display_qty: Option<Quantity>,
87 emulation_trigger: Option<TriggerType>,
88 trigger_instrument_id: Option<InstrumentId>,
89 contingency_type: Option<ContingencyType>,
90 order_list_id: Option<OrderListId>,
91 linked_order_ids: Option<Vec<ClientOrderId>>,
92 parent_order_id: Option<ClientOrderId>,
93 exec_algorithm_id: Option<ExecAlgorithmId>,
94 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
95 exec_spawn_id: Option<ClientOrderId>,
96 tags: Option<Vec<Ustr>>,
97 init_id: UUID4,
98 ts_init: UnixNanos,
99 ) -> Result<Self, OrderError> {
100 check_positive_quantity(quantity, stringify!(quantity))?;
101 check_display_qty(display_qty, quantity)?;
102 check_time_in_force(time_in_force, expire_time)?;
103
104 let init_order = OrderInitialized::new(
105 trader_id,
106 strategy_id,
107 instrument_id,
108 client_order_id,
109 order_side,
110 OrderType::StopMarket,
111 quantity,
112 time_in_force,
113 false,
114 reduce_only,
115 quote_quantity,
116 false,
117 init_id,
118 ts_init,
119 ts_init,
120 None,
121 Some(trigger_price),
122 Some(trigger_type),
123 None,
124 None,
125 None,
126 expire_time,
127 display_qty,
128 emulation_trigger,
129 trigger_instrument_id,
130 contingency_type,
131 order_list_id,
132 linked_order_ids,
133 parent_order_id,
134 exec_algorithm_id,
135 exec_algorithm_params,
136 exec_spawn_id,
137 tags,
138 );
139
140 Ok(Self {
141 core: OrderCore::new(init_order),
142 trigger_price,
143 trigger_type,
144 expire_time,
145 display_qty,
146 trigger_instrument_id,
147 is_triggered: false,
148 ts_triggered: None,
149 protection_price: None,
150 })
151 }
152
153 #[expect(clippy::too_many_arguments)]
159 #[must_use]
160 pub fn new(
161 trader_id: TraderId,
162 strategy_id: StrategyId,
163 instrument_id: InstrumentId,
164 client_order_id: ClientOrderId,
165 order_side: OrderSide,
166 quantity: Quantity,
167 trigger_price: Price,
168 trigger_type: TriggerType,
169 time_in_force: TimeInForce,
170 expire_time: Option<UnixNanos>,
171 reduce_only: bool,
172 quote_quantity: bool,
173 display_qty: Option<Quantity>,
174 emulation_trigger: Option<TriggerType>,
175 trigger_instrument_id: Option<InstrumentId>,
176 contingency_type: Option<ContingencyType>,
177 order_list_id: Option<OrderListId>,
178 linked_order_ids: Option<Vec<ClientOrderId>>,
179 parent_order_id: Option<ClientOrderId>,
180 exec_algorithm_id: Option<ExecAlgorithmId>,
181 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
182 exec_spawn_id: Option<ClientOrderId>,
183 tags: Option<Vec<Ustr>>,
184 init_id: UUID4,
185 ts_init: UnixNanos,
186 ) -> Self {
187 Self::new_checked(
188 trader_id,
189 strategy_id,
190 instrument_id,
191 client_order_id,
192 order_side,
193 quantity,
194 trigger_price,
195 trigger_type,
196 time_in_force,
197 expire_time,
198 reduce_only,
199 quote_quantity,
200 display_qty,
201 emulation_trigger,
202 trigger_instrument_id,
203 contingency_type,
204 order_list_id,
205 linked_order_ids,
206 parent_order_id,
207 exec_algorithm_id,
208 exec_algorithm_params,
209 exec_spawn_id,
210 tags,
211 init_id,
212 ts_init,
213 )
214 .unwrap_or_else(|e| panic!("{FAILED}: {e}"))
215 }
216}
217
218impl PartialEq for StopMarketOrder {
219 fn eq(&self, other: &Self) -> bool {
220 self.client_order_id == other.client_order_id
221 }
222}
223
224impl Deref for StopMarketOrder {
225 type Target = OrderCore;
226
227 fn deref(&self) -> &Self::Target {
228 &self.core
229 }
230}
231
232impl DerefMut for StopMarketOrder {
233 fn deref_mut(&mut self) -> &mut Self::Target {
234 &mut self.core
235 }
236}
237
238impl Order for StopMarketOrder {
239 fn into_any(self) -> OrderAny {
240 OrderAny::StopMarket(self)
241 }
242
243 fn status(&self) -> OrderStatus {
244 self.status
245 }
246
247 fn trader_id(&self) -> TraderId {
248 self.trader_id
249 }
250
251 fn strategy_id(&self) -> StrategyId {
252 self.strategy_id
253 }
254
255 fn instrument_id(&self) -> InstrumentId {
256 self.instrument_id
257 }
258
259 fn symbol(&self) -> Symbol {
260 self.instrument_id.symbol
261 }
262
263 fn venue(&self) -> Venue {
264 self.instrument_id.venue
265 }
266
267 fn client_order_id(&self) -> ClientOrderId {
268 self.client_order_id
269 }
270
271 fn venue_order_id(&self) -> Option<VenueOrderId> {
272 self.venue_order_id
273 }
274
275 fn position_id(&self) -> Option<PositionId> {
276 self.position_id
277 }
278
279 fn account_id(&self) -> Option<AccountId> {
280 self.account_id
281 }
282
283 fn last_trade_id(&self) -> Option<TradeId> {
284 self.last_trade_id
285 }
286
287 fn order_side(&self) -> OrderSide {
288 self.side
289 }
290
291 fn order_type(&self) -> OrderType {
292 self.order_type
293 }
294
295 fn quantity(&self) -> Quantity {
296 self.quantity
297 }
298
299 fn time_in_force(&self) -> TimeInForce {
300 self.time_in_force
301 }
302
303 fn expire_time(&self) -> Option<UnixNanos> {
304 self.expire_time
305 }
306
307 fn price(&self) -> Option<Price> {
308 self.protection_price
309 }
310
311 fn trigger_price(&self) -> Option<Price> {
312 Some(self.trigger_price)
313 }
314
315 fn trigger_type(&self) -> Option<TriggerType> {
316 Some(self.trigger_type)
317 }
318
319 fn liquidity_side(&self) -> Option<LiquiditySide> {
320 self.liquidity_side
321 }
322
323 fn is_post_only(&self) -> bool {
324 false
325 }
326
327 fn is_reduce_only(&self) -> bool {
328 self.is_reduce_only
329 }
330
331 fn is_quote_quantity(&self) -> bool {
332 self.is_quote_quantity
333 }
334
335 fn has_price(&self) -> bool {
336 self.protection_price.is_some()
337 }
338
339 fn display_qty(&self) -> Option<Quantity> {
340 self.display_qty
341 }
342
343 fn limit_offset(&self) -> Option<Decimal> {
344 None
345 }
346
347 fn trailing_offset(&self) -> Option<Decimal> {
348 None
349 }
350
351 fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
352 None
353 }
354
355 fn emulation_trigger(&self) -> Option<TriggerType> {
356 self.emulation_trigger
357 }
358
359 fn trigger_instrument_id(&self) -> Option<InstrumentId> {
360 self.trigger_instrument_id
361 }
362
363 fn contingency_type(&self) -> Option<ContingencyType> {
364 self.contingency_type
365 }
366
367 fn order_list_id(&self) -> Option<OrderListId> {
368 self.order_list_id
369 }
370
371 fn linked_order_ids(&self) -> Option<&[ClientOrderId]> {
372 self.linked_order_ids.as_deref()
373 }
374
375 fn parent_order_id(&self) -> Option<ClientOrderId> {
376 self.parent_order_id
377 }
378
379 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
380 self.exec_algorithm_id
381 }
382
383 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>> {
384 self.exec_algorithm_params.as_ref()
385 }
386
387 fn exec_spawn_id(&self) -> Option<ClientOrderId> {
388 self.exec_spawn_id
389 }
390
391 fn tags(&self) -> Option<&[Ustr]> {
392 self.tags.as_deref()
393 }
394
395 fn filled_qty(&self) -> Quantity {
396 self.filled_qty
397 }
398
399 fn leaves_qty(&self) -> Quantity {
400 self.leaves_qty
401 }
402
403 fn overfill_qty(&self) -> Quantity {
404 self.overfill_qty
405 }
406
407 fn avg_px(&self) -> Option<f64> {
408 self.avg_px
409 }
410
411 fn slippage(&self) -> Option<f64> {
412 self.slippage
413 }
414
415 fn init_id(&self) -> UUID4 {
416 self.init_id
417 }
418
419 fn ts_init(&self) -> UnixNanos {
420 self.ts_init
421 }
422
423 fn ts_submitted(&self) -> Option<UnixNanos> {
424 self.ts_submitted
425 }
426
427 fn ts_accepted(&self) -> Option<UnixNanos> {
428 self.ts_accepted
429 }
430
431 fn ts_closed(&self) -> Option<UnixNanos> {
432 self.ts_closed
433 }
434
435 fn ts_last(&self) -> UnixNanos {
436 self.ts_last
437 }
438
439 fn events(&self) -> Vec<&OrderEventAny> {
440 self.events.iter().collect()
441 }
442
443 fn venue_order_ids(&self) -> Vec<&VenueOrderId> {
444 self.venue_order_ids.iter().collect()
445 }
446
447 fn trade_ids(&self) -> Vec<&TradeId> {
448 self.trade_ids.iter().collect()
449 }
450
451 fn commissions(&self) -> &IndexMap<Currency, Money> {
452 &self.commissions
453 }
454
455 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
456 let is_order_filled = matches!(event, OrderEventAny::Filled(_));
457 let is_order_triggered = matches!(event, OrderEventAny::Triggered(_));
458 let ts_event = if is_order_triggered {
459 Some(event.ts_event())
460 } else {
461 None
462 };
463
464 self.core.apply(event.clone())?;
465
466 if let OrderEventAny::Updated(ref event) = event {
467 self.update(event);
468 }
469
470 if is_order_triggered {
471 self.is_triggered = true;
472 self.ts_triggered = ts_event;
473 }
474
475 if is_order_filled {
476 self.core.set_slippage(self.trigger_price);
477 }
478
479 Ok(())
480 }
481
482 fn update(&mut self, event: &OrderUpdated) {
483 assert!(event.price.is_none(), "{}", OrderError::InvalidOrderEvent);
484
485 if let Some(trigger_price) = event.trigger_price {
486 self.trigger_price = trigger_price;
487 }
488
489 self.protection_price = event.protection_price;
490 self.quantity = event.quantity;
491 self.leaves_qty = self.quantity.saturating_sub(self.filled_qty);
492 }
493
494 fn is_triggered(&self) -> Option<bool> {
495 Some(self.is_triggered)
496 }
497
498 fn set_position_id(&mut self, position_id: Option<PositionId>) {
499 self.position_id = position_id;
500 }
501
502 fn set_quantity(&mut self, quantity: Quantity) {
503 self.quantity = quantity;
504 }
505
506 fn set_leaves_qty(&mut self, leaves_qty: Quantity) {
507 self.leaves_qty = leaves_qty;
508 }
509
510 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>) {
511 self.emulation_trigger = emulation_trigger;
512 }
513
514 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool) {
515 self.is_quote_quantity = is_quote_quantity;
516 }
517
518 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide) {
519 self.liquidity_side = Some(liquidity_side);
520 }
521
522 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
523 self.core.would_reduce_only(side, position_qty)
524 }
525
526 fn previous_status(&self) -> Option<OrderStatus> {
527 self.core.previous_status
528 }
529}
530
531impl Display for StopMarketOrder {
532 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533 write!(
534 f,
535 "StopMarketOrder(\
536 {} {} {} {} {}, \
537 status={}, \
538 client_order_id={}, \
539 venue_order_id={}, \
540 position_id={}, \
541 exec_algorithm_id={}, \
542 exec_spawn_id={}, \
543 tags={:?}\
544 )",
545 self.side,
546 self.quantity.to_formatted_string(),
547 self.instrument_id,
548 self.order_type,
549 self.time_in_force,
550 self.status,
551 self.client_order_id,
552 self.venue_order_id.map_or_else(
553 || "None".to_string(),
554 |venue_order_id| format!("{venue_order_id}")
555 ),
556 self.position_id.map_or_else(
557 || "None".to_string(),
558 |position_id| format!("{position_id}")
559 ),
560 self.exec_algorithm_id
561 .map_or_else(|| "None".to_string(), |id| format!("{id}")),
562 self.exec_spawn_id
563 .map_or_else(|| "None".to_string(), |id| format!("{id}")),
564 self.tags
565 )
566 }
567}
568
569impl From<OrderInitialized> for StopMarketOrder {
570 fn from(event: OrderInitialized) -> Self {
571 Self::new(
572 event.trader_id,
573 event.strategy_id,
574 event.instrument_id,
575 event.client_order_id,
576 event.order_side,
577 event.quantity,
578 event.trigger_price.expect(
579 "Error initializing order: `trigger_price` was `None` for `StopMarketOrder`",
580 ),
581 event.trigger_type.expect(
582 "Error initializing order: `trigger_type` was `None` for `StopMarketOrder`",
583 ),
584 event.time_in_force,
585 event.expire_time,
586 event.reduce_only,
587 event.quote_quantity,
588 event.display_qty,
589 event.emulation_trigger,
590 event.trigger_instrument_id,
591 event.contingency_type,
592 event.order_list_id,
593 event.linked_order_ids,
594 event.parent_order_id,
595 event.exec_algorithm_id,
596 event.exec_algorithm_params,
597 event.exec_spawn_id,
598 event.tags,
599 event.event_id,
600 event.ts_event,
601 )
602 }
603}
604
605#[cfg(test)]
609mod tests {
610 use rstest::rstest;
611
612 use super::*;
613 use crate::{
614 enums::{TimeInForce, TriggerType},
615 events::order::spec::OrderInitializedSpec,
616 identifiers::InstrumentId,
617 instruments::{CurrencyPair, stubs::*},
618 orders::{builder::OrderTestBuilder, stubs::TestOrderStubs},
619 types::{Price, Quantity},
620 };
621
622 #[rstest]
623 fn test_initialize(audusd_sim: CurrencyPair) {
624 let order = OrderTestBuilder::new(OrderType::StopMarket)
625 .instrument_id(audusd_sim.id)
626 .side(OrderSide::Buy)
627 .trigger_price(Price::from("0.68000"))
628 .trigger_type(TriggerType::LastPrice)
629 .quantity(Quantity::from(1))
630 .build();
631
632 assert_eq!(order.trigger_price(), Some(Price::from("0.68000")));
633 assert_eq!(order.price(), None);
634
635 assert_eq!(order.time_in_force(), TimeInForce::Gtc);
636
637 assert_eq!(order.is_triggered(), Some(false));
638 assert_eq!(order.filled_qty(), Quantity::from(0));
639 assert_eq!(order.leaves_qty(), Quantity::from(1));
640
641 assert_eq!(order.display_qty(), None);
642 assert_eq!(order.trigger_instrument_id(), None);
643 assert_eq!(order.order_list_id(), None);
644 }
645
646 #[rstest]
647 fn test_display(audusd_sim: CurrencyPair) {
648 let order = OrderTestBuilder::new(OrderType::StopMarket)
649 .instrument_id(audusd_sim.id)
650 .side(OrderSide::Buy)
651 .trigger_price(Price::from("0.68000"))
652 .trigger_type(TriggerType::LastPrice)
653 .quantity(Quantity::from(1))
654 .build();
655
656 assert_eq!(
657 order.to_string(),
658 "StopMarketOrder(BUY 1 AUD/USD.SIM STOP_MARKET GTC, status=INITIALIZED, client_order_id=O-19700101-000000-001-001-1, venue_order_id=None, position_id=None, exec_algorithm_id=None, exec_spawn_id=None, tags=None)"
659 );
660 }
661
662 #[rstest]
663 #[should_panic(expected = "Condition failed: `display_qty` may not exceed `quantity`")]
664 fn test_display_qty_gt_quantity_err(audusd_sim: CurrencyPair) {
665 let _ = OrderTestBuilder::new(OrderType::StopMarket)
666 .instrument_id(audusd_sim.id)
667 .side(OrderSide::Buy)
668 .trigger_price(Price::from("0.68000"))
669 .trigger_type(TriggerType::LastPrice)
670 .quantity(Quantity::from(1))
671 .display_qty(Quantity::from(2))
672 .build();
673 }
674
675 #[rstest]
676 #[should_panic(
677 expected = "Condition failed: invalid `Quantity` for 'quantity' not positive, was 0"
678 )]
679 fn test_quantity_zero_err(audusd_sim: CurrencyPair) {
680 let _ = OrderTestBuilder::new(OrderType::StopMarket)
681 .instrument_id(audusd_sim.id)
682 .side(OrderSide::Buy)
683 .trigger_price(Price::from("0.68000"))
684 .trigger_type(TriggerType::LastPrice)
685 .quantity(Quantity::from(0))
686 .build();
687 }
688
689 #[rstest]
690 #[should_panic(expected = "Condition failed: `expire_time` is required for `GTD` order")]
691 fn test_gtd_without_expire_err(audusd_sim: CurrencyPair) {
692 let _ = OrderTestBuilder::new(OrderType::StopMarket)
693 .instrument_id(audusd_sim.id)
694 .side(OrderSide::Buy)
695 .trigger_price(Price::from("0.68000"))
696 .trigger_type(TriggerType::LastPrice)
697 .time_in_force(TimeInForce::Gtd)
698 .quantity(Quantity::from(1))
699 .build();
700 }
701
702 #[rstest]
703 fn test_stop_market_order_update() {
704 let order = OrderTestBuilder::new(OrderType::StopMarket)
706 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
707 .quantity(Quantity::from(10))
708 .trigger_price(Price::new(100.0, 2))
709 .build();
710
711 let mut accepted_order = TestOrderStubs::make_accepted_order(&order);
712
713 let updated_trigger_price = Price::new(95.0, 2);
715 let updated_quantity = Quantity::from(5);
716
717 let event = OrderUpdated {
718 client_order_id: accepted_order.client_order_id(),
719 strategy_id: accepted_order.strategy_id(),
720 trigger_price: Some(updated_trigger_price),
721 quantity: updated_quantity,
722 ..Default::default()
723 };
724
725 accepted_order.apply(OrderEventAny::Updated(event)).unwrap();
726
727 assert_eq!(accepted_order.quantity(), updated_quantity);
729 assert_eq!(accepted_order.trigger_price(), Some(updated_trigger_price));
730 }
731
732 #[rstest]
733 fn test_stop_market_order_expire_time() {
734 let expire_time = UnixNanos::from(1_234_567_890);
736 let order = OrderTestBuilder::new(OrderType::StopMarket)
737 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
738 .quantity(Quantity::from(10))
739 .trigger_price(Price::new(100.0, 2))
740 .expire_time(expire_time)
741 .build();
742
743 assert_eq!(order.expire_time(), Some(expire_time));
745 }
746
747 #[rstest]
748 fn test_stop_market_order_trigger_instrument_id() {
749 let trigger_instrument_id = InstrumentId::from("ETH-USDT.BINANCE");
751 let order = OrderTestBuilder::new(OrderType::StopMarket)
752 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
753 .quantity(Quantity::from(10))
754 .trigger_price(Price::new(100.0, 2))
755 .trigger_instrument_id(trigger_instrument_id)
756 .build();
757
758 assert_eq!(order.trigger_instrument_id(), Some(trigger_instrument_id));
760 }
761
762 #[rstest]
763 fn test_stop_market_order_from_order_initialized() {
764 let order_initialized = OrderInitializedSpec::builder()
766 .order_type(OrderType::StopMarket)
767 .quantity(Quantity::from(10))
768 .trigger_price(Price::new(100.0, 2))
769 .trigger_type(TriggerType::Default)
770 .build();
771
772 let order: StopMarketOrder = order_initialized.clone().into();
774
775 assert_eq!(order.trader_id(), order_initialized.trader_id);
777 assert_eq!(order.strategy_id(), order_initialized.strategy_id);
778 assert_eq!(order.instrument_id(), order_initialized.instrument_id);
779 assert_eq!(order.client_order_id(), order_initialized.client_order_id);
780 assert_eq!(order.quantity(), order_initialized.quantity);
781 assert_eq!(order.trigger_price(), order_initialized.trigger_price);
782 assert_eq!(order.trigger_type(), order_initialized.trigger_type);
783 }
784
785 #[rstest]
786 fn test_stop_market_order_is_triggered() {
787 let order = OrderTestBuilder::new(OrderType::StopMarket)
789 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
790 .quantity(Quantity::from(10))
791 .trigger_price(Price::new(100.0, 2))
792 .build();
793
794 assert_eq!(order.is_triggered(), Some(false));
796 }
797
798 #[rstest]
799 fn test_stop_market_order_protection_price_update() {
800 let order = OrderTestBuilder::new(OrderType::StopMarket)
802 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
803 .quantity(Quantity::from(10))
804 .trigger_price(Price::new(100.0, 2))
805 .build();
806
807 let mut accepted_order = TestOrderStubs::make_accepted_order(&order);
808
809 let calculated_protection_price = Price::new(95.0, 2);
811
812 let event = OrderUpdated {
813 client_order_id: accepted_order.client_order_id(),
814 strategy_id: accepted_order.strategy_id(),
815 protection_price: Some(calculated_protection_price),
816 ..Default::default()
817 };
818
819 assert_eq!(accepted_order.price(), None);
820 assert!(!accepted_order.has_price());
821
822 accepted_order.apply(OrderEventAny::Updated(event)).unwrap();
823
824 assert_eq!(accepted_order.price(), Some(calculated_protection_price));
826 assert!(accepted_order.has_price());
827 }
828}