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