1use std::collections::HashMap;
17
18use arrow::{datatypes::Schema, error::ArrowError, record_batch::RecordBatch};
19use nautilus_model::events::{
20 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated, OrderExpired,
21 OrderFillVoided, OrderFilled, OrderInitialized, OrderModifyRejected, OrderPendingCancel,
22 OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted, OrderTriggered, OrderUpdated,
23};
24
25use super::{
26 ArrowSchemaProvider, DecodeTypedFromRecordBatch, EncodeToRecordBatch, EncodingError,
27 KEY_INSTRUMENT_ID,
28 json::{JsonFieldSpec, decode_batch, encode_batch, metadata_for_type, schema_for_type},
29};
30
31const ORDER_INITIALIZED_FIELDS: &[JsonFieldSpec] = &[
32 JsonFieldSpec::utf8("trader_id", false),
33 JsonFieldSpec::utf8("strategy_id", false),
34 JsonFieldSpec::utf8("instrument_id", false),
35 JsonFieldSpec::utf8("client_order_id", false),
36 JsonFieldSpec::utf8("order_side", false),
37 JsonFieldSpec::utf8("order_type", false),
38 JsonFieldSpec::utf8("quantity", false),
39 JsonFieldSpec::utf8("time_in_force", false),
40 JsonFieldSpec::boolean("post_only", false),
41 JsonFieldSpec::boolean("reduce_only", false),
42 JsonFieldSpec::boolean("quote_quantity", false),
43 JsonFieldSpec::boolean("reconciliation", false),
44 JsonFieldSpec::utf8("event_id", false),
45 JsonFieldSpec::u64("ts_event", false),
46 JsonFieldSpec::u64("ts_init", false),
47 JsonFieldSpec::utf8("price", true),
48 JsonFieldSpec::utf8("trigger_price", true),
49 JsonFieldSpec::utf8("trigger_type", true),
50 JsonFieldSpec::utf8("limit_offset", true),
51 JsonFieldSpec::utf8("trailing_offset", true),
52 JsonFieldSpec::utf8("trailing_offset_type", true),
53 JsonFieldSpec::u64("expire_time", true),
54 JsonFieldSpec::utf8("display_qty", true),
55 JsonFieldSpec::utf8("emulation_trigger", true),
56 JsonFieldSpec::utf8("trigger_instrument_id", true),
57 JsonFieldSpec::utf8("contingency_type", true),
58 JsonFieldSpec::utf8("order_list_id", true),
59 JsonFieldSpec::utf8_json("linked_order_ids", true),
60 JsonFieldSpec::utf8("parent_order_id", true),
61 JsonFieldSpec::utf8("exec_algorithm_id", true),
62 JsonFieldSpec::utf8_json("exec_algorithm_params", true),
63 JsonFieldSpec::utf8("exec_spawn_id", true),
64 JsonFieldSpec::utf8_json("tags", true),
65 JsonFieldSpec::utf8("activation_price", true),
68];
69
70const ORDER_DENIED_FIELDS: &[JsonFieldSpec] = &[
71 JsonFieldSpec::utf8("trader_id", false),
72 JsonFieldSpec::utf8("strategy_id", false),
73 JsonFieldSpec::utf8("instrument_id", false),
74 JsonFieldSpec::utf8("client_order_id", false),
75 JsonFieldSpec::utf8("reason", false),
76 JsonFieldSpec::utf8("event_id", false),
77 JsonFieldSpec::u64("ts_event", false),
78 JsonFieldSpec::u64("ts_init", false),
79];
80
81const ORDER_EMULATED_FIELDS: &[JsonFieldSpec] = &[
82 JsonFieldSpec::utf8("trader_id", false),
83 JsonFieldSpec::utf8("strategy_id", false),
84 JsonFieldSpec::utf8("instrument_id", false),
85 JsonFieldSpec::utf8("client_order_id", false),
86 JsonFieldSpec::utf8("event_id", false),
87 JsonFieldSpec::u64("ts_event", false),
88 JsonFieldSpec::u64("ts_init", false),
89];
90
91const ORDER_SUBMITTED_FIELDS: &[JsonFieldSpec] = &[
92 JsonFieldSpec::utf8("trader_id", false),
93 JsonFieldSpec::utf8("strategy_id", false),
94 JsonFieldSpec::utf8("instrument_id", false),
95 JsonFieldSpec::utf8("client_order_id", false),
96 JsonFieldSpec::utf8("account_id", false),
97 JsonFieldSpec::utf8("event_id", false),
98 JsonFieldSpec::u64("ts_event", false),
99 JsonFieldSpec::u64("ts_init", false),
100];
101
102const ORDER_ACCEPTED_FIELDS: &[JsonFieldSpec] = &[
103 JsonFieldSpec::utf8("trader_id", false),
104 JsonFieldSpec::utf8("strategy_id", false),
105 JsonFieldSpec::utf8("instrument_id", false),
106 JsonFieldSpec::utf8("client_order_id", false),
107 JsonFieldSpec::utf8("venue_order_id", false),
108 JsonFieldSpec::utf8("account_id", false),
109 JsonFieldSpec::utf8("event_id", false),
110 JsonFieldSpec::u64("ts_event", false),
111 JsonFieldSpec::u64("ts_init", false),
112 JsonFieldSpec::boolean("reconciliation", false),
113];
114
115const ORDER_REJECTED_FIELDS: &[JsonFieldSpec] = &[
116 JsonFieldSpec::utf8("trader_id", false),
117 JsonFieldSpec::utf8("strategy_id", false),
118 JsonFieldSpec::utf8("instrument_id", false),
119 JsonFieldSpec::utf8("client_order_id", false),
120 JsonFieldSpec::utf8("account_id", false),
121 JsonFieldSpec::utf8("reason", false),
122 JsonFieldSpec::utf8("event_id", false),
123 JsonFieldSpec::u64("ts_event", false),
124 JsonFieldSpec::u64("ts_init", false),
125 JsonFieldSpec::boolean("reconciliation", false),
126 JsonFieldSpec::boolean("due_post_only", false),
127];
128
129const ORDER_PENDING_CANCEL_FIELDS: &[JsonFieldSpec] = &[
130 JsonFieldSpec::utf8("trader_id", false),
131 JsonFieldSpec::utf8("strategy_id", false),
132 JsonFieldSpec::utf8("instrument_id", false),
133 JsonFieldSpec::utf8("client_order_id", false),
134 JsonFieldSpec::utf8("account_id", true),
135 JsonFieldSpec::utf8("event_id", false),
136 JsonFieldSpec::u64("ts_event", false),
137 JsonFieldSpec::u64("ts_init", false),
138 JsonFieldSpec::boolean("reconciliation", false),
139 JsonFieldSpec::utf8("venue_order_id", true),
140];
141
142const ORDER_CANCELED_FIELDS: &[JsonFieldSpec] = &[
143 JsonFieldSpec::utf8("trader_id", false),
144 JsonFieldSpec::utf8("strategy_id", false),
145 JsonFieldSpec::utf8("instrument_id", false),
146 JsonFieldSpec::utf8("client_order_id", false),
147 JsonFieldSpec::utf8("event_id", false),
148 JsonFieldSpec::u64("ts_event", false),
149 JsonFieldSpec::u64("ts_init", false),
150 JsonFieldSpec::boolean("reconciliation", false),
151 JsonFieldSpec::utf8("venue_order_id", true),
152 JsonFieldSpec::utf8("account_id", true),
153];
154
155const ORDER_CANCEL_REJECTED_FIELDS: &[JsonFieldSpec] = &[
156 JsonFieldSpec::utf8("trader_id", false),
157 JsonFieldSpec::utf8("strategy_id", false),
158 JsonFieldSpec::utf8("instrument_id", false),
159 JsonFieldSpec::utf8("client_order_id", false),
160 JsonFieldSpec::utf8("reason", false),
161 JsonFieldSpec::utf8("event_id", false),
162 JsonFieldSpec::u64("ts_event", false),
163 JsonFieldSpec::u64("ts_init", false),
164 JsonFieldSpec::boolean("reconciliation", false),
165 JsonFieldSpec::utf8("venue_order_id", true),
166 JsonFieldSpec::utf8("account_id", true),
167];
168
169const ORDER_EXPIRED_FIELDS: &[JsonFieldSpec] = &[
170 JsonFieldSpec::utf8("trader_id", false),
171 JsonFieldSpec::utf8("strategy_id", false),
172 JsonFieldSpec::utf8("instrument_id", false),
173 JsonFieldSpec::utf8("client_order_id", false),
174 JsonFieldSpec::utf8("event_id", false),
175 JsonFieldSpec::u64("ts_event", false),
176 JsonFieldSpec::u64("ts_init", false),
177 JsonFieldSpec::boolean("reconciliation", false),
178 JsonFieldSpec::utf8("venue_order_id", true),
179 JsonFieldSpec::utf8("account_id", true),
180];
181
182const ORDER_TRIGGERED_FIELDS: &[JsonFieldSpec] = &[
183 JsonFieldSpec::utf8("trader_id", false),
184 JsonFieldSpec::utf8("strategy_id", false),
185 JsonFieldSpec::utf8("instrument_id", false),
186 JsonFieldSpec::utf8("client_order_id", false),
187 JsonFieldSpec::utf8("event_id", false),
188 JsonFieldSpec::u64("ts_event", false),
189 JsonFieldSpec::u64("ts_init", false),
190 JsonFieldSpec::boolean("reconciliation", false),
191 JsonFieldSpec::utf8("venue_order_id", true),
192 JsonFieldSpec::utf8("account_id", true),
193];
194
195const ORDER_PENDING_UPDATE_FIELDS: &[JsonFieldSpec] = &[
196 JsonFieldSpec::utf8("trader_id", false),
197 JsonFieldSpec::utf8("strategy_id", false),
198 JsonFieldSpec::utf8("instrument_id", false),
199 JsonFieldSpec::utf8("client_order_id", false),
200 JsonFieldSpec::utf8("account_id", true),
201 JsonFieldSpec::utf8("event_id", false),
202 JsonFieldSpec::u64("ts_event", false),
203 JsonFieldSpec::u64("ts_init", false),
204 JsonFieldSpec::boolean("reconciliation", false),
205 JsonFieldSpec::utf8("venue_order_id", true),
206];
207
208const ORDER_RELEASED_FIELDS: &[JsonFieldSpec] = &[
209 JsonFieldSpec::utf8("trader_id", false),
210 JsonFieldSpec::utf8("strategy_id", false),
211 JsonFieldSpec::utf8("instrument_id", false),
212 JsonFieldSpec::utf8("client_order_id", false),
213 JsonFieldSpec::utf8("released_price", false),
214 JsonFieldSpec::utf8("event_id", false),
215 JsonFieldSpec::u64("ts_event", false),
216 JsonFieldSpec::u64("ts_init", false),
217];
218
219const ORDER_MODIFY_REJECTED_FIELDS: &[JsonFieldSpec] = &[
220 JsonFieldSpec::utf8("trader_id", false),
221 JsonFieldSpec::utf8("strategy_id", false),
222 JsonFieldSpec::utf8("instrument_id", false),
223 JsonFieldSpec::utf8("client_order_id", false),
224 JsonFieldSpec::utf8("reason", false),
225 JsonFieldSpec::utf8("event_id", false),
226 JsonFieldSpec::u64("ts_event", false),
227 JsonFieldSpec::u64("ts_init", false),
228 JsonFieldSpec::boolean("reconciliation", false),
229 JsonFieldSpec::utf8("venue_order_id", true),
230 JsonFieldSpec::utf8("account_id", true),
231];
232
233const ORDER_UPDATED_FIELDS: &[JsonFieldSpec] = &[
234 JsonFieldSpec::utf8("trader_id", false),
235 JsonFieldSpec::utf8("strategy_id", false),
236 JsonFieldSpec::utf8("instrument_id", false),
237 JsonFieldSpec::utf8("client_order_id", false),
238 JsonFieldSpec::utf8("venue_order_id", true),
239 JsonFieldSpec::utf8("account_id", true),
240 JsonFieldSpec::utf8("quantity", false),
241 JsonFieldSpec::utf8("price", true),
242 JsonFieldSpec::utf8("trigger_price", true),
243 JsonFieldSpec::utf8("protection_price", true),
244 JsonFieldSpec::boolean("is_quote_quantity", false),
245 JsonFieldSpec::utf8("event_id", false),
246 JsonFieldSpec::u64("ts_event", false),
247 JsonFieldSpec::u64("ts_init", false),
248 JsonFieldSpec::boolean("reconciliation", false),
249];
250
251const ORDER_FILLED_FIELDS: &[JsonFieldSpec] = &[
252 JsonFieldSpec::utf8("trader_id", false),
253 JsonFieldSpec::utf8("strategy_id", false),
254 JsonFieldSpec::utf8("instrument_id", false),
255 JsonFieldSpec::utf8("client_order_id", false),
256 JsonFieldSpec::utf8("venue_order_id", false),
257 JsonFieldSpec::utf8("account_id", false),
258 JsonFieldSpec::utf8("trade_id", false),
259 JsonFieldSpec::utf8("order_side", false),
260 JsonFieldSpec::utf8("order_type", false),
261 JsonFieldSpec::utf8("last_qty", false),
262 JsonFieldSpec::utf8("last_px", false),
263 JsonFieldSpec::utf8("currency", false),
264 JsonFieldSpec::utf8("liquidity_side", false),
265 JsonFieldSpec::utf8("event_id", false),
266 JsonFieldSpec::u64("ts_event", false),
267 JsonFieldSpec::u64("ts_init", false),
268 JsonFieldSpec::boolean("reconciliation", false),
269 JsonFieldSpec::utf8("position_id", true),
270 JsonFieldSpec::utf8("commission", true),
271 JsonFieldSpec::utf8_json("info", true),
272];
273
274const ORDER_FILL_VOIDED_FIELDS: &[JsonFieldSpec] = &[
275 JsonFieldSpec::utf8("trader_id", false),
276 JsonFieldSpec::utf8("strategy_id", false),
277 JsonFieldSpec::utf8("instrument_id", false),
278 JsonFieldSpec::utf8("client_order_id", false),
279 JsonFieldSpec::utf8("venue_order_id", false),
280 JsonFieldSpec::utf8("account_id", false),
281 JsonFieldSpec::utf8("correction_id", false),
282 JsonFieldSpec::utf8("trade_id", false),
283 JsonFieldSpec::utf8("voided_qty", false),
284 JsonFieldSpec::utf8("commission_voided", true),
285 JsonFieldSpec::utf8("order_side", false),
286 JsonFieldSpec::utf8("order_type", false),
287 JsonFieldSpec::utf8("last_px", false),
288 JsonFieldSpec::utf8("currency", false),
289 JsonFieldSpec::utf8("liquidity_side", false),
290 JsonFieldSpec::utf8("position_id", true),
291 JsonFieldSpec::utf8("reason", true),
292 JsonFieldSpec::utf8_json("info", true),
293 JsonFieldSpec::utf8("event_id", false),
294 JsonFieldSpec::u64("ts_event", false),
295 JsonFieldSpec::u64("ts_init", false),
296 JsonFieldSpec::boolean("reconciliation", false),
297 JsonFieldSpec::boolean("is_reopened", false),
298 JsonFieldSpec::utf8("causation_id", true),
299];
300
301fn instrument_metadata(type_name: &'static str, instrument_id: &str) -> HashMap<String, String> {
302 let mut metadata = metadata_for_type(type_name);
303 metadata.insert(KEY_INSTRUMENT_ID.to_string(), instrument_id.to_string());
304 metadata
305}
306
307macro_rules! impl_order_event_arrow {
308 ($type:ty, $type_name:expr, $fields:expr) => {
309 impl ArrowSchemaProvider for $type {
310 fn get_schema(metadata: Option<HashMap<String, String>>) -> Schema {
311 schema_for_type($type_name, metadata, $fields)
312 }
313 }
314
315 impl EncodeToRecordBatch for $type {
316 fn encode_batch(
317 metadata: &HashMap<String, String>,
318 data: &[Self],
319 ) -> Result<RecordBatch, ArrowError> {
320 encode_batch($type_name, metadata, data, $fields)
321 }
322
323 fn metadata(&self) -> HashMap<String, String> {
324 instrument_metadata($type_name, &self.instrument_id.to_string())
325 }
326 }
327
328 impl DecodeTypedFromRecordBatch for $type {
329 fn decode_typed_batch(
330 metadata: &HashMap<String, String>,
331 record_batch: RecordBatch,
332 ) -> Result<Vec<Self>, EncodingError> {
333 decode_batch(metadata, &record_batch, $fields, Some($type_name))
334 }
335 }
336 };
337}
338
339impl_order_event_arrow!(
340 OrderInitialized,
341 "OrderInitialized",
342 ORDER_INITIALIZED_FIELDS
343);
344impl_order_event_arrow!(OrderDenied, "OrderDenied", ORDER_DENIED_FIELDS);
345impl_order_event_arrow!(OrderEmulated, "OrderEmulated", ORDER_EMULATED_FIELDS);
346impl_order_event_arrow!(OrderSubmitted, "OrderSubmitted", ORDER_SUBMITTED_FIELDS);
347impl_order_event_arrow!(OrderAccepted, "OrderAccepted", ORDER_ACCEPTED_FIELDS);
348impl_order_event_arrow!(OrderRejected, "OrderRejected", ORDER_REJECTED_FIELDS);
349impl_order_event_arrow!(
350 OrderPendingCancel,
351 "OrderPendingCancel",
352 ORDER_PENDING_CANCEL_FIELDS
353);
354impl_order_event_arrow!(OrderCanceled, "OrderCanceled", ORDER_CANCELED_FIELDS);
355impl_order_event_arrow!(
356 OrderCancelRejected,
357 "OrderCancelRejected",
358 ORDER_CANCEL_REJECTED_FIELDS
359);
360impl_order_event_arrow!(OrderExpired, "OrderExpired", ORDER_EXPIRED_FIELDS);
361impl_order_event_arrow!(OrderTriggered, "OrderTriggered", ORDER_TRIGGERED_FIELDS);
362impl_order_event_arrow!(
363 OrderPendingUpdate,
364 "OrderPendingUpdate",
365 ORDER_PENDING_UPDATE_FIELDS
366);
367impl_order_event_arrow!(OrderReleased, "OrderReleased", ORDER_RELEASED_FIELDS);
368impl_order_event_arrow!(
369 OrderModifyRejected,
370 "OrderModifyRejected",
371 ORDER_MODIFY_REJECTED_FIELDS
372);
373impl_order_event_arrow!(OrderUpdated, "OrderUpdated", ORDER_UPDATED_FIELDS);
374impl_order_event_arrow!(OrderFilled, "OrderFilled", ORDER_FILLED_FIELDS);
375impl_order_event_arrow!(OrderFillVoided, "OrderFillVoided", ORDER_FILL_VOIDED_FIELDS);
376
377#[cfg(test)]
378mod tests {
379 use std::str::FromStr;
380
381 use indexmap::IndexMap;
382 use nautilus_core::UUID4;
383 use nautilus_model::{
384 events::order::{
385 spec::OrderFillVoidedSpec,
386 stubs::{
387 order_accepted, order_cancel_rejected, order_denied_max_submitted_rate,
388 order_emulated, order_expired, order_filled, order_initialized_buy_limit,
389 order_modify_rejected, order_pending_cancel, order_pending_update,
390 order_rejected_insufficient_margin, order_released, order_submitted,
391 order_triggered, order_updated,
392 },
393 },
394 identifiers::PositionId,
395 types::{Money, Quantity},
396 };
397 use rstest::rstest;
398 use rust_decimal::Decimal;
399 use ustr::Ustr;
400
401 use super::*;
402
403 #[rstest]
404 fn test_order_initialized_round_trip(order_initialized_buy_limit: OrderInitialized) {
405 let event = OrderInitialized {
406 limit_offset: Some(Decimal::from_str("0.123456789123456789").unwrap()),
407 trailing_offset: Some(Decimal::from_str("0.987654321987654321").unwrap()),
408 ..order_initialized_buy_limit
409 };
410 let metadata = event.metadata();
411 let batch =
412 OrderInitialized::encode_batch(&metadata, std::slice::from_ref(&event)).unwrap();
413 let decoded =
414 OrderInitialized::decode_typed_batch(batch.schema().metadata(), batch).unwrap();
415
416 assert_eq!(decoded, vec![event]);
417 }
418
419 #[rstest]
420 fn test_order_filled_round_trip(order_filled: OrderFilled) {
421 let event = order_filled;
422 let metadata = event.metadata();
423 let batch = OrderFilled::encode_batch(&metadata, std::slice::from_ref(&event)).unwrap();
424 let decoded = OrderFilled::decode_typed_batch(batch.schema().metadata(), batch).unwrap();
425
426 assert_eq!(decoded, vec![event]);
427 }
428
429 #[rstest]
430 fn test_order_fill_voided_round_trip() {
431 roundtrip(OrderFillVoidedSpec::builder().is_reopened(true).build());
432 }
433
434 #[rstest]
435 fn test_order_fill_voided_populated_optionals_round_trip() {
436 let mut event = OrderFillVoidedSpec::builder()
437 .voided_qty(Quantity::from("0.561000"))
438 .commission_voided(Money::from("12.20000000 USDT"))
439 .position_id(PositionId::from("P-001"))
440 .reason(Ustr::from("VENUE_VOID"))
441 .info(IndexMap::from([(Ustr::from("source"), Ustr::from("test"))]))
442 .is_reopened(true)
443 .build();
444 event.causation_id = Some(UUID4::new());
445
446 roundtrip(event);
447 }
448
449 fn roundtrip<T>(event: T)
450 where
451 T: ArrowSchemaProvider
452 + EncodeToRecordBatch
453 + DecodeTypedFromRecordBatch
454 + Clone
455 + PartialEq
456 + std::fmt::Debug,
457 {
458 let metadata = event.metadata();
459 let batch = T::encode_batch(&metadata, std::slice::from_ref(&event)).unwrap();
460 let decoded = T::decode_typed_batch(batch.schema().metadata(), batch).unwrap();
461 assert_eq!(decoded, vec![event]);
462 }
463
464 #[rstest]
465 fn test_order_denied_round_trip(order_denied_max_submitted_rate: OrderDenied) {
466 roundtrip(order_denied_max_submitted_rate);
467 }
468
469 #[rstest]
470 fn test_order_submitted_round_trip(order_submitted: OrderSubmitted) {
471 roundtrip(order_submitted);
472 }
473
474 #[rstest]
475 fn test_order_accepted_round_trip(order_accepted: OrderAccepted) {
476 roundtrip(order_accepted);
477 }
478
479 #[rstest]
480 fn test_order_rejected_round_trip(order_rejected_insufficient_margin: OrderRejected) {
481 roundtrip(order_rejected_insufficient_margin);
482 }
483
484 #[rstest]
485 fn test_order_canceled_round_trip() {
486 use nautilus_model::events::OrderCanceled;
487 roundtrip(OrderCanceled::default());
488 }
489
490 #[rstest]
491 fn test_order_updated_round_trip(order_updated: OrderUpdated) {
492 roundtrip(order_updated);
493 }
494
495 #[rstest]
496 fn test_order_triggered_round_trip(order_triggered: OrderTriggered) {
497 roundtrip(order_triggered);
498 }
499
500 #[rstest]
501 fn test_order_expired_round_trip(order_expired: OrderExpired) {
502 roundtrip(order_expired);
503 }
504
505 #[rstest]
506 fn test_order_pending_update_round_trip(order_pending_update: OrderPendingUpdate) {
507 roundtrip(order_pending_update);
508 }
509
510 #[rstest]
511 fn test_order_pending_update_none_account_round_trip(order_pending_update: OrderPendingUpdate) {
512 roundtrip(OrderPendingUpdate {
513 account_id: None,
514 ..order_pending_update
515 });
516 }
517
518 #[rstest]
519 fn test_order_pending_cancel_round_trip(order_pending_cancel: OrderPendingCancel) {
520 roundtrip(order_pending_cancel);
521 }
522
523 #[rstest]
524 fn test_order_pending_cancel_none_account_round_trip(order_pending_cancel: OrderPendingCancel) {
525 roundtrip(OrderPendingCancel {
526 account_id: None,
527 ..order_pending_cancel
528 });
529 }
530
531 #[rstest]
532 fn test_order_cancel_rejected_round_trip(order_cancel_rejected: OrderCancelRejected) {
533 roundtrip(order_cancel_rejected);
534 }
535
536 #[rstest]
537 fn test_order_modify_rejected_round_trip(order_modify_rejected: OrderModifyRejected) {
538 roundtrip(order_modify_rejected);
539 }
540
541 #[rstest]
542 fn test_order_emulated_round_trip(order_emulated: OrderEmulated) {
543 roundtrip(order_emulated);
544 }
545
546 #[rstest]
547 fn test_order_released_round_trip(order_released: OrderReleased) {
548 roundtrip(order_released);
549 }
550}