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> {
56 if events.is_empty() {
57 anyhow::bail!("No order events provided to create OrderAny");
58 }
59
60 let init_event = events.first().unwrap();
62 match init_event {
63 OrderEventAny::Initialized(init) => {
64 let mut order = Self::from(init.clone());
65 for event in events.into_iter().skip(1) {
67 println!("Applying event: {event:?}"); order.apply(event).unwrap();
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 From<OrderAny> for PassiveOrderAny {
110 fn from(order: OrderAny) -> PassiveOrderAny {
111 match order {
112 OrderAny::Limit(_) => PassiveOrderAny::Limit(order.into()),
113 OrderAny::LimitIfTouched(_) => PassiveOrderAny::Stop(order.into()),
114 OrderAny::MarketIfTouched(_) => PassiveOrderAny::Stop(order.into()),
115 OrderAny::StopLimit(_) => PassiveOrderAny::Stop(order.into()),
116 OrderAny::StopMarket(_) => PassiveOrderAny::Stop(order.into()),
117 OrderAny::TrailingStopLimit(_) => PassiveOrderAny::Stop(order.into()),
118 OrderAny::TrailingStopMarket(_) => PassiveOrderAny::Stop(order.into()),
119 OrderAny::MarketToLimit(_) => PassiveOrderAny::Limit(order.into()),
120 _ => panic!("WIP: Implement trait bound to require `HasPrice`"),
121 }
122 }
123}
124
125impl From<PassiveOrderAny> for OrderAny {
126 fn from(order: PassiveOrderAny) -> OrderAny {
127 match order {
128 PassiveOrderAny::Limit(order) => order.into(),
129 PassiveOrderAny::Stop(order) => order.into(),
130 }
131 }
132}
133
134impl From<OrderAny> for StopOrderAny {
135 fn from(order: OrderAny) -> StopOrderAny {
136 match order {
137 OrderAny::LimitIfTouched(order) => StopOrderAny::LimitIfTouched(order),
138 OrderAny::MarketIfTouched(order) => StopOrderAny::MarketIfTouched(order),
139 OrderAny::StopLimit(order) => StopOrderAny::StopLimit(order),
140 OrderAny::StopMarket(order) => StopOrderAny::StopMarket(order),
141 OrderAny::TrailingStopLimit(order) => StopOrderAny::TrailingStopLimit(order),
142 OrderAny::TrailingStopMarket(order) => StopOrderAny::TrailingStopMarket(order),
143 _ => panic!("WIP: Implement trait bound to require `HasStopPrice`"),
144 }
145 }
146}
147
148impl From<StopOrderAny> for OrderAny {
149 fn from(order: StopOrderAny) -> OrderAny {
150 match order {
151 StopOrderAny::LimitIfTouched(order) => OrderAny::LimitIfTouched(order),
152 StopOrderAny::MarketIfTouched(order) => OrderAny::MarketIfTouched(order),
153 StopOrderAny::StopLimit(order) => OrderAny::StopLimit(order),
154 StopOrderAny::StopMarket(order) => OrderAny::StopMarket(order),
155 StopOrderAny::TrailingStopLimit(order) => OrderAny::TrailingStopLimit(order),
156 StopOrderAny::TrailingStopMarket(order) => OrderAny::TrailingStopMarket(order),
157 }
158 }
159}
160
161impl From<OrderAny> for LimitOrderAny {
162 fn from(order: OrderAny) -> LimitOrderAny {
163 match order {
164 OrderAny::Limit(order) => LimitOrderAny::Limit(order),
165 OrderAny::MarketToLimit(order) => LimitOrderAny::MarketToLimit(order),
166 OrderAny::StopLimit(order) => LimitOrderAny::StopLimit(order),
167 OrderAny::TrailingStopLimit(order) => LimitOrderAny::TrailingStopLimit(order),
168 _ => panic!("WIP: Implement trait bound to require `HasLimitPrice`"),
169 }
170 }
171}
172
173impl From<LimitOrderAny> for OrderAny {
174 fn from(order: LimitOrderAny) -> OrderAny {
175 match order {
176 LimitOrderAny::Limit(order) => OrderAny::Limit(order),
177 LimitOrderAny::MarketToLimit(order) => OrderAny::MarketToLimit(order),
178 LimitOrderAny::StopLimit(order) => OrderAny::StopLimit(order),
179 LimitOrderAny::TrailingStopLimit(order) => OrderAny::TrailingStopLimit(order),
180 }
181 }
182}
183
184#[derive(Clone, Debug)]
185#[enum_dispatch(Order)]
186pub enum PassiveOrderAny {
187 Limit(LimitOrderAny),
188 Stop(StopOrderAny),
189}
190
191impl PassiveOrderAny {
192 #[must_use]
193 pub fn to_any(&self) -> OrderAny {
194 match self {
195 Self::Limit(order) => order.clone().into(),
196 Self::Stop(order) => order.clone().into(),
197 }
198 }
199}
200
201impl PartialEq for PassiveOrderAny {
203 fn eq(&self, rhs: &Self) -> bool {
204 match self {
205 Self::Limit(order) => order.client_order_id() == rhs.client_order_id(),
206 Self::Stop(order) => order.client_order_id() == rhs.client_order_id(),
207 }
208 }
209}
210
211#[derive(Clone, Debug)]
212#[enum_dispatch(Order)]
213pub enum LimitOrderAny {
214 Limit(LimitOrder),
215 MarketToLimit(MarketToLimitOrder),
216 StopLimit(StopLimitOrder),
217 TrailingStopLimit(TrailingStopLimitOrder),
218}
219
220impl LimitOrderAny {
221 #[must_use]
225 pub fn limit_px(&self) -> Price {
226 match self {
227 Self::Limit(order) => order.price,
228 Self::MarketToLimit(order) => order.price.expect("No price for order"), Self::StopLimit(order) => order.price,
230 Self::TrailingStopLimit(order) => order.price,
231 }
232 }
233}
234
235impl PartialEq for LimitOrderAny {
236 fn eq(&self, rhs: &Self) -> bool {
237 match self {
238 Self::Limit(order) => order.client_order_id == rhs.client_order_id(),
239 Self::MarketToLimit(order) => order.client_order_id == rhs.client_order_id(),
240 Self::StopLimit(order) => order.client_order_id == rhs.client_order_id(),
241 Self::TrailingStopLimit(order) => order.client_order_id == rhs.client_order_id(),
242 }
243 }
244}
245
246#[derive(Clone, Debug)]
247#[enum_dispatch(Order)]
248pub enum StopOrderAny {
249 LimitIfTouched(LimitIfTouchedOrder),
250 MarketIfTouched(MarketIfTouchedOrder),
251 StopLimit(StopLimitOrder),
252 StopMarket(StopMarketOrder),
253 TrailingStopLimit(TrailingStopLimitOrder),
254 TrailingStopMarket(TrailingStopMarketOrder),
255}
256
257impl StopOrderAny {
258 #[must_use]
259 pub fn stop_px(&self) -> Price {
260 match self {
261 Self::LimitIfTouched(order) => order.trigger_price,
262 Self::MarketIfTouched(order) => order.trigger_price,
263 Self::StopLimit(order) => order.trigger_price,
264 Self::StopMarket(order) => order.trigger_price,
265 Self::TrailingStopLimit(order) => order.trigger_price,
266 Self::TrailingStopMarket(order) => order.trigger_price,
267 }
268 }
269}
270
271impl PartialEq for StopOrderAny {
273 fn eq(&self, rhs: &Self) -> bool {
274 match self {
275 Self::LimitIfTouched(order) => order.client_order_id == rhs.client_order_id(),
276 Self::StopLimit(order) => order.client_order_id == rhs.client_order_id(),
277 Self::StopMarket(order) => order.client_order_id == rhs.client_order_id(),
278 Self::MarketIfTouched(order) => order.client_order_id == rhs.client_order_id(),
279 Self::TrailingStopLimit(order) => order.client_order_id == rhs.client_order_id(),
280 Self::TrailingStopMarket(order) => order.client_order_id == rhs.client_order_id(),
281 }
282 }
283}
284
285#[cfg(test)]
289mod tests {
290 use rust_decimal::Decimal;
291
292 use super::*;
293 use crate::{
294 enums::{OrderType, TrailingOffsetType},
295 events::{OrderEventAny, OrderUpdated, order::initialized::OrderInitializedBuilder},
296 identifiers::{ClientOrderId, InstrumentId, StrategyId},
297 orders::builder::OrderTestBuilder,
298 types::{Price, Quantity},
299 };
300
301 #[test]
302 fn test_order_any_equality() {
303 let client_order_id = ClientOrderId::from("ORDER-001");
305
306 let market_order = OrderTestBuilder::new(OrderType::Market)
307 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
308 .quantity(Quantity::from(10))
309 .client_order_id(client_order_id.clone())
310 .build();
311
312 let limit_order = OrderTestBuilder::new(OrderType::Limit)
313 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
314 .quantity(Quantity::from(10))
315 .price(Price::new(100.0, 2))
316 .client_order_id(client_order_id)
317 .build();
318
319 assert_eq!(market_order, limit_order);
321 }
322
323 #[test]
324 fn test_order_any_conversion_from_events() {
325 let init_event = OrderInitializedBuilder::default()
327 .order_type(OrderType::Market)
328 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
329 .quantity(Quantity::from(10))
330 .build()
331 .unwrap();
332
333 let events = vec![OrderEventAny::Initialized(init_event.clone())];
335
336 let order = OrderAny::from_events(events).unwrap();
338
339 assert_eq!(order.order_type(), OrderType::Market);
341 assert_eq!(order.instrument_id(), init_event.instrument_id);
342 assert_eq!(order.quantity(), init_event.quantity);
343 }
344
345 #[test]
346 fn test_order_any_from_events_empty_error() {
347 let events: Vec<OrderEventAny> = vec![];
348 let result = OrderAny::from_events(events);
349
350 assert!(result.is_err());
351 assert_eq!(
352 result.unwrap_err().to_string(),
353 "No order events provided to create OrderAny"
354 );
355 }
356
357 #[test]
358 fn test_order_any_from_events_wrong_first_event() {
359 let client_order_id = ClientOrderId::from("ORDER-001");
361 let strategy_id = StrategyId::from("STRATEGY-001");
362
363 let update_event = OrderUpdated {
364 client_order_id,
365 strategy_id,
366 quantity: Quantity::from(20),
367 ..Default::default()
368 };
369
370 let events = vec![OrderEventAny::Updated(update_event)];
372
373 let result = OrderAny::from_events(events);
375 assert!(result.is_err());
376 assert_eq!(
377 result.unwrap_err().to_string(),
378 "First event must be `OrderInitialized`"
379 );
380 }
381
382 #[test]
383 fn test_passive_order_any_conversion() {
384 let limit_order = OrderTestBuilder::new(OrderType::Limit)
386 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
387 .quantity(Quantity::from(10))
388 .price(Price::new(100.0, 2))
389 .build();
390
391 let passive_order: PassiveOrderAny = limit_order.clone().into();
393 let order_any: OrderAny = passive_order.into();
394
395 assert_eq!(order_any.order_type(), OrderType::Limit);
397 assert_eq!(order_any.quantity(), Quantity::from(10));
398 }
399
400 #[test]
401 fn test_stop_order_any_conversion() {
402 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
404 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
405 .quantity(Quantity::from(10))
406 .trigger_price(Price::new(100.0, 2))
407 .build();
408
409 let stop_order_any: StopOrderAny = stop_order.into();
411 let order_any: OrderAny = stop_order_any.into();
412
413 assert_eq!(order_any.order_type(), OrderType::StopMarket);
415 assert_eq!(order_any.quantity(), Quantity::from(10));
416 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
417 }
418
419 #[test]
420 fn test_limit_order_any_conversion() {
421 let limit_order = OrderTestBuilder::new(OrderType::Limit)
423 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
424 .quantity(Quantity::from(10))
425 .price(Price::new(100.0, 2))
426 .build();
427
428 let limit_order_any: LimitOrderAny = limit_order.into();
430 let order_any: OrderAny = limit_order_any.into();
431
432 assert_eq!(order_any.order_type(), OrderType::Limit);
434 assert_eq!(order_any.quantity(), Quantity::from(10));
435 }
436
437 #[test]
438 fn test_limit_order_any_limit_price() {
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 = limit_order.into();
448
449 let limit_px = limit_order_any.limit_px();
451 assert_eq!(limit_px, Price::new(100.0, 2));
452 }
453
454 #[test]
455 fn test_stop_order_any_stop_price() {
456 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
458 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
459 .quantity(Quantity::from(10))
460 .trigger_price(Price::new(100.0, 2))
461 .build();
462
463 let stop_order_any: StopOrderAny = stop_order.into();
465
466 let stop_px = stop_order_any.stop_px();
468 assert_eq!(stop_px, Price::new(100.0, 2));
469 }
470
471 #[test]
472 fn test_trailing_stop_market_order_conversion() {
473 let trailing_stop_order = OrderTestBuilder::new(OrderType::TrailingStopMarket)
475 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
476 .quantity(Quantity::from(10))
477 .trigger_price(Price::new(100.0, 2))
478 .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
480 .build();
481
482 let stop_order_any: StopOrderAny = trailing_stop_order.clone().into();
484
485 let order_any: OrderAny = stop_order_any.into();
487
488 assert_eq!(order_any.order_type(), OrderType::TrailingStopMarket);
490 assert_eq!(order_any.quantity(), Quantity::from(10));
491 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
492 assert_eq!(order_any.trailing_offset(), Some(Decimal::new(5, 1)));
493 assert_eq!(
494 order_any.trailing_offset_type(),
495 Some(TrailingOffsetType::NoTrailingOffset)
496 );
497 }
498
499 #[test]
500 fn test_trailing_stop_limit_order_conversion() {
501 let trailing_stop_limit = OrderTestBuilder::new(OrderType::TrailingStopLimit)
503 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
504 .quantity(Quantity::from(10))
505 .price(Price::new(99.0, 2))
506 .trigger_price(Price::new(100.0, 2))
507 .limit_offset(Decimal::new(10, 1)) .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
510 .build();
511
512 let limit_order_any: LimitOrderAny = trailing_stop_limit.clone().into();
514
515 assert_eq!(limit_order_any.limit_px(), Price::new(99.0, 2));
517
518 let order_any: OrderAny = limit_order_any.into();
520
521 assert_eq!(order_any.order_type(), OrderType::TrailingStopLimit);
523 assert_eq!(order_any.quantity(), Quantity::from(10));
524 assert_eq!(order_any.price(), Some(Price::new(99.0, 2)));
525 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
526 assert_eq!(order_any.trailing_offset(), Some(Decimal::new(5, 1)));
527 }
528
529 #[test]
530 fn test_passive_order_any_to_any() {
531 let limit_order = OrderTestBuilder::new(OrderType::Limit)
533 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
534 .quantity(Quantity::from(10))
535 .price(Price::new(100.0, 2))
536 .build();
537
538 let passive_order: PassiveOrderAny = limit_order.into();
540
541 let order_any = passive_order.to_any();
543
544 assert_eq!(order_any.order_type(), OrderType::Limit);
546 assert_eq!(order_any.quantity(), Quantity::from(10));
547 assert_eq!(order_any.price(), Some(Price::new(100.0, 2)));
548 }
549}