Skip to main content

nautilus_model/events/order/
initialized.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::fmt::{Debug, Display};
17
18use indexmap::IndexMap;
19use nautilus_core::{
20    UUID4, UnixNanos,
21    correctness::{FAILED, check_predicate_false},
22};
23use rust_decimal::Decimal;
24use serde::{Deserialize, Serialize};
25use ustr::Ustr;
26
27use crate::{
28    enums::{
29        ContingencyType, LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType,
30        TriggerType,
31    },
32    events::OrderEvent,
33    identifiers::{
34        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
35        StrategyId, TradeId, TraderId, VenueOrderId,
36    },
37    orders::{OrderAny, OrderError},
38    types::{Currency, Money, Price, Quantity},
39};
40
41/// Represents an event where an order has been initialized.
42///
43/// This is a seed event which can instantiate any order through a creation
44/// method. This event should contain enough information to be able to send it
45/// 'over the wire' and have a valid order created with exactly the same
46/// properties as if it had been instantiated locally.
47#[repr(C)]
48#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(tag = "type")]
50#[cfg_attr(
51    feature = "python",
52    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
53)]
54#[cfg_attr(
55    feature = "python",
56    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
57)]
58pub struct OrderInitialized {
59    /// The trader ID associated with the event.
60    pub trader_id: TraderId,
61    /// The strategy ID associated with the event.
62    pub strategy_id: StrategyId,
63    /// The instrument ID associated with the event.
64    pub instrument_id: InstrumentId,
65    /// The client order ID associated with the event.
66    pub client_order_id: ClientOrderId,
67    /// The order side.
68    pub order_side: OrderSide,
69    /// The order type.
70    pub order_type: OrderType,
71    /// The order quantity.
72    pub quantity: Quantity,
73    /// The order time in force.
74    pub time_in_force: TimeInForce,
75    /// If the order will only provide liquidity (make a market).
76    pub post_only: bool,
77    /// If the order carries the 'reduce-only' execution instruction.
78    pub reduce_only: bool,
79    /// If the order quantity is denominated in the quote currency.
80    pub quote_quantity: bool,
81    /// If the event was generated during reconciliation.
82    pub reconciliation: bool,
83    /// The unique identifier for the event.
84    pub event_id: UUID4,
85    /// UNIX timestamp (nanoseconds) when the event occurred.
86    pub ts_event: UnixNanos,
87    /// UNIX timestamp (nanoseconds) when the event was initialized.
88    pub ts_init: UnixNanos,
89    /// The order price (LIMIT).
90    pub price: Option<Price>,
91    /// The order activation price for trailing-stop orders.
92    pub activation_price: Option<Price>,
93    /// The order trigger price (STOP).
94    pub trigger_price: Option<Price>,
95    /// The trigger type for the order.
96    #[serde(default, with = "crate::enums::serde_option_trigger_type")]
97    pub trigger_type: Option<TriggerType>,
98    /// The trailing offset for the orders limit price.
99    pub limit_offset: Option<Decimal>,
100    /// The trailing offset for the orders trigger price (STOP).
101    pub trailing_offset: Option<Decimal>,
102    /// The trailing offset type.
103    #[serde(default, with = "crate::enums::serde_option_trailing_offset_type")]
104    pub trailing_offset_type: Option<TrailingOffsetType>,
105    /// The order expiration, `None` for no expiration.
106    pub expire_time: Option<UnixNanos>,
107    /// The quantity of the `LIMIT` order to display on the public book (iceberg).
108    pub display_qty: Option<Quantity>,
109    /// The emulation trigger type for the order.
110    #[serde(default, with = "crate::enums::serde_option_trigger_type")]
111    pub emulation_trigger: Option<TriggerType>,
112    /// The emulation trigger instrument ID for the order (if `None` then will be the `instrument_id`).
113    pub trigger_instrument_id: Option<InstrumentId>,
114    /// The order contingency type.
115    #[serde(default, with = "crate::enums::serde_option_contingency_type")]
116    pub contingency_type: Option<ContingencyType>,
117    /// The order list ID associated with the order.
118    pub order_list_id: Option<OrderListId>,
119    ///  The order linked client order ID(s).
120    pub linked_order_ids: Option<Vec<ClientOrderId>>,
121    /// The orders parent client order ID.
122    pub parent_order_id: Option<ClientOrderId>,
123    /// The execution algorithm ID for the order.
124    pub exec_algorithm_id: Option<ExecAlgorithmId>,
125    /// The execution algorithm parameters for the order.
126    pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
127    /// The execution algorithm spawning primary client order ID.
128    pub exec_spawn_id: Option<ClientOrderId>,
129    /// The custom user tags for the order.
130    pub tags: Option<Vec<Ustr>>,
131    /// The causation ID associated with the event.
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub causation_id: Option<UUID4>,
134}
135
136impl OrderInitialized {
137    /// Creates a new [`OrderInitialized`] instance with correctness checking.
138    ///
139    /// # Errors
140    ///
141    /// Returns an error if:
142    /// - A contingent order has no linked order IDs.
143    /// - An execution algorithm is set without an execution spawn ID.
144    #[expect(clippy::too_many_arguments)]
145    #[expect(
146        clippy::fn_params_excessive_bools,
147        reason = "domain event constructor requires multiple boolean flags"
148    )]
149    pub fn new_checked(
150        trader_id: TraderId,
151        strategy_id: StrategyId,
152        instrument_id: InstrumentId,
153        client_order_id: ClientOrderId,
154        order_side: OrderSide,
155        order_type: OrderType,
156        quantity: Quantity,
157        time_in_force: TimeInForce,
158        post_only: bool,
159        reduce_only: bool,
160        quote_quantity: bool,
161        reconciliation: bool,
162        event_id: UUID4,
163        ts_event: UnixNanos,
164        ts_init: UnixNanos,
165        price: Option<Price>,
166        activation_price: Option<Price>,
167        trigger_price: Option<Price>,
168        trigger_type: Option<TriggerType>,
169        limit_offset: Option<Decimal>,
170        trailing_offset: Option<Decimal>,
171        trailing_offset_type: Option<TrailingOffsetType>,
172        expire_time: Option<UnixNanos>,
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    ) -> Result<Self, OrderError> {
185        check_predicate_false(
186            contingency_type.is_some() && linked_order_ids.as_ref().is_none_or(Vec::is_empty),
187            "`linked_order_ids` is required for contingent orders",
188        )?;
189        check_predicate_false(
190            exec_algorithm_id.is_some() && exec_spawn_id.is_none(),
191            "`exec_spawn_id` is required when `exec_algorithm_id` is set",
192        )?;
193
194        Ok(Self {
195            trader_id,
196            strategy_id,
197            instrument_id,
198            client_order_id,
199            order_side,
200            order_type,
201            quantity,
202            time_in_force,
203            post_only,
204            reduce_only,
205            quote_quantity,
206            reconciliation,
207            event_id,
208            ts_event,
209            ts_init,
210            price,
211            activation_price,
212            trigger_price,
213            trigger_type,
214            limit_offset,
215            trailing_offset,
216            trailing_offset_type,
217            expire_time,
218            display_qty,
219            emulation_trigger,
220            trigger_instrument_id,
221            contingency_type,
222            order_list_id,
223            linked_order_ids,
224            parent_order_id,
225            exec_algorithm_id,
226            exec_algorithm_params,
227            exec_spawn_id,
228            tags,
229            causation_id: None,
230        })
231    }
232
233    /// Creates a new [`OrderInitialized`] instance.
234    ///
235    /// # Panics
236    ///
237    /// Panics if any order metadata validation fails (see
238    /// [`OrderInitialized::new_checked`]).
239    #[expect(clippy::too_many_arguments)]
240    #[expect(
241        clippy::fn_params_excessive_bools,
242        reason = "domain event constructor requires multiple boolean flags"
243    )]
244    #[must_use]
245    pub fn new(
246        trader_id: TraderId,
247        strategy_id: StrategyId,
248        instrument_id: InstrumentId,
249        client_order_id: ClientOrderId,
250        order_side: OrderSide,
251        order_type: OrderType,
252        quantity: Quantity,
253        time_in_force: TimeInForce,
254        post_only: bool,
255        reduce_only: bool,
256        quote_quantity: bool,
257        reconciliation: bool,
258        event_id: UUID4,
259        ts_event: UnixNanos,
260        ts_init: UnixNanos,
261        price: Option<Price>,
262        activation_price: Option<Price>,
263        trigger_price: Option<Price>,
264        trigger_type: Option<TriggerType>,
265        limit_offset: Option<Decimal>,
266        trailing_offset: Option<Decimal>,
267        trailing_offset_type: Option<TrailingOffsetType>,
268        expire_time: Option<UnixNanos>,
269        display_qty: Option<Quantity>,
270        emulation_trigger: Option<TriggerType>,
271        trigger_instrument_id: Option<InstrumentId>,
272        contingency_type: Option<ContingencyType>,
273        order_list_id: Option<OrderListId>,
274        linked_order_ids: Option<Vec<ClientOrderId>>,
275        parent_order_id: Option<ClientOrderId>,
276        exec_algorithm_id: Option<ExecAlgorithmId>,
277        exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
278        exec_spawn_id: Option<ClientOrderId>,
279        tags: Option<Vec<Ustr>>,
280    ) -> Self {
281        Self::new_checked(
282            trader_id,
283            strategy_id,
284            instrument_id,
285            client_order_id,
286            order_side,
287            order_type,
288            quantity,
289            time_in_force,
290            post_only,
291            reduce_only,
292            quote_quantity,
293            reconciliation,
294            event_id,
295            ts_event,
296            ts_init,
297            price,
298            activation_price,
299            trigger_price,
300            trigger_type,
301            limit_offset,
302            trailing_offset,
303            trailing_offset_type,
304            expire_time,
305            display_qty,
306            emulation_trigger,
307            trigger_instrument_id,
308            contingency_type,
309            order_list_id,
310            linked_order_ids,
311            parent_order_id,
312            exec_algorithm_id,
313            exec_algorithm_params,
314            exec_spawn_id,
315            tags,
316        )
317        .unwrap_or_else(|e| panic!("{FAILED}: {e}"))
318    }
319}
320
321impl Debug for OrderInitialized {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        write!(
324            f,
325            "{}(\
326            trader_id={}, \
327            strategy_id={}, \
328            instrument_id={}, \
329            client_order_id={}, \
330            side={}, \
331            type={}, \
332            quantity={}, \
333            time_in_force={}, \
334            post_only={}, \
335            reduce_only={}, \
336            quote_quantity={}, \
337            price={}, \
338            emulation_trigger={}, \
339            trigger_instrument_id={}, \
340            contingency_type={}, \
341            order_list_id={}, \
342            linked_order_ids=[{}], \
343            parent_order_id={}, \
344            exec_algorithm_id={}, \
345            exec_algorithm_params={}, \
346            exec_spawn_id={}, \
347            tags={}, \
348            event_id={}, \
349            ts_init={})",
350            stringify!(OrderInitialized),
351            self.trader_id,
352            self.strategy_id,
353            self.instrument_id,
354            self.client_order_id,
355            self.order_side,
356            self.order_type,
357            self.quantity,
358            self.time_in_force,
359            self.post_only,
360            self.reduce_only,
361            self.quote_quantity,
362            self.price
363                .map_or("None".to_string(), |price| format!("{price}")),
364            self.emulation_trigger
365                .map_or("None".to_string(), |trigger| format!("{trigger}")),
366            self.trigger_instrument_id
367                .map_or("None".to_string(), |instrument_id| format!(
368                    "{instrument_id}"
369                )),
370            self.contingency_type
371                .map_or("None".to_string(), |contingency_type| format!(
372                    "{contingency_type}"
373                )),
374            self.order_list_id
375                .map_or("None".to_string(), |order_list_id| format!(
376                    "{order_list_id}"
377                )),
378            self.linked_order_ids
379                .as_ref()
380                .map_or("None".to_string(), |linked_order_ids| linked_order_ids
381                    .iter()
382                    .map(ToString::to_string)
383                    .collect::<Vec<_>>()
384                    .join(", ")),
385            self.parent_order_id
386                .map_or("None".to_string(), |parent_order_id| format!(
387                    "{parent_order_id}"
388                )),
389            self.exec_algorithm_id
390                .map_or("None".to_string(), |exec_algorithm_id| format!(
391                    "{exec_algorithm_id}"
392                )),
393            self.exec_algorithm_params
394                .as_ref()
395                .map_or("None".to_string(), |exec_algorithm_params| format!(
396                    "{exec_algorithm_params:?}"
397                )),
398            self.exec_spawn_id
399                .map_or("None".to_string(), |exec_spawn_id| format!(
400                    "{exec_spawn_id}"
401                )),
402            self.tags.as_ref().map_or("None".to_string(), |tags| tags
403                .iter()
404                .map(ToString::to_string)
405                .collect::<Vec<String>>()
406                .join(", ")),
407            self.event_id,
408            self.ts_init
409        )
410    }
411}
412
413impl Display for OrderInitialized {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        write!(
416            f,
417            "{}(\
418            instrument_id={}, \
419            client_order_id={}, \
420            side={}, \
421            type={}, \
422            quantity={}, \
423            time_in_force={}, \
424            post_only={}, \
425            reduce_only={}, \
426            quote_quantity={}, \
427            price={}, \
428            emulation_trigger={}, \
429            trigger_instrument_id={}, \
430            contingency_type={}, \
431            order_list_id={}, \
432            linked_order_ids=[{}], \
433            parent_order_id={}, \
434            exec_algorithm_id={}, \
435            exec_algorithm_params={}, \
436            exec_spawn_id={}, \
437            tags={})",
438            stringify!(OrderInitialized),
439            self.instrument_id,
440            self.client_order_id,
441            self.order_side,
442            self.order_type,
443            self.quantity,
444            self.time_in_force,
445            self.post_only,
446            self.reduce_only,
447            self.quote_quantity,
448            self.price
449                .map_or("None".to_string(), |price| format!("{price}")),
450            self.emulation_trigger
451                .map_or("None".to_string(), |trigger| format!("{trigger}")),
452            self.trigger_instrument_id
453                .map_or("None".to_string(), |instrument_id| format!(
454                    "{instrument_id}"
455                )),
456            self.contingency_type
457                .map_or("None".to_string(), |contingency_type| format!(
458                    "{contingency_type}"
459                )),
460            self.order_list_id
461                .map_or("None".to_string(), |order_list_id| format!(
462                    "{order_list_id}"
463                )),
464            self.linked_order_ids
465                .as_ref()
466                .map_or("None".to_string(), |linked_order_ids| linked_order_ids
467                    .iter()
468                    .map(ToString::to_string)
469                    .collect::<Vec<_>>()
470                    .join(", ")),
471            self.parent_order_id
472                .map_or("None".to_string(), |parent_order_id| format!(
473                    "{parent_order_id}"
474                )),
475            self.exec_algorithm_id
476                .map_or("None".to_string(), |exec_algorithm_id| format!(
477                    "{exec_algorithm_id}"
478                )),
479            self.exec_algorithm_params
480                .as_ref()
481                .map_or("None".to_string(), |exec_algorithm_params| format!(
482                    "{exec_algorithm_params:?}"
483                )),
484            self.exec_spawn_id
485                .map_or("None".to_string(), |exec_spawn_id| format!(
486                    "{exec_spawn_id}"
487                )),
488            self.tags.as_ref().map_or("None".to_string(), |tags| tags
489                .iter()
490                .map(ToString::to_string)
491                .collect::<Vec<String>>()
492                .join(", ")),
493        )
494    }
495}
496
497impl OrderEvent for OrderInitialized {
498    fn id(&self) -> UUID4 {
499        self.event_id
500    }
501
502    fn type_name(&self) -> &'static str {
503        stringify!(OrderInitialized)
504    }
505
506    fn order_type(&self) -> Option<OrderType> {
507        Some(self.order_type)
508    }
509
510    fn order_side(&self) -> Option<OrderSide> {
511        Some(self.order_side)
512    }
513
514    fn trader_id(&self) -> TraderId {
515        self.trader_id
516    }
517
518    fn strategy_id(&self) -> StrategyId {
519        self.strategy_id
520    }
521
522    fn instrument_id(&self) -> InstrumentId {
523        self.instrument_id
524    }
525
526    fn trade_id(&self) -> Option<TradeId> {
527        None
528    }
529
530    fn currency(&self) -> Option<Currency> {
531        None
532    }
533
534    fn client_order_id(&self) -> ClientOrderId {
535        self.client_order_id
536    }
537
538    fn reason(&self) -> Option<Ustr> {
539        None
540    }
541
542    fn quantity(&self) -> Option<Quantity> {
543        Some(self.quantity)
544    }
545
546    fn time_in_force(&self) -> Option<TimeInForce> {
547        Some(self.time_in_force)
548    }
549
550    fn liquidity_side(&self) -> Option<LiquiditySide> {
551        None
552    }
553
554    fn post_only(&self) -> Option<bool> {
555        Some(self.post_only)
556    }
557
558    fn reduce_only(&self) -> Option<bool> {
559        Some(self.reduce_only)
560    }
561
562    fn quote_quantity(&self) -> Option<bool> {
563        Some(self.quote_quantity)
564    }
565
566    fn reconciliation(&self) -> bool {
567        false
568    }
569
570    fn price(&self) -> Option<Price> {
571        self.price
572    }
573
574    fn last_px(&self) -> Option<Price> {
575        None
576    }
577
578    fn last_qty(&self) -> Option<Quantity> {
579        None
580    }
581
582    fn activation_price(&self) -> Option<Price> {
583        self.activation_price
584    }
585
586    fn trigger_price(&self) -> Option<Price> {
587        self.trigger_price
588    }
589
590    fn trigger_type(&self) -> Option<TriggerType> {
591        self.trigger_type
592    }
593
594    fn limit_offset(&self) -> Option<Decimal> {
595        self.limit_offset
596    }
597
598    fn trailing_offset(&self) -> Option<Decimal> {
599        self.trailing_offset
600    }
601
602    fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
603        self.trailing_offset_type
604    }
605
606    fn expire_time(&self) -> Option<UnixNanos> {
607        self.expire_time
608    }
609
610    fn display_qty(&self) -> Option<Quantity> {
611        self.display_qty
612    }
613
614    fn emulation_trigger(&self) -> Option<TriggerType> {
615        self.emulation_trigger
616    }
617
618    fn trigger_instrument_id(&self) -> Option<InstrumentId> {
619        self.trigger_instrument_id
620    }
621
622    fn contingency_type(&self) -> Option<ContingencyType> {
623        self.contingency_type
624    }
625
626    fn order_list_id(&self) -> Option<OrderListId> {
627        self.order_list_id
628    }
629
630    fn linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
631        self.linked_order_ids.clone()
632    }
633
634    fn parent_order_id(&self) -> Option<ClientOrderId> {
635        self.parent_order_id
636    }
637
638    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
639        self.exec_algorithm_id
640    }
641
642    fn exec_algorithm_params(&self) -> Option<IndexMap<Ustr, Ustr>> {
643        self.exec_algorithm_params.clone()
644    }
645
646    fn exec_spawn_id(&self) -> Option<ClientOrderId> {
647        self.exec_spawn_id
648    }
649
650    fn tags(&self) -> Option<Vec<Ustr>> {
651        self.tags.clone()
652    }
653
654    fn venue_order_id(&self) -> Option<VenueOrderId> {
655        None
656    }
657
658    fn account_id(&self) -> Option<AccountId> {
659        None
660    }
661
662    fn position_id(&self) -> Option<PositionId> {
663        None
664    }
665
666    fn commission(&self) -> Option<Money> {
667        None
668    }
669
670    fn ts_event(&self) -> UnixNanos {
671        self.ts_event
672    }
673
674    fn ts_init(&self) -> UnixNanos {
675        self.ts_init
676    }
677}
678
679impl TryFrom<OrderInitialized> for OrderAny {
680    type Error = OrderError;
681
682    fn try_from(order: OrderInitialized) -> Result<Self, Self::Error> {
683        Ok(match order.order_type {
684            OrderType::Limit => Self::Limit(order.try_into()?),
685            OrderType::Market => Self::Market(order.try_into()?),
686            OrderType::StopMarket => Self::StopMarket(order.try_into()?),
687            OrderType::StopLimit => Self::StopLimit(order.try_into()?),
688            OrderType::LimitIfTouched => Self::LimitIfTouched(order.try_into()?),
689            OrderType::TrailingStopLimit => Self::TrailingStopLimit(order.try_into()?),
690            OrderType::TrailingStopMarket => Self::TrailingStopMarket(order.try_into()?),
691            OrderType::MarketToLimit => Self::MarketToLimit(order.try_into()?),
692            OrderType::MarketIfTouched => Self::MarketIfTouched(order.try_into()?),
693        })
694    }
695}
696
697#[cfg(test)]
698mod test {
699    use indexmap::IndexMap;
700    use rstest::rstest;
701    use ustr::Ustr;
702
703    use crate::events::{
704        OrderEvent,
705        order::{initialized::OrderInitialized, stubs::*},
706    };
707
708    #[rstest]
709    fn test_order_initialized(order_initialized_buy_limit: OrderInitialized) {
710        let display = format!("{order_initialized_buy_limit}");
711        assert_eq!(
712            display,
713            "OrderInitialized(instrument_id=BTCUSDT.COINBASE, client_order_id=O-19700101-000000-001-001-1, \
714            side=BUY, type=LIMIT, quantity=0.561, time_in_force=DAY, post_only=true, reduce_only=true, \
715            quote_quantity=false, price=22000, emulation_trigger=BID_ASK, trigger_instrument_id=BTCUSDT.COINBASE, \
716            contingency_type=OTO, order_list_id=1, linked_order_ids=[O-2020872378424], parent_order_id=None, \
717            exec_algorithm_id=None, exec_algorithm_params=None, exec_spawn_id=None, tags=None)"
718        );
719    }
720
721    #[rstest]
722    fn test_order_initialized_event_exposes_tags_and_exec_algorithm_params() {
723        let mut params = IndexMap::new();
724        params.insert(Ustr::from("speed"), Ustr::from("fast"));
725        let tags = vec![Ustr::from("tag-1"), Ustr::from("tag-2")];
726        let event = OrderInitialized {
727            exec_algorithm_params: Some(params.clone()),
728            tags: Some(tags.clone()),
729            ..OrderInitialized::default()
730        };
731
732        assert_eq!(OrderEvent::exec_algorithm_params(&event), Some(params));
733        assert_eq!(OrderEvent::tags(&event), Some(tags));
734    }
735
736    #[rstest]
737    fn test_order_initialized_serialization() {
738        let original = OrderInitialized::default();
739        let json = serde_json::to_string(&original).unwrap();
740        let deserialized: OrderInitialized = serde_json::from_str(&json).unwrap();
741        assert_eq!(original, deserialized);
742    }
743
744    #[rstest]
745    fn test_order_initialized_serialization_preserves_activation_price() {
746        use crate::types::Price;
747
748        let original = OrderInitialized {
749            activation_price: Some(Price::from("0.68500")),
750            ..OrderInitialized::default()
751        };
752        let json = serde_json::to_string(&original).unwrap();
753        let deserialized: OrderInitialized = serde_json::from_str(&json).unwrap();
754
755        assert_eq!(deserialized.activation_price, Some(Price::from("0.68500")));
756        assert_eq!(original, deserialized);
757    }
758}