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