1use std::fmt::Display;
17
18use enum_dispatch::enum_dispatch;
19use serde::{Deserialize, Serialize};
20
21use super::{
22 Order, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder, market::MarketOrder,
23 market_if_touched::MarketIfTouchedOrder, market_to_limit::MarketToLimitOrder,
24 stop_limit::StopLimitOrder, stop_market::StopMarketOrder,
25 trailing_stop_limit::TrailingStopLimitOrder, trailing_stop_market::TrailingStopMarketOrder,
26};
27use crate::{events::OrderEventAny, identifiers::OrderListId, types::Price};
28
29#[derive(Clone, Debug, Serialize, Deserialize)]
30#[enum_dispatch(Order)]
31pub enum OrderAny {
32 Limit(LimitOrder),
33 LimitIfTouched(LimitIfTouchedOrder),
34 Market(MarketOrder),
35 MarketIfTouched(MarketIfTouchedOrder),
36 MarketToLimit(MarketToLimitOrder),
37 StopLimit(StopLimitOrder),
38 StopMarket(StopMarketOrder),
39 TrailingStopLimit(TrailingStopLimitOrder),
40 TrailingStopMarket(TrailingStopMarketOrder),
41}
42
43impl OrderAny {
44 pub fn from_events(events: Vec<OrderEventAny>) -> anyhow::Result<Self> {
57 if events.is_empty() {
58 anyhow::bail!("No order events provided to create OrderAny");
59 }
60
61 let init_event = events.first().unwrap();
63 match init_event {
64 OrderEventAny::Initialized(init) => {
65 let mut order = Self::from(init.clone());
66 for event in events.into_iter().skip(1) {
68 order.apply(event)?;
70 }
71 Ok(order)
72 }
73 _ => {
74 anyhow::bail!("First event must be `OrderInitialized`");
75 }
76 }
77 }
78
79 #[must_use]
87 pub fn init_event(&self) -> &crate::events::OrderInitialized {
88 match self
90 .events()
91 .first()
92 .expect("Order invariant violated: no events")
93 {
94 OrderEventAny::Initialized(init) => init,
95 _ => panic!("Order invariant violated: first event must be OrderInitialized"),
96 }
97 }
98
99 pub fn set_order_list_id(&mut self, id: OrderListId) {
103 match self {
104 Self::Limit(o) => o.order_list_id = Some(id),
105 Self::LimitIfTouched(o) => o.order_list_id = Some(id),
106 Self::Market(o) => o.order_list_id = Some(id),
107 Self::MarketIfTouched(o) => o.order_list_id = Some(id),
108 Self::MarketToLimit(o) => o.order_list_id = Some(id),
109 Self::StopLimit(o) => o.order_list_id = Some(id),
110 Self::StopMarket(o) => o.order_list_id = Some(id),
111 Self::TrailingStopLimit(o) => o.order_list_id = Some(id),
112 Self::TrailingStopMarket(o) => o.order_list_id = Some(id),
113 }
114 }
115}
116
117impl PartialEq for OrderAny {
118 fn eq(&self, other: &Self) -> bool {
119 self.client_order_id() == other.client_order_id()
120 }
121}
122
123impl Eq for OrderAny {}
125
126impl Display for OrderAny {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 write!(
129 f,
130 "{}",
131 match self {
132 Self::Limit(order) => order.to_string(),
133 Self::LimitIfTouched(order) => order.to_string(),
134 Self::Market(order) => order.to_string(),
135 Self::MarketIfTouched(order) => order.to_string(),
136 Self::MarketToLimit(order) => order.to_string(),
137 Self::StopLimit(order) => order.to_string(),
138 Self::StopMarket(order) => order.to_string(),
139 Self::TrailingStopLimit(order) => order.to_string(),
140 Self::TrailingStopMarket(order) => order.to_string(),
141 }
142 )
143 }
144}
145
146impl TryFrom<OrderAny> for PassiveOrderAny {
147 type Error = String;
148
149 fn try_from(order: OrderAny) -> Result<Self, Self::Error> {
150 match order {
151 OrderAny::Limit(_) => Ok(Self::Limit(LimitOrderAny::try_from(order)?)),
152 OrderAny::LimitIfTouched(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
153 OrderAny::MarketIfTouched(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
154 OrderAny::StopLimit(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
155 OrderAny::StopMarket(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
156 OrderAny::TrailingStopLimit(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
157 OrderAny::TrailingStopMarket(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
158 OrderAny::MarketToLimit(_) => Ok(Self::Limit(LimitOrderAny::try_from(order)?)),
159 OrderAny::Market(_) => Ok(Self::Limit(LimitOrderAny::try_from(order)?)),
160 }
161 }
162}
163
164impl From<PassiveOrderAny> for OrderAny {
165 fn from(order: PassiveOrderAny) -> Self {
166 match order {
167 PassiveOrderAny::Limit(order) => order.into(),
168 PassiveOrderAny::Stop(order) => order.into(),
169 }
170 }
171}
172
173impl TryFrom<OrderAny> for StopOrderAny {
174 type Error = String;
175
176 fn try_from(order: OrderAny) -> Result<Self, Self::Error> {
177 match order {
178 OrderAny::LimitIfTouched(order) => Ok(Self::LimitIfTouched(order)),
179 OrderAny::MarketIfTouched(order) => Ok(Self::MarketIfTouched(order)),
180 OrderAny::StopLimit(order) => Ok(Self::StopLimit(order)),
181 OrderAny::StopMarket(order) => Ok(Self::StopMarket(order)),
182 OrderAny::TrailingStopLimit(order) => Ok(Self::TrailingStopLimit(order)),
183 OrderAny::TrailingStopMarket(order) => Ok(Self::TrailingStopMarket(order)),
184 _ => Err(format!(
185 "Cannot convert {:?} order to StopOrderAny: order type does not have a stop/trigger price",
186 order.order_type()
187 )),
188 }
189 }
190}
191
192impl From<StopOrderAny> for OrderAny {
193 fn from(order: StopOrderAny) -> Self {
194 match order {
195 StopOrderAny::LimitIfTouched(order) => Self::LimitIfTouched(order),
196 StopOrderAny::MarketIfTouched(order) => Self::MarketIfTouched(order),
197 StopOrderAny::StopLimit(order) => Self::StopLimit(order),
198 StopOrderAny::StopMarket(order) => Self::StopMarket(order),
199 StopOrderAny::TrailingStopLimit(order) => Self::TrailingStopLimit(order),
200 StopOrderAny::TrailingStopMarket(order) => Self::TrailingStopMarket(order),
201 }
202 }
203}
204
205impl TryFrom<OrderAny> for LimitOrderAny {
206 type Error = String;
207
208 fn try_from(order: OrderAny) -> Result<Self, Self::Error> {
209 match order {
210 OrderAny::Limit(order) => Ok(Self::Limit(order)),
211 OrderAny::MarketToLimit(order) => Ok(Self::MarketToLimit(order)),
212 OrderAny::StopLimit(order) => Ok(Self::StopLimit(order)),
213 OrderAny::TrailingStopLimit(order) => Ok(Self::TrailingStopLimit(order)),
214 OrderAny::Market(order) => Ok(Self::MarketOrderWithProtection(order)),
215 _ => Err(format!(
216 "Cannot convert {:?} order to LimitOrderAny: order type does not have a limit price",
217 order.order_type()
218 )),
219 }
220 }
221}
222
223impl From<LimitOrderAny> for OrderAny {
224 fn from(order: LimitOrderAny) -> Self {
225 match order {
226 LimitOrderAny::Limit(order) => Self::Limit(order),
227 LimitOrderAny::MarketToLimit(order) => Self::MarketToLimit(order),
228 LimitOrderAny::StopLimit(order) => Self::StopLimit(order),
229 LimitOrderAny::TrailingStopLimit(order) => Self::TrailingStopLimit(order),
230 LimitOrderAny::MarketOrderWithProtection(order) => Self::Market(order),
231 }
232 }
233}
234
235#[derive(Clone, Debug)]
236#[enum_dispatch(Order)]
237pub enum PassiveOrderAny {
238 Limit(LimitOrderAny),
239 Stop(StopOrderAny),
240}
241
242impl PassiveOrderAny {
243 #[must_use]
244 pub fn to_any(&self) -> OrderAny {
245 match self {
246 Self::Limit(order) => order.clone().into(),
247 Self::Stop(order) => order.clone().into(),
248 }
249 }
250}
251
252impl PartialEq for PassiveOrderAny {
254 fn eq(&self, rhs: &Self) -> bool {
255 match self {
256 Self::Limit(order) => order.client_order_id() == rhs.client_order_id(),
257 Self::Stop(order) => order.client_order_id() == rhs.client_order_id(),
258 }
259 }
260}
261
262#[derive(Clone, Debug)]
263#[enum_dispatch(Order)]
264pub enum LimitOrderAny {
265 Limit(LimitOrder),
266 MarketToLimit(MarketToLimitOrder),
267 StopLimit(StopLimitOrder),
268 TrailingStopLimit(TrailingStopLimitOrder),
269 MarketOrderWithProtection(MarketOrder),
270}
271
272impl LimitOrderAny {
273 #[must_use]
279 pub fn limit_px(&self) -> Price {
280 match self {
281 Self::Limit(order) => order.price,
282 Self::MarketToLimit(order) => order.price.expect("MarketToLimit order price not set"),
283 Self::StopLimit(order) => order.price,
284 Self::TrailingStopLimit(order) => order.price,
285 Self::MarketOrderWithProtection(order) => {
286 order.protection_price.expect("No price for order")
287 }
288 }
289 }
290}
291
292impl PartialEq for LimitOrderAny {
293 fn eq(&self, rhs: &Self) -> bool {
294 match self {
295 Self::Limit(order) => order.client_order_id == rhs.client_order_id(),
296 Self::MarketToLimit(order) => order.client_order_id == rhs.client_order_id(),
297 Self::StopLimit(order) => order.client_order_id == rhs.client_order_id(),
298 Self::TrailingStopLimit(order) => order.client_order_id == rhs.client_order_id(),
299 Self::MarketOrderWithProtection(order) => {
300 order.client_order_id == rhs.client_order_id()
301 }
302 }
303 }
304}
305
306#[derive(Clone, Debug)]
307#[enum_dispatch(Order)]
308pub enum StopOrderAny {
309 LimitIfTouched(LimitIfTouchedOrder),
310 MarketIfTouched(MarketIfTouchedOrder),
311 StopLimit(StopLimitOrder),
312 StopMarket(StopMarketOrder),
313 TrailingStopLimit(TrailingStopLimitOrder),
314 TrailingStopMarket(TrailingStopMarketOrder),
315}
316
317impl StopOrderAny {
318 #[must_use]
319 pub fn stop_px(&self) -> Price {
320 match self {
321 Self::LimitIfTouched(o) => o.trigger_price,
322 Self::MarketIfTouched(o) => o.trigger_price,
323 Self::StopLimit(o) => o.trigger_price,
324 Self::StopMarket(o) => o.trigger_price,
325 Self::TrailingStopLimit(o) => o.activation_price.unwrap_or(o.trigger_price),
326 Self::TrailingStopMarket(o) => o.activation_price.unwrap_or(o.trigger_price),
327 }
328 }
329}
330
331impl PartialEq for StopOrderAny {
333 fn eq(&self, rhs: &Self) -> bool {
334 match self {
335 Self::LimitIfTouched(order) => order.client_order_id == rhs.client_order_id(),
336 Self::StopLimit(order) => order.client_order_id == rhs.client_order_id(),
337 Self::StopMarket(order) => order.client_order_id == rhs.client_order_id(),
338 Self::MarketIfTouched(order) => order.client_order_id == rhs.client_order_id(),
339 Self::TrailingStopLimit(order) => order.client_order_id == rhs.client_order_id(),
340 Self::TrailingStopMarket(order) => order.client_order_id == rhs.client_order_id(),
341 }
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use rstest::rstest;
348 use rust_decimal::Decimal;
349 use rust_decimal_macros::dec;
350
351 use super::*;
352 use crate::{
353 enums::{OrderType, TrailingOffsetType},
354 events::{OrderEventAny, OrderUpdated, order::initialized::OrderInitializedBuilder},
355 identifiers::{ClientOrderId, InstrumentId, StrategyId},
356 orders::builder::OrderTestBuilder,
357 types::{Price, Quantity},
358 };
359
360 #[rstest]
361 fn test_order_any_equality() {
362 let client_order_id = ClientOrderId::from("ORDER-001");
364
365 let market_order = OrderTestBuilder::new(OrderType::Market)
366 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
367 .quantity(Quantity::from(10))
368 .client_order_id(client_order_id)
369 .build();
370
371 let limit_order = OrderTestBuilder::new(OrderType::Limit)
372 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
373 .quantity(Quantity::from(10))
374 .price(Price::new(100.0, 2))
375 .client_order_id(client_order_id)
376 .build();
377
378 assert_eq!(market_order, limit_order);
380 }
381
382 #[rstest]
383 fn test_order_any_conversion_from_events() {
384 let init_event = OrderInitializedBuilder::default()
386 .order_type(OrderType::Market)
387 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
388 .quantity(Quantity::from(10))
389 .build()
390 .unwrap();
391
392 let events = vec![OrderEventAny::Initialized(init_event.clone())];
394
395 let order = OrderAny::from_events(events).unwrap();
397
398 assert_eq!(order.order_type(), OrderType::Market);
400 assert_eq!(order.instrument_id(), init_event.instrument_id);
401 assert_eq!(order.quantity(), init_event.quantity);
402 }
403
404 #[rstest]
405 fn test_order_any_from_events_empty_error() {
406 let events: Vec<OrderEventAny> = vec![];
407 let result = OrderAny::from_events(events);
408
409 assert!(result.is_err());
410 assert_eq!(
411 result.unwrap_err().to_string(),
412 "No order events provided to create OrderAny"
413 );
414 }
415
416 #[rstest]
417 fn test_order_any_from_events_wrong_first_event() {
418 let client_order_id = ClientOrderId::from("ORDER-001");
420 let strategy_id = StrategyId::from("STRATEGY-001");
421
422 let update_event = OrderUpdated {
423 client_order_id,
424 strategy_id,
425 quantity: Quantity::from(20),
426 ..Default::default()
427 };
428
429 let events = vec![OrderEventAny::Updated(update_event)];
431
432 let result = OrderAny::from_events(events);
434 assert!(result.is_err());
435 assert_eq!(
436 result.unwrap_err().to_string(),
437 "First event must be `OrderInitialized`"
438 );
439 }
440
441 #[rstest]
442 fn test_passive_order_any_conversion() {
443 let limit_order = OrderTestBuilder::new(OrderType::Limit)
445 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
446 .quantity(Quantity::from(10))
447 .price(Price::new(100.0, 2))
448 .build();
449
450 let passive_order = PassiveOrderAny::try_from(limit_order).unwrap();
452 let order_any: OrderAny = passive_order.into();
453
454 assert_eq!(order_any.order_type(), OrderType::Limit);
456 assert_eq!(order_any.quantity(), Quantity::from(10));
457 }
458
459 #[rstest]
460 fn test_stop_order_any_conversion() {
461 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
463 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
464 .quantity(Quantity::from(10))
465 .trigger_price(Price::new(100.0, 2))
466 .build();
467
468 let stop_order_any = StopOrderAny::try_from(stop_order).unwrap();
470 let order_any: OrderAny = stop_order_any.into();
471
472 assert_eq!(order_any.order_type(), OrderType::StopMarket);
474 assert_eq!(order_any.quantity(), Quantity::from(10));
475 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
476 }
477
478 #[rstest]
479 fn test_limit_order_any_conversion() {
480 let limit_order = OrderTestBuilder::new(OrderType::Limit)
482 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
483 .quantity(Quantity::from(10))
484 .price(Price::new(100.0, 2))
485 .build();
486
487 let limit_order_any = LimitOrderAny::try_from(limit_order).unwrap();
489 let order_any: OrderAny = limit_order_any.into();
490
491 assert_eq!(order_any.order_type(), OrderType::Limit);
493 assert_eq!(order_any.quantity(), Quantity::from(10));
494 }
495
496 #[rstest]
497 fn test_limit_order_any_limit_price() {
498 let limit_order = OrderTestBuilder::new(OrderType::Limit)
500 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
501 .quantity(Quantity::from(10))
502 .price(Price::new(100.0, 2))
503 .build();
504
505 let limit_order_any = LimitOrderAny::try_from(limit_order).unwrap();
507
508 let limit_px = limit_order_any.limit_px();
510 assert_eq!(limit_px, Price::new(100.0, 2));
511 }
512
513 #[rstest]
514 fn test_stop_order_any_stop_price() {
515 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
517 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
518 .quantity(Quantity::from(10))
519 .trigger_price(Price::new(100.0, 2))
520 .build();
521
522 let stop_order_any = StopOrderAny::try_from(stop_order).unwrap();
524
525 let stop_px = stop_order_any.stop_px();
527 assert_eq!(stop_px, Price::new(100.0, 2));
528 }
529
530 #[rstest]
531 fn test_trailing_stop_market_order_conversion() {
532 let trailing_stop_order = OrderTestBuilder::new(OrderType::TrailingStopMarket)
534 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
535 .quantity(Quantity::from(10))
536 .trigger_price(Price::new(100.0, 2))
537 .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
539 .build();
540
541 let stop_order_any = StopOrderAny::try_from(trailing_stop_order).unwrap();
543
544 let order_any: OrderAny = stop_order_any.into();
546
547 assert_eq!(order_any.order_type(), OrderType::TrailingStopMarket);
549 assert_eq!(order_any.quantity(), Quantity::from(10));
550 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
551 assert_eq!(order_any.trailing_offset(), Some(dec!(0.5)));
552 assert_eq!(
553 order_any.trailing_offset_type(),
554 Some(TrailingOffsetType::NoTrailingOffset)
555 );
556 }
557
558 #[rstest]
559 fn test_trailing_stop_limit_order_conversion() {
560 let trailing_stop_limit = OrderTestBuilder::new(OrderType::TrailingStopLimit)
562 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
563 .quantity(Quantity::from(10))
564 .price(Price::new(99.0, 2))
565 .trigger_price(Price::new(100.0, 2))
566 .limit_offset(Decimal::new(10, 1)) .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
569 .build();
570
571 let limit_order_any = LimitOrderAny::try_from(trailing_stop_limit).unwrap();
573
574 assert_eq!(limit_order_any.limit_px(), Price::new(99.0, 2));
576
577 let order_any: OrderAny = limit_order_any.into();
579
580 assert_eq!(order_any.order_type(), OrderType::TrailingStopLimit);
582 assert_eq!(order_any.quantity(), Quantity::from(10));
583 assert_eq!(order_any.price(), Some(Price::new(99.0, 2)));
584 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
585 assert_eq!(order_any.trailing_offset(), Some(dec!(0.5)));
586 }
587
588 #[rstest]
589 fn test_passive_order_any_to_any() {
590 let limit_order = OrderTestBuilder::new(OrderType::Limit)
592 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
593 .quantity(Quantity::from(10))
594 .price(Price::new(100.0, 2))
595 .build();
596
597 let passive_order = PassiveOrderAny::try_from(limit_order).unwrap();
599
600 let order_any = passive_order.to_any();
602
603 assert_eq!(order_any.order_type(), OrderType::Limit);
605 assert_eq!(order_any.quantity(), Quantity::from(10));
606 assert_eq!(order_any.price(), Some(Price::new(100.0, 2)));
607 }
608}