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, 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
100impl PartialEq for OrderAny {
101 fn eq(&self, other: &Self) -> bool {
102 self.client_order_id() == other.client_order_id()
103 }
104}
105
106impl Eq for OrderAny {}
108
109impl Display for OrderAny {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(
112 f,
113 "{}",
114 match self {
115 Self::Limit(order) => order.to_string(),
116 Self::LimitIfTouched(order) => order.to_string(),
117 Self::Market(order) => order.to_string(),
118 Self::MarketIfTouched(order) => order.to_string(),
119 Self::MarketToLimit(order) => order.to_string(),
120 Self::StopLimit(order) => order.to_string(),
121 Self::StopMarket(order) => order.to_string(),
122 Self::TrailingStopLimit(order) => order.to_string(),
123 Self::TrailingStopMarket(order) => order.to_string(),
124 }
125 )
126 }
127}
128
129impl TryFrom<OrderAny> for PassiveOrderAny {
130 type Error = String;
131
132 fn try_from(order: OrderAny) -> Result<Self, Self::Error> {
133 match order {
134 OrderAny::Limit(_) => Ok(Self::Limit(LimitOrderAny::try_from(order)?)),
135 OrderAny::LimitIfTouched(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
136 OrderAny::MarketIfTouched(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
137 OrderAny::StopLimit(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
138 OrderAny::StopMarket(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
139 OrderAny::TrailingStopLimit(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
140 OrderAny::TrailingStopMarket(_) => Ok(Self::Stop(StopOrderAny::try_from(order)?)),
141 OrderAny::MarketToLimit(_) => Ok(Self::Limit(LimitOrderAny::try_from(order)?)),
142 OrderAny::Market(_) => Ok(Self::Limit(LimitOrderAny::try_from(order)?)),
143 }
144 }
145}
146
147impl From<PassiveOrderAny> for OrderAny {
148 fn from(order: PassiveOrderAny) -> Self {
149 match order {
150 PassiveOrderAny::Limit(order) => order.into(),
151 PassiveOrderAny::Stop(order) => order.into(),
152 }
153 }
154}
155
156impl TryFrom<OrderAny> for StopOrderAny {
157 type Error = String;
158
159 fn try_from(order: OrderAny) -> Result<Self, Self::Error> {
160 match order {
161 OrderAny::LimitIfTouched(order) => Ok(Self::LimitIfTouched(order)),
162 OrderAny::MarketIfTouched(order) => Ok(Self::MarketIfTouched(order)),
163 OrderAny::StopLimit(order) => Ok(Self::StopLimit(order)),
164 OrderAny::StopMarket(order) => Ok(Self::StopMarket(order)),
165 OrderAny::TrailingStopLimit(order) => Ok(Self::TrailingStopLimit(order)),
166 OrderAny::TrailingStopMarket(order) => Ok(Self::TrailingStopMarket(order)),
167 _ => Err(format!(
168 "Cannot convert {:?} order to StopOrderAny: order type does not have a stop/trigger price",
169 order.order_type()
170 )),
171 }
172 }
173}
174
175impl From<StopOrderAny> for OrderAny {
176 fn from(order: StopOrderAny) -> Self {
177 match order {
178 StopOrderAny::LimitIfTouched(order) => Self::LimitIfTouched(order),
179 StopOrderAny::MarketIfTouched(order) => Self::MarketIfTouched(order),
180 StopOrderAny::StopLimit(order) => Self::StopLimit(order),
181 StopOrderAny::StopMarket(order) => Self::StopMarket(order),
182 StopOrderAny::TrailingStopLimit(order) => Self::TrailingStopLimit(order),
183 StopOrderAny::TrailingStopMarket(order) => Self::TrailingStopMarket(order),
184 }
185 }
186}
187
188impl TryFrom<OrderAny> for LimitOrderAny {
189 type Error = String;
190
191 fn try_from(order: OrderAny) -> Result<Self, Self::Error> {
192 match order {
193 OrderAny::Limit(order) => Ok(Self::Limit(order)),
194 OrderAny::MarketToLimit(order) => Ok(Self::MarketToLimit(order)),
195 OrderAny::StopLimit(order) => Ok(Self::StopLimit(order)),
196 OrderAny::TrailingStopLimit(order) => Ok(Self::TrailingStopLimit(order)),
197 OrderAny::Market(order) => Ok(Self::MarketOrderWithProtection(order)),
198 _ => Err(format!(
199 "Cannot convert {:?} order to LimitOrderAny: order type does not have a limit price",
200 order.order_type()
201 )),
202 }
203 }
204}
205
206impl From<LimitOrderAny> for OrderAny {
207 fn from(order: LimitOrderAny) -> Self {
208 match order {
209 LimitOrderAny::Limit(order) => Self::Limit(order),
210 LimitOrderAny::MarketToLimit(order) => Self::MarketToLimit(order),
211 LimitOrderAny::StopLimit(order) => Self::StopLimit(order),
212 LimitOrderAny::TrailingStopLimit(order) => Self::TrailingStopLimit(order),
213 LimitOrderAny::MarketOrderWithProtection(order) => Self::Market(order),
214 }
215 }
216}
217
218#[derive(Clone, Debug)]
219#[enum_dispatch(Order)]
220pub enum PassiveOrderAny {
221 Limit(LimitOrderAny),
222 Stop(StopOrderAny),
223}
224
225impl PassiveOrderAny {
226 #[must_use]
227 pub fn to_any(&self) -> OrderAny {
228 match self {
229 Self::Limit(order) => order.clone().into(),
230 Self::Stop(order) => order.clone().into(),
231 }
232 }
233}
234
235impl PartialEq for PassiveOrderAny {
237 fn eq(&self, rhs: &Self) -> bool {
238 match self {
239 Self::Limit(order) => order.client_order_id() == rhs.client_order_id(),
240 Self::Stop(order) => order.client_order_id() == rhs.client_order_id(),
241 }
242 }
243}
244
245#[derive(Clone, Debug)]
246#[enum_dispatch(Order)]
247pub enum LimitOrderAny {
248 Limit(LimitOrder),
249 MarketToLimit(MarketToLimitOrder),
250 StopLimit(StopLimitOrder),
251 TrailingStopLimit(TrailingStopLimitOrder),
252 MarketOrderWithProtection(MarketOrder),
253}
254
255impl LimitOrderAny {
256 #[must_use]
262 pub fn limit_px(&self) -> Price {
263 match self {
264 Self::Limit(order) => order.price,
265 Self::MarketToLimit(order) => order.price.expect("MarketToLimit order price not set"),
266 Self::StopLimit(order) => order.price,
267 Self::TrailingStopLimit(order) => order.price,
268 Self::MarketOrderWithProtection(order) => {
269 order.protection_price.expect("No price for order")
270 }
271 }
272 }
273}
274
275impl PartialEq for LimitOrderAny {
276 fn eq(&self, rhs: &Self) -> bool {
277 match self {
278 Self::Limit(order) => order.client_order_id == rhs.client_order_id(),
279 Self::MarketToLimit(order) => order.client_order_id == rhs.client_order_id(),
280 Self::StopLimit(order) => order.client_order_id == rhs.client_order_id(),
281 Self::TrailingStopLimit(order) => order.client_order_id == rhs.client_order_id(),
282 Self::MarketOrderWithProtection(order) => {
283 order.client_order_id == rhs.client_order_id()
284 }
285 }
286 }
287}
288
289#[derive(Clone, Debug)]
290#[enum_dispatch(Order)]
291pub enum StopOrderAny {
292 LimitIfTouched(LimitIfTouchedOrder),
293 MarketIfTouched(MarketIfTouchedOrder),
294 StopLimit(StopLimitOrder),
295 StopMarket(StopMarketOrder),
296 TrailingStopLimit(TrailingStopLimitOrder),
297 TrailingStopMarket(TrailingStopMarketOrder),
298}
299
300impl StopOrderAny {
301 #[must_use]
302 pub fn stop_px(&self) -> Price {
303 match self {
304 Self::LimitIfTouched(o) => o.trigger_price,
305 Self::MarketIfTouched(o) => o.trigger_price,
306 Self::StopLimit(o) => o.trigger_price,
307 Self::StopMarket(o) => o.trigger_price,
308 Self::TrailingStopLimit(o) => o.activation_price.unwrap_or(o.trigger_price),
309 Self::TrailingStopMarket(o) => o.activation_price.unwrap_or(o.trigger_price),
310 }
311 }
312}
313
314impl PartialEq for StopOrderAny {
316 fn eq(&self, rhs: &Self) -> bool {
317 match self {
318 Self::LimitIfTouched(order) => order.client_order_id == rhs.client_order_id(),
319 Self::StopLimit(order) => order.client_order_id == rhs.client_order_id(),
320 Self::StopMarket(order) => order.client_order_id == rhs.client_order_id(),
321 Self::MarketIfTouched(order) => order.client_order_id == rhs.client_order_id(),
322 Self::TrailingStopLimit(order) => order.client_order_id == rhs.client_order_id(),
323 Self::TrailingStopMarket(order) => order.client_order_id == rhs.client_order_id(),
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use rstest::rstest;
331 use rust_decimal::Decimal;
332 use rust_decimal_macros::dec;
333
334 use super::*;
335 use crate::{
336 enums::{OrderType, TrailingOffsetType},
337 events::{OrderEventAny, OrderUpdated, order::initialized::OrderInitializedBuilder},
338 identifiers::{ClientOrderId, InstrumentId, StrategyId},
339 orders::builder::OrderTestBuilder,
340 types::{Price, Quantity},
341 };
342
343 #[rstest]
344 fn test_order_any_equality() {
345 let client_order_id = ClientOrderId::from("ORDER-001");
347
348 let market_order = OrderTestBuilder::new(OrderType::Market)
349 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
350 .quantity(Quantity::from(10))
351 .client_order_id(client_order_id)
352 .build();
353
354 let limit_order = OrderTestBuilder::new(OrderType::Limit)
355 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
356 .quantity(Quantity::from(10))
357 .price(Price::new(100.0, 2))
358 .client_order_id(client_order_id)
359 .build();
360
361 assert_eq!(market_order, limit_order);
363 }
364
365 #[rstest]
366 fn test_order_any_conversion_from_events() {
367 let init_event = OrderInitializedBuilder::default()
369 .order_type(OrderType::Market)
370 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
371 .quantity(Quantity::from(10))
372 .build()
373 .unwrap();
374
375 let events = vec![OrderEventAny::Initialized(init_event.clone())];
377
378 let order = OrderAny::from_events(events).unwrap();
380
381 assert_eq!(order.order_type(), OrderType::Market);
383 assert_eq!(order.instrument_id(), init_event.instrument_id);
384 assert_eq!(order.quantity(), init_event.quantity);
385 }
386
387 #[rstest]
388 fn test_order_any_from_events_empty_error() {
389 let events: Vec<OrderEventAny> = vec![];
390 let result = OrderAny::from_events(events);
391
392 assert!(result.is_err());
393 assert_eq!(
394 result.unwrap_err().to_string(),
395 "No order events provided to create OrderAny"
396 );
397 }
398
399 #[rstest]
400 fn test_order_any_from_events_wrong_first_event() {
401 let client_order_id = ClientOrderId::from("ORDER-001");
403 let strategy_id = StrategyId::from("STRATEGY-001");
404
405 let update_event = OrderUpdated {
406 client_order_id,
407 strategy_id,
408 quantity: Quantity::from(20),
409 ..Default::default()
410 };
411
412 let events = vec![OrderEventAny::Updated(update_event)];
414
415 let result = OrderAny::from_events(events);
417 assert!(result.is_err());
418 assert_eq!(
419 result.unwrap_err().to_string(),
420 "First event must be `OrderInitialized`"
421 );
422 }
423
424 #[rstest]
425 fn test_passive_order_any_conversion() {
426 let limit_order = OrderTestBuilder::new(OrderType::Limit)
428 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
429 .quantity(Quantity::from(10))
430 .price(Price::new(100.0, 2))
431 .build();
432
433 let passive_order = PassiveOrderAny::try_from(limit_order).unwrap();
435 let order_any: OrderAny = passive_order.into();
436
437 assert_eq!(order_any.order_type(), OrderType::Limit);
439 assert_eq!(order_any.quantity(), Quantity::from(10));
440 }
441
442 #[rstest]
443 fn test_stop_order_any_conversion() {
444 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
446 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
447 .quantity(Quantity::from(10))
448 .trigger_price(Price::new(100.0, 2))
449 .build();
450
451 let stop_order_any = StopOrderAny::try_from(stop_order).unwrap();
453 let order_any: OrderAny = stop_order_any.into();
454
455 assert_eq!(order_any.order_type(), OrderType::StopMarket);
457 assert_eq!(order_any.quantity(), Quantity::from(10));
458 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
459 }
460
461 #[rstest]
462 fn test_limit_order_any_conversion() {
463 let limit_order = OrderTestBuilder::new(OrderType::Limit)
465 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
466 .quantity(Quantity::from(10))
467 .price(Price::new(100.0, 2))
468 .build();
469
470 let limit_order_any = LimitOrderAny::try_from(limit_order).unwrap();
472 let order_any: OrderAny = limit_order_any.into();
473
474 assert_eq!(order_any.order_type(), OrderType::Limit);
476 assert_eq!(order_any.quantity(), Quantity::from(10));
477 }
478
479 #[rstest]
480 fn test_limit_order_any_limit_price() {
481 let limit_order = OrderTestBuilder::new(OrderType::Limit)
483 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
484 .quantity(Quantity::from(10))
485 .price(Price::new(100.0, 2))
486 .build();
487
488 let limit_order_any = LimitOrderAny::try_from(limit_order).unwrap();
490
491 let limit_px = limit_order_any.limit_px();
493 assert_eq!(limit_px, Price::new(100.0, 2));
494 }
495
496 #[rstest]
497 fn test_stop_order_any_stop_price() {
498 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
500 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
501 .quantity(Quantity::from(10))
502 .trigger_price(Price::new(100.0, 2))
503 .build();
504
505 let stop_order_any = StopOrderAny::try_from(stop_order).unwrap();
507
508 let stop_px = stop_order_any.stop_px();
510 assert_eq!(stop_px, Price::new(100.0, 2));
511 }
512
513 #[rstest]
514 fn test_trailing_stop_market_order_conversion() {
515 let trailing_stop_order = OrderTestBuilder::new(OrderType::TrailingStopMarket)
517 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
518 .quantity(Quantity::from(10))
519 .trigger_price(Price::new(100.0, 2))
520 .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
522 .build();
523
524 let stop_order_any = StopOrderAny::try_from(trailing_stop_order).unwrap();
526
527 let order_any: OrderAny = stop_order_any.into();
529
530 assert_eq!(order_any.order_type(), OrderType::TrailingStopMarket);
532 assert_eq!(order_any.quantity(), Quantity::from(10));
533 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
534 assert_eq!(order_any.trailing_offset(), Some(dec!(0.5)));
535 assert_eq!(
536 order_any.trailing_offset_type(),
537 Some(TrailingOffsetType::NoTrailingOffset)
538 );
539 }
540
541 #[rstest]
542 fn test_trailing_stop_limit_order_conversion() {
543 let trailing_stop_limit = OrderTestBuilder::new(OrderType::TrailingStopLimit)
545 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
546 .quantity(Quantity::from(10))
547 .price(Price::new(99.0, 2))
548 .trigger_price(Price::new(100.0, 2))
549 .limit_offset(Decimal::new(10, 1)) .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
552 .build();
553
554 let limit_order_any = LimitOrderAny::try_from(trailing_stop_limit).unwrap();
556
557 assert_eq!(limit_order_any.limit_px(), Price::new(99.0, 2));
559
560 let order_any: OrderAny = limit_order_any.into();
562
563 assert_eq!(order_any.order_type(), OrderType::TrailingStopLimit);
565 assert_eq!(order_any.quantity(), Quantity::from(10));
566 assert_eq!(order_any.price(), Some(Price::new(99.0, 2)));
567 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
568 assert_eq!(order_any.trailing_offset(), Some(dec!(0.5)));
569 }
570
571 #[rstest]
572 fn test_passive_order_any_to_any() {
573 let limit_order = OrderTestBuilder::new(OrderType::Limit)
575 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
576 .quantity(Quantity::from(10))
577 .price(Price::new(100.0, 2))
578 .build();
579
580 let passive_order = PassiveOrderAny::try_from(limit_order).unwrap();
582
583 let order_any = passive_order.to_any();
585
586 assert_eq!(order_any.order_type(), OrderType::Limit);
588 assert_eq!(order_any.quantity(), Quantity::from(10));
589 assert_eq!(order_any.price(), Some(Price::new(100.0, 2)));
590 }
591}