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(o) => o.trigger_price,
262 Self::MarketIfTouched(o) => o.trigger_price,
263 Self::StopLimit(o) => o.trigger_price,
264 Self::StopMarket(o) => o.trigger_price,
265 Self::TrailingStopLimit(o) => o.activation_price.unwrap_or(o.trigger_price),
266 Self::TrailingStopMarket(o) => o.activation_price.unwrap_or(o.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 rstest::rstest;
291 use rust_decimal::Decimal;
292
293 use super::*;
294 use crate::{
295 enums::{OrderType, TrailingOffsetType},
296 events::{OrderEventAny, OrderUpdated, order::initialized::OrderInitializedBuilder},
297 identifiers::{ClientOrderId, InstrumentId, StrategyId},
298 orders::builder::OrderTestBuilder,
299 types::{Price, Quantity},
300 };
301
302 #[rstest]
303 fn test_order_any_equality() {
304 let client_order_id = ClientOrderId::from("ORDER-001");
306
307 let market_order = OrderTestBuilder::new(OrderType::Market)
308 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
309 .quantity(Quantity::from(10))
310 .client_order_id(client_order_id)
311 .build();
312
313 let limit_order = OrderTestBuilder::new(OrderType::Limit)
314 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
315 .quantity(Quantity::from(10))
316 .price(Price::new(100.0, 2))
317 .client_order_id(client_order_id)
318 .build();
319
320 assert_eq!(market_order, limit_order);
322 }
323
324 #[rstest]
325 fn test_order_any_conversion_from_events() {
326 let init_event = OrderInitializedBuilder::default()
328 .order_type(OrderType::Market)
329 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
330 .quantity(Quantity::from(10))
331 .build()
332 .unwrap();
333
334 let events = vec![OrderEventAny::Initialized(init_event.clone())];
336
337 let order = OrderAny::from_events(events).unwrap();
339
340 assert_eq!(order.order_type(), OrderType::Market);
342 assert_eq!(order.instrument_id(), init_event.instrument_id);
343 assert_eq!(order.quantity(), init_event.quantity);
344 }
345
346 #[rstest]
347 fn test_order_any_from_events_empty_error() {
348 let events: Vec<OrderEventAny> = vec![];
349 let result = OrderAny::from_events(events);
350
351 assert!(result.is_err());
352 assert_eq!(
353 result.unwrap_err().to_string(),
354 "No order events provided to create OrderAny"
355 );
356 }
357
358 #[rstest]
359 fn test_order_any_from_events_wrong_first_event() {
360 let client_order_id = ClientOrderId::from("ORDER-001");
362 let strategy_id = StrategyId::from("STRATEGY-001");
363
364 let update_event = OrderUpdated {
365 client_order_id,
366 strategy_id,
367 quantity: Quantity::from(20),
368 ..Default::default()
369 };
370
371 let events = vec![OrderEventAny::Updated(update_event)];
373
374 let result = OrderAny::from_events(events);
376 assert!(result.is_err());
377 assert_eq!(
378 result.unwrap_err().to_string(),
379 "First event must be `OrderInitialized`"
380 );
381 }
382
383 #[rstest]
384 fn test_passive_order_any_conversion() {
385 let limit_order = OrderTestBuilder::new(OrderType::Limit)
387 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
388 .quantity(Quantity::from(10))
389 .price(Price::new(100.0, 2))
390 .build();
391
392 let passive_order: PassiveOrderAny = limit_order.clone().into();
394 let order_any: OrderAny = passive_order.into();
395
396 assert_eq!(order_any.order_type(), OrderType::Limit);
398 assert_eq!(order_any.quantity(), Quantity::from(10));
399 }
400
401 #[rstest]
402 fn test_stop_order_any_conversion() {
403 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
405 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
406 .quantity(Quantity::from(10))
407 .trigger_price(Price::new(100.0, 2))
408 .build();
409
410 let stop_order_any: StopOrderAny = stop_order.into();
412 let order_any: OrderAny = stop_order_any.into();
413
414 assert_eq!(order_any.order_type(), OrderType::StopMarket);
416 assert_eq!(order_any.quantity(), Quantity::from(10));
417 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
418 }
419
420 #[rstest]
421 fn test_limit_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 limit_order_any: LimitOrderAny = limit_order.into();
431 let order_any: OrderAny = limit_order_any.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_limit_order_any_limit_price() {
440 let limit_order = OrderTestBuilder::new(OrderType::Limit)
442 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
443 .quantity(Quantity::from(10))
444 .price(Price::new(100.0, 2))
445 .build();
446
447 let limit_order_any: LimitOrderAny = limit_order.into();
449
450 let limit_px = limit_order_any.limit_px();
452 assert_eq!(limit_px, Price::new(100.0, 2));
453 }
454
455 #[rstest]
456 fn test_stop_order_any_stop_price() {
457 let stop_order = OrderTestBuilder::new(OrderType::StopMarket)
459 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
460 .quantity(Quantity::from(10))
461 .trigger_price(Price::new(100.0, 2))
462 .build();
463
464 let stop_order_any: StopOrderAny = stop_order.into();
466
467 let stop_px = stop_order_any.stop_px();
469 assert_eq!(stop_px, Price::new(100.0, 2));
470 }
471
472 #[rstest]
473 fn test_trailing_stop_market_order_conversion() {
474 let trailing_stop_order = OrderTestBuilder::new(OrderType::TrailingStopMarket)
476 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
477 .quantity(Quantity::from(10))
478 .trigger_price(Price::new(100.0, 2))
479 .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
481 .build();
482
483 let stop_order_any: StopOrderAny = trailing_stop_order.clone().into();
485
486 let order_any: OrderAny = stop_order_any.into();
488
489 assert_eq!(order_any.order_type(), OrderType::TrailingStopMarket);
491 assert_eq!(order_any.quantity(), Quantity::from(10));
492 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
493 assert_eq!(order_any.trailing_offset(), Some(Decimal::new(5, 1)));
494 assert_eq!(
495 order_any.trailing_offset_type(),
496 Some(TrailingOffsetType::NoTrailingOffset)
497 );
498 }
499
500 #[rstest]
501 fn test_trailing_stop_limit_order_conversion() {
502 let trailing_stop_limit = OrderTestBuilder::new(OrderType::TrailingStopLimit)
504 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
505 .quantity(Quantity::from(10))
506 .price(Price::new(99.0, 2))
507 .trigger_price(Price::new(100.0, 2))
508 .limit_offset(Decimal::new(10, 1)) .trailing_offset(Decimal::new(5, 1)) .trailing_offset_type(TrailingOffsetType::NoTrailingOffset)
511 .build();
512
513 let limit_order_any: LimitOrderAny = trailing_stop_limit.clone().into();
515
516 assert_eq!(limit_order_any.limit_px(), Price::new(99.0, 2));
518
519 let order_any: OrderAny = limit_order_any.into();
521
522 assert_eq!(order_any.order_type(), OrderType::TrailingStopLimit);
524 assert_eq!(order_any.quantity(), Quantity::from(10));
525 assert_eq!(order_any.price(), Some(Price::new(99.0, 2)));
526 assert_eq!(order_any.trigger_price(), Some(Price::new(100.0, 2)));
527 assert_eq!(order_any.trailing_offset(), Some(Decimal::new(5, 1)));
528 }
529
530 #[rstest]
531 fn test_passive_order_any_to_any() {
532 let limit_order = OrderTestBuilder::new(OrderType::Limit)
534 .instrument_id(InstrumentId::from("BTC-USDT.BINANCE"))
535 .quantity(Quantity::from(10))
536 .price(Price::new(100.0, 2))
537 .build();
538
539 let passive_order: PassiveOrderAny = limit_order.into();
541
542 let order_any = passive_order.to_any();
544
545 assert_eq!(order_any.order_type(), OrderType::Limit);
547 assert_eq!(order_any.quantity(), Quantity::from(10));
548 assert_eq!(order_any.price(), Some(Price::new(100.0, 2)));
549 }
550}