1use crate::order::{
2 id::StrategyId,
3 request::{OrderRequestCancel, OrderRequestOpen, RequestCancel, RequestOpen},
4 state::UnindexedOrderState,
5};
6use chrono::{DateTime, Utc};
7use derive_more::{Constructor, Display};
8use id::ClientOrderId;
9use rust_decimal::Decimal;
10use rustrade_instrument::{
11 Side,
12 asset::{AssetIndex, name::AssetNameExchange},
13 exchange::{ExchangeId, ExchangeIndex},
14 instrument::{InstrumentIndex, name::InstrumentNameExchange},
15};
16use serde::{Deserialize, Serialize};
17use state::{
18 ActiveOrderState, Cancelled, Expired, InactiveOrderState, Open, OpenInFlight, OrderState,
19};
20
21pub mod id;
23
24pub mod state;
28
29pub mod request;
33
34pub mod bracket;
36
37pub type UnindexedOrder = Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>;
39
40pub type UnindexedOrderKey = OrderKey<ExchangeId, InstrumentNameExchange>;
43
44pub type UnindexedInactiveOrder =
47 Order<ExchangeId, InstrumentNameExchange, state::UnindexedInactiveOrderState>;
48
49pub type UnindexedOrderSnapshot = Order<
52 ExchangeId,
53 InstrumentNameExchange,
54 OrderState<AssetNameExchange, InstrumentNameExchange>,
55>;
56
57pub type OrderSnapshot<
59 ExchangeKey = ExchangeIndex,
60 AssetKey = AssetIndex,
61 InstrumentKey = InstrumentIndex,
62> = Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>;
63
64#[derive(
65 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
66)]
67
68pub struct OrderEvent<State, ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> {
69 pub key: OrderKey<ExchangeKey, InstrumentKey>,
70 pub state: State,
71}
72
73#[derive(
74 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
75)]
76pub struct OrderKey<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> {
77 pub exchange: ExchangeKey,
78 pub instrument: InstrumentKey,
79 pub strategy: StrategyId,
80 pub cid: ClientOrderId,
81}
82
83impl<ExchangeKey, InstrumentKey> OrderKey<ExchangeKey, &InstrumentKey>
84where
85 InstrumentKey: Clone,
86{
87 pub fn into_owned_instrument(self) -> OrderKey<ExchangeKey, InstrumentKey> {
94 let Self {
95 exchange,
96 instrument,
97 strategy,
98 cid,
99 } = self;
100
101 OrderKey {
102 exchange,
103 instrument: instrument.clone(),
104 strategy,
105 cid,
106 }
107 }
108}
109
110impl<State, ExchangeKey, InstrumentKey> OrderEvent<State, ExchangeKey, &InstrumentKey>
111where
112 InstrumentKey: Clone,
113{
114 pub fn into_owned_instrument(self) -> OrderEvent<State, ExchangeKey, InstrumentKey> {
118 let Self { key, state } = self;
119
120 OrderEvent {
121 key: key.into_owned_instrument(),
122 state,
123 }
124 }
125}
126
127#[derive(
128 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
129)]
130pub struct Order<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex, State = OrderState> {
131 pub key: OrderKey<ExchangeKey, InstrumentKey>,
132 pub side: Side,
133 pub price: Option<Decimal>,
137 pub quantity: Decimal,
138 pub kind: OrderKind,
139 pub time_in_force: TimeInForce,
140 pub state: State,
141}
142
143impl<ExchangeKey, InstrumentKey, State> Order<ExchangeKey, InstrumentKey, State> {
144 pub fn map_state<NewState>(
146 self,
147 f: impl FnOnce(State) -> NewState,
148 ) -> Order<ExchangeKey, InstrumentKey, NewState> {
149 let Order {
150 key,
151 side,
152 price,
153 quantity,
154 kind,
155 time_in_force,
156 state,
157 } = self;
158
159 Order {
160 key,
161 side,
162 price,
163 quantity,
164 kind,
165 time_in_force,
166 state: f(state),
167 }
168 }
169}
170
171impl<ExchangeKey, AssetKey, InstrumentKey>
172 Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
173{
174 pub fn to_active(&self) -> Option<Order<ExchangeKey, InstrumentKey, ActiveOrderState>>
175 where
176 ExchangeKey: Clone,
177 InstrumentKey: Clone,
178 {
179 let OrderState::Active(state) = &self.state else {
180 return None;
181 };
182
183 Some(Order {
184 key: self.key.clone(),
185 side: self.side,
186 price: self.price,
187 quantity: self.quantity,
188 kind: self.kind,
189 time_in_force: self.time_in_force,
190 state: state.clone(),
191 })
192 }
193
194 pub fn to_inactive(
195 &self,
196 ) -> Option<Order<ExchangeKey, InstrumentKey, InactiveOrderState<AssetKey, InstrumentKey>>>
197 where
198 ExchangeKey: Clone,
199 AssetKey: Clone,
200 InstrumentKey: Clone,
201 {
202 let OrderState::Inactive(state) = &self.state else {
203 return None;
204 };
205
206 Some(Order {
207 key: self.key.clone(),
208 side: self.side,
209 price: self.price,
210 quantity: self.quantity,
211 kind: self.kind,
212 time_in_force: self.time_in_force,
213 state: state.clone(),
214 })
215 }
216}
217
218impl<ExchangeKey, InstrumentKey> Order<ExchangeKey, InstrumentKey, ActiveOrderState>
219where
220 ExchangeKey: Clone,
221 InstrumentKey: Clone,
222{
223 pub fn to_request_cancel(&self) -> Option<OrderRequestCancel<ExchangeKey, InstrumentKey>> {
224 let Order { key, state, .. } = self;
225
226 let request_cancel = match state {
227 ActiveOrderState::OpenInFlight(_) => RequestCancel { id: None },
228 ActiveOrderState::Open(open) => RequestCancel {
229 id: Some(open.id.clone()),
230 },
231 _ => return None,
232 };
233
234 Some(OrderRequestCancel {
235 key: key.clone(),
236 state: request_cancel,
237 })
238 }
239}
240
241#[derive(
248 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
249)]
250pub enum TrailingOffsetType {
251 Absolute,
253 Percentage,
255 BasisPoints,
257}
258
259#[derive(
260 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
261)]
262#[non_exhaustive]
263pub enum OrderKind {
264 Market,
265 Limit,
266 #[display("Stop({trigger_price})")]
268 Stop {
269 trigger_price: Decimal,
270 },
271 #[display("StopLimit({trigger_price})")]
273 StopLimit {
274 trigger_price: Decimal,
275 },
276 #[display("TakeProfit({trigger_price})")]
279 TakeProfit {
280 trigger_price: Decimal,
281 },
282 #[display("TakeProfitLimit({trigger_price})")]
284 TakeProfitLimit {
285 trigger_price: Decimal,
286 },
287 #[display("TrailingStop({offset}, {offset_type}, activation: {activation_price:?})")]
289 TrailingStop {
290 offset: Decimal,
291 offset_type: TrailingOffsetType,
292 activation_price: Option<Decimal>,
305 },
306 #[display("TrailingStopLimit({offset}, {offset_type}, {stop_price}, {limit_offset})")]
310 TrailingStopLimit {
311 offset: Decimal,
312 offset_type: TrailingOffsetType,
313 stop_price: Decimal,
321 limit_offset: Decimal,
323 },
324 #[display("TrailingStopFixedLimit({offset}, {offset_type}, activation: {activation_price:?})")]
333 TrailingStopFixedLimit {
334 offset: Decimal,
335 offset_type: TrailingOffsetType,
336 activation_price: Option<Decimal>,
338 },
339}
340
341#[derive(
342 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
343)]
344pub enum TimeInForce {
345 GoodUntilCancelled {
346 post_only: bool,
347 },
348 GoodUntilEndOfDay,
349 FillOrKill,
350 ImmediateOrCancel,
351 #[display("GoodTillDate({expiry})")]
353 GoodTillDate {
354 expiry: DateTime<Utc>,
355 },
356 AtOpen,
358 AtClose,
360}
361
362impl<ExchangeKey, InstrumentKey> Order<ExchangeKey, InstrumentKey, ActiveOrderState>
363where
364 ExchangeKey: Clone,
365 InstrumentKey: Clone,
366{
367 pub fn open_in_flight(
369 request: &OrderRequestOpen<ExchangeKey, InstrumentKey>,
370 time_sent: DateTime<Utc>,
371 ) -> Self {
372 let OrderRequestOpen {
373 key,
374 state:
375 RequestOpen {
376 side,
377 price,
378 quantity,
379 kind,
380 time_in_force,
381 position_id: _,
382 reduce_only: _, market: _, },
385 } = request;
386
387 Self {
388 key: key.clone(),
389 side: *side,
390 price: *price,
391 quantity: *quantity,
392 kind: *kind,
393 time_in_force: *time_in_force,
394 state: ActiveOrderState::OpenInFlight(OpenInFlight::new(time_sent)),
395 }
396 }
397}
398
399impl<ExchangeKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
400 for Order<ExchangeKey, InstrumentKey, ActiveOrderState>
401{
402 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
403 value.map_state(ActiveOrderState::Open)
404 }
405}
406
407impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
408 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
409{
410 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
411 value.map_state(|state| OrderState::Active(ActiveOrderState::Open(state)))
412 }
413}
414
415impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Cancelled>>
416 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
417{
418 fn from(value: Order<ExchangeKey, InstrumentKey, Cancelled>) -> Self {
419 value.map_state(|state| OrderState::Inactive(InactiveOrderState::Cancelled(state)))
420 }
421}
422
423impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Expired>>
424 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
425{
426 fn from(value: Order<ExchangeKey, InstrumentKey, Expired>) -> Self {
427 value.map_state(|state| OrderState::Inactive(InactiveOrderState::Expired(state)))
428 }
429}
430
431#[cfg(test)]
432#[allow(clippy::unwrap_used)] mod tests {
434 use super::*;
435 use rust_decimal_macros::dec;
436
437 #[test]
438 fn order_kind_display_take_profit() {
439 let tp = OrderKind::TakeProfit {
440 trigger_price: dec!(100.50),
441 };
442 assert_eq!(tp.to_string(), "TakeProfit(100.50)");
443 }
444
445 #[test]
446 fn order_kind_display_take_profit_limit() {
447 let tpl = OrderKind::TakeProfitLimit {
448 trigger_price: dec!(200.25),
449 };
450 assert_eq!(tpl.to_string(), "TakeProfitLimit(200.25)");
451 }
452
453 #[test]
454 fn order_kind_serde_roundtrip_take_profit() {
455 let original = OrderKind::TakeProfit {
456 trigger_price: dec!(150.00),
457 };
458 let json = serde_json::to_string(&original).unwrap();
459 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
460 assert_eq!(original, deserialized);
461 }
462
463 #[test]
464 fn order_kind_serde_roundtrip_take_profit_limit() {
465 let original = OrderKind::TakeProfitLimit {
466 trigger_price: dec!(250.75),
467 };
468 let json = serde_json::to_string(&original).unwrap();
469 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
470 assert_eq!(original, deserialized);
471 }
472 #[test]
475 fn trailing_kinds_round_trip_and_a_stored_trailing_stop_loads() {
476 for activation_price in [None, Some(dec!(105))] {
477 for original in [
478 OrderKind::TrailingStop {
479 offset: dec!(1),
480 offset_type: TrailingOffsetType::Percentage,
481 activation_price,
482 },
483 OrderKind::TrailingStopFixedLimit {
484 offset: dec!(25),
485 offset_type: TrailingOffsetType::BasisPoints,
486 activation_price,
487 },
488 ] {
489 let json = serde_json::to_string(&original).unwrap();
490 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
491 assert_eq!(original, deserialized);
492 }
493 }
494 let stored: OrderKind =
495 serde_json::from_str(r#"{"TrailingStop":{"offset":"1","offset_type":"Percentage"}}"#)
496 .unwrap();
497 assert_eq!(
498 stored,
499 OrderKind::TrailingStop {
500 offset: dec!(1),
501 offset_type: TrailingOffsetType::Percentage,
502 activation_price: None,
503 }
504 );
505 assert_eq!(
506 OrderKind::TrailingStop {
507 offset: dec!(1),
508 offset_type: TrailingOffsetType::Percentage,
509 activation_price: Some(dec!(105)),
510 }
511 .to_string(),
512 "TrailingStop(1, Percentage, activation: Some(105))"
513 );
514 }
515}