Skip to main content

nautilus_model/events/order/spec/
updated.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 nautilus_core::{UUID4, UnixNanos};
17
18use crate::{
19    events::OrderUpdated,
20    identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId},
21    stubs::{TestDefault, test_uuid},
22    types::{Price, Quantity},
23};
24
25/// Test-only fluent spec for [`OrderUpdated`].
26///
27/// All fields carry sensible defaults so callers only set what differs.
28/// `build()` constructs the event through [`OrderUpdated::new`] so any future invariants
29/// added to the production constructor are exercised by tests built on this spec.
30#[derive(Debug, Clone, bon::Builder)]
31#[builder(finish_fn = into_spec)]
32pub struct OrderUpdatedSpec {
33    #[builder(default = TraderId::test_default())]
34    pub trader_id: TraderId,
35    #[builder(default = StrategyId::test_default())]
36    pub strategy_id: StrategyId,
37    #[builder(default = InstrumentId::test_default())]
38    pub instrument_id: InstrumentId,
39    #[builder(default = ClientOrderId::test_default())]
40    pub client_order_id: ClientOrderId,
41    #[builder(default = Quantity::new(100_000.0, 0))]
42    pub quantity: Quantity,
43    #[builder(default = test_uuid())]
44    pub event_id: UUID4,
45    #[builder(default = UnixNanos::default())]
46    pub ts_event: UnixNanos,
47    #[builder(default = UnixNanos::default())]
48    pub ts_init: UnixNanos,
49    #[builder(default = false)]
50    pub reconciliation: bool,
51    pub venue_order_id: Option<VenueOrderId>,
52    pub account_id: Option<AccountId>,
53    pub price: Option<Price>,
54    pub trigger_price: Option<Price>,
55    pub protection_price: Option<Price>,
56    #[builder(default = false)]
57    pub is_quote_quantity: bool,
58}
59
60impl<S: order_updated_spec_builder::IsComplete> OrderUpdatedSpecBuilder<S> {
61    /// Builds the spec and constructs an [`OrderUpdated`] through its production constructor.
62    #[must_use]
63    pub fn build(self) -> OrderUpdated {
64        let spec = self.into_spec();
65        OrderUpdated::new(
66            spec.trader_id,
67            spec.strategy_id,
68            spec.instrument_id,
69            spec.client_order_id,
70            spec.quantity,
71            spec.event_id,
72            spec.ts_event,
73            spec.ts_init,
74            spec.reconciliation,
75            spec.venue_order_id,
76            spec.account_id,
77            spec.price,
78            spec.trigger_price,
79            spec.protection_price,
80            spec.is_quote_quantity,
81        )
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use rstest::rstest;
88
89    use super::*;
90    use crate::stubs::reset_test_uuid_rng;
91
92    #[rstest]
93    fn defaults_are_sensible() {
94        // Pin the spec's no-arg defaults so accidental drift in any individual default surfaces here,
95        // rather than as silent behavior change in downstream tests.
96        let event = OrderUpdatedSpec::builder().build();
97        assert_eq!(event.trader_id, TraderId::test_default());
98        assert_eq!(event.strategy_id, StrategyId::test_default());
99        assert_eq!(event.instrument_id, InstrumentId::test_default());
100        assert_eq!(event.client_order_id, ClientOrderId::test_default());
101        assert_eq!(event.quantity, Quantity::new(100_000.0, 0));
102        assert_eq!(event.ts_event, UnixNanos::default());
103        assert_eq!(event.ts_init, UnixNanos::default());
104        assert_eq!(event.reconciliation, 0);
105        assert_eq!(event.venue_order_id, None);
106        assert_eq!(event.account_id, None);
107        assert_eq!(event.price, None);
108        assert_eq!(event.trigger_price, None);
109        assert_eq!(event.protection_price, None);
110        assert!(!event.is_quote_quantity);
111    }
112
113    #[rstest]
114    fn overrides_apply_through_constructor() {
115        let event = OrderUpdatedSpec::builder()
116            .quantity(Quantity::from(50_000))
117            .price(Price::from("22000"))
118            .is_quote_quantity(true)
119            .build();
120
121        assert_eq!(event.quantity, Quantity::from(50_000));
122        assert_eq!(event.price, Some(Price::from("22000")));
123        assert!(event.is_quote_quantity);
124        assert_eq!(event.trader_id, TraderId::test_default());
125    }
126
127    #[rstest]
128    fn event_ids_are_unique_within_a_run() {
129        reset_test_uuid_rng();
130        let a = OrderUpdatedSpec::builder().build();
131        let b = OrderUpdatedSpec::builder().build();
132        let c = OrderUpdatedSpec::builder().build();
133        assert_ne!(a.event_id, b.event_id);
134        assert_ne!(b.event_id, c.event_id);
135        assert_ne!(a.event_id, c.event_id);
136    }
137
138    #[rstest]
139    fn event_id_sequence_is_reproducible() {
140        // Reset before each draw so the comparison is run-order independent.
141        reset_test_uuid_rng();
142        let first_run: Vec<_> = (0..3)
143            .map(|_| OrderUpdatedSpec::builder().build().event_id)
144            .collect();
145
146        reset_test_uuid_rng();
147        let second_run: Vec<_> = (0..3)
148            .map(|_| OrderUpdatedSpec::builder().build().event_id)
149            .collect();
150
151        assert_eq!(first_run, second_run);
152    }
153}