nautilus_model/events/order/spec/
initialized.rs1use indexmap::IndexMap;
17use nautilus_core::{UUID4, UnixNanos};
18use rust_decimal::Decimal;
19use ustr::Ustr;
20
21use crate::{
22 enums::{ContingencyType, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
23 events::OrderInitialized,
24 identifiers::{
25 ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
26 },
27 stubs::{TestDefault, test_uuid},
28 types::{Price, Quantity},
29};
30
31#[derive(Debug, Clone, bon::Builder)]
37#[builder(finish_fn = into_spec)]
38#[expect(
39 clippy::struct_excessive_bools,
40 reason = "spec mirrors `OrderInitialized` field set; bool count is fixed by the event"
41)]
42pub struct OrderInitializedSpec {
43 #[builder(default = TraderId::test_default())]
44 pub trader_id: TraderId,
45 #[builder(default = StrategyId::test_default())]
46 pub strategy_id: StrategyId,
47 #[builder(default = InstrumentId::test_default())]
48 pub instrument_id: InstrumentId,
49 #[builder(default = ClientOrderId::test_default())]
50 pub client_order_id: ClientOrderId,
51 #[builder(default = OrderSide::Buy)]
52 pub order_side: OrderSide,
53 #[builder(default = OrderType::Market)]
54 pub order_type: OrderType,
55 #[builder(default = Quantity::new(100_000.0, 0))]
56 pub quantity: Quantity,
57 #[builder(default = TimeInForce::Day)]
58 pub time_in_force: TimeInForce,
59 #[builder(default = false)]
60 pub post_only: bool,
61 #[builder(default = false)]
62 pub reduce_only: bool,
63 #[builder(default = false)]
64 pub quote_quantity: bool,
65 #[builder(default = false)]
66 pub reconciliation: bool,
67 #[builder(default = test_uuid())]
68 pub event_id: UUID4,
69 #[builder(default = UnixNanos::default())]
70 pub ts_event: UnixNanos,
71 #[builder(default = UnixNanos::default())]
72 pub ts_init: UnixNanos,
73 pub price: Option<Price>,
74 pub trigger_price: Option<Price>,
75 pub trigger_type: Option<TriggerType>,
76 pub limit_offset: Option<Decimal>,
77 pub trailing_offset: Option<Decimal>,
78 pub trailing_offset_type: Option<TrailingOffsetType>,
79 pub expire_time: Option<UnixNanos>,
80 pub display_qty: Option<Quantity>,
81 pub emulation_trigger: Option<TriggerType>,
82 pub trigger_instrument_id: Option<InstrumentId>,
83 pub contingency_type: Option<ContingencyType>,
84 pub order_list_id: Option<OrderListId>,
85 pub linked_order_ids: Option<Vec<ClientOrderId>>,
86 pub parent_order_id: Option<ClientOrderId>,
87 pub exec_algorithm_id: Option<ExecAlgorithmId>,
88 pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
89 pub exec_spawn_id: Option<ClientOrderId>,
90 pub tags: Option<Vec<Ustr>>,
91}
92
93impl<S: order_initialized_spec_builder::IsComplete> OrderInitializedSpecBuilder<S> {
94 #[must_use]
96 pub fn build(self) -> OrderInitialized {
97 let spec = self.into_spec();
98 OrderInitialized::new(
99 spec.trader_id,
100 spec.strategy_id,
101 spec.instrument_id,
102 spec.client_order_id,
103 spec.order_side,
104 spec.order_type,
105 spec.quantity,
106 spec.time_in_force,
107 spec.post_only,
108 spec.reduce_only,
109 spec.quote_quantity,
110 spec.reconciliation,
111 spec.event_id,
112 spec.ts_event,
113 spec.ts_init,
114 spec.price,
115 spec.trigger_price,
116 spec.trigger_type,
117 spec.limit_offset,
118 spec.trailing_offset,
119 spec.trailing_offset_type,
120 spec.expire_time,
121 spec.display_qty,
122 spec.emulation_trigger,
123 spec.trigger_instrument_id,
124 spec.contingency_type,
125 spec.order_list_id,
126 spec.linked_order_ids,
127 spec.parent_order_id,
128 spec.exec_algorithm_id,
129 spec.exec_algorithm_params,
130 spec.exec_spawn_id,
131 spec.tags,
132 )
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use rstest::rstest;
139
140 use super::*;
141 use crate::stubs::reset_test_uuid_rng;
142
143 #[rstest]
144 fn defaults_are_sensible() {
145 let order = OrderInitializedSpec::builder().build();
148 assert_eq!(order.trader_id, TraderId::test_default());
149 assert_eq!(order.strategy_id, StrategyId::test_default());
150 assert_eq!(order.instrument_id, InstrumentId::test_default());
151 assert_eq!(order.client_order_id, ClientOrderId::test_default());
152 assert_eq!(order.order_side, OrderSide::Buy);
153 assert_eq!(order.order_type, OrderType::Market);
154 assert_eq!(order.quantity, Quantity::new(100_000.0, 0));
155 assert_eq!(order.time_in_force, TimeInForce::Day);
156 assert!(!order.post_only);
157 assert!(!order.reduce_only);
158 assert!(!order.quote_quantity);
159 assert!(!order.reconciliation);
160 assert_eq!(order.ts_event, UnixNanos::default());
161 assert_eq!(order.ts_init, UnixNanos::default());
162 assert_eq!(order.price, None);
163 assert_eq!(order.trigger_price, None);
164 assert_eq!(order.trigger_type, None);
165 assert_eq!(order.limit_offset, None);
166 assert_eq!(order.trailing_offset, None);
167 assert_eq!(order.trailing_offset_type, None);
168 assert_eq!(order.expire_time, None);
169 assert_eq!(order.display_qty, None);
170 assert_eq!(order.emulation_trigger, None);
171 assert_eq!(order.trigger_instrument_id, None);
172 assert_eq!(order.contingency_type, None);
173 assert_eq!(order.order_list_id, None);
174 assert_eq!(order.linked_order_ids, None);
175 assert_eq!(order.parent_order_id, None);
176 assert_eq!(order.exec_algorithm_id, None);
177 assert_eq!(order.exec_algorithm_params, None);
178 assert_eq!(order.exec_spawn_id, None);
179 assert_eq!(order.tags, None);
180 }
181
182 #[rstest]
183 fn overrides_apply_through_constructor() {
184 let order = OrderInitializedSpec::builder()
185 .order_type(OrderType::Limit)
186 .order_side(OrderSide::Sell)
187 .quantity(Quantity::from("50"))
188 .price(Price::from("1.25000"))
189 .post_only(true)
190 .build();
191
192 assert_eq!(order.order_type, OrderType::Limit);
193 assert_eq!(order.order_side, OrderSide::Sell);
194 assert_eq!(order.quantity, Quantity::from("50"));
195 assert_eq!(order.price, Some(Price::from("1.25000")));
196 assert!(order.post_only);
197 assert_eq!(order.trader_id, TraderId::test_default());
198 }
199
200 #[rstest]
201 fn event_ids_are_unique_within_a_run() {
202 reset_test_uuid_rng();
203 let a = OrderInitializedSpec::builder().build();
204 let b = OrderInitializedSpec::builder().build();
205 let c = OrderInitializedSpec::builder().build();
206 assert_ne!(a.event_id, b.event_id);
207 assert_ne!(b.event_id, c.event_id);
208 assert_ne!(a.event_id, c.event_id);
209 }
210
211 #[rstest]
212 fn event_id_sequence_is_reproducible() {
213 reset_test_uuid_rng();
215 let first_run: Vec<_> = (0..3)
216 .map(|_| OrderInitializedSpec::builder().build().event_id)
217 .collect();
218
219 reset_test_uuid_rng();
220 let second_run: Vec<_> = (0..3)
221 .map(|_| OrderInitializedSpec::builder().build().event_id)
222 .collect();
223
224 assert_eq!(first_run, second_run);
225 }
226}