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