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