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 UnindexedOrderSnapshot = Order<
47 ExchangeId,
48 InstrumentNameExchange,
49 OrderState<AssetNameExchange, InstrumentNameExchange>,
50>;
51
52pub type OrderSnapshot<
54 ExchangeKey = ExchangeIndex,
55 AssetKey = AssetIndex,
56 InstrumentKey = InstrumentIndex,
57> = Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>;
58
59#[derive(
60 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
61)]
62
63pub struct OrderEvent<State, ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> {
64 pub key: OrderKey<ExchangeKey, InstrumentKey>,
65 pub state: State,
66}
67
68#[derive(
69 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
70)]
71pub struct OrderKey<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> {
72 pub exchange: ExchangeKey,
73 pub instrument: InstrumentKey,
74 pub strategy: StrategyId,
75 pub cid: ClientOrderId,
76}
77
78impl<ExchangeKey, InstrumentKey> OrderKey<ExchangeKey, &InstrumentKey>
79where
80 InstrumentKey: Clone,
81{
82 pub fn into_owned_instrument(self) -> OrderKey<ExchangeKey, InstrumentKey> {
89 let Self {
90 exchange,
91 instrument,
92 strategy,
93 cid,
94 } = self;
95
96 OrderKey {
97 exchange,
98 instrument: instrument.clone(),
99 strategy,
100 cid,
101 }
102 }
103}
104
105impl<State, ExchangeKey, InstrumentKey> OrderEvent<State, ExchangeKey, &InstrumentKey>
106where
107 InstrumentKey: Clone,
108{
109 pub fn into_owned_instrument(self) -> OrderEvent<State, ExchangeKey, InstrumentKey> {
113 let Self { key, state } = self;
114
115 OrderEvent {
116 key: key.into_owned_instrument(),
117 state,
118 }
119 }
120}
121
122#[derive(
123 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
124)]
125pub struct Order<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex, State = OrderState> {
126 pub key: OrderKey<ExchangeKey, InstrumentKey>,
127 pub side: Side,
128 pub price: Option<Decimal>,
130 pub quantity: Decimal,
131 pub kind: OrderKind,
132 pub time_in_force: TimeInForce,
133 pub state: State,
134}
135
136impl<ExchangeKey, AssetKey, InstrumentKey>
137 Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
138{
139 pub fn to_active(&self) -> Option<Order<ExchangeKey, InstrumentKey, ActiveOrderState>>
140 where
141 ExchangeKey: Clone,
142 InstrumentKey: Clone,
143 {
144 let OrderState::Active(state) = &self.state else {
145 return None;
146 };
147
148 Some(Order {
149 key: self.key.clone(),
150 side: self.side,
151 price: self.price,
152 quantity: self.quantity,
153 kind: self.kind,
154 time_in_force: self.time_in_force,
155 state: state.clone(),
156 })
157 }
158
159 pub fn to_inactive(
160 &self,
161 ) -> Option<Order<ExchangeKey, InstrumentKey, InactiveOrderState<AssetKey, InstrumentKey>>>
162 where
163 ExchangeKey: Clone,
164 AssetKey: Clone,
165 InstrumentKey: Clone,
166 {
167 let OrderState::Inactive(state) = &self.state else {
168 return None;
169 };
170
171 Some(Order {
172 key: self.key.clone(),
173 side: self.side,
174 price: self.price,
175 quantity: self.quantity,
176 kind: self.kind,
177 time_in_force: self.time_in_force,
178 state: state.clone(),
179 })
180 }
181}
182
183impl<ExchangeKey, InstrumentKey> Order<ExchangeKey, InstrumentKey, ActiveOrderState>
184where
185 ExchangeKey: Clone,
186 InstrumentKey: Clone,
187{
188 pub fn to_request_cancel(&self) -> Option<OrderRequestCancel<ExchangeKey, InstrumentKey>> {
189 let Order { key, state, .. } = self;
190
191 let request_cancel = match state {
192 ActiveOrderState::OpenInFlight(_) => RequestCancel { id: None },
193 ActiveOrderState::Open(open) => RequestCancel {
194 id: Some(open.id.clone()),
195 },
196 _ => return None,
197 };
198
199 Some(OrderRequestCancel {
200 key: key.clone(),
201 state: request_cancel,
202 })
203 }
204}
205
206#[derive(
213 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
214)]
215pub enum TrailingOffsetType {
216 Absolute,
218 Percentage,
220 BasisPoints,
222}
223
224#[derive(
225 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
226)]
227#[non_exhaustive]
228pub enum OrderKind {
229 Market,
230 Limit,
231 #[display("Stop({trigger_price})")]
233 Stop {
234 trigger_price: Decimal,
235 },
236 #[display("StopLimit({trigger_price})")]
238 StopLimit {
239 trigger_price: Decimal,
240 },
241 #[display("TakeProfit({trigger_price})")]
244 TakeProfit {
245 trigger_price: Decimal,
246 },
247 #[display("TakeProfitLimit({trigger_price})")]
249 TakeProfitLimit {
250 trigger_price: Decimal,
251 },
252 #[display("TrailingStop({offset}, {offset_type})")]
254 TrailingStop {
255 offset: Decimal,
256 offset_type: TrailingOffsetType,
257 },
258 #[display("TrailingStopLimit({offset}, {offset_type}, {limit_offset})")]
260 TrailingStopLimit {
261 offset: Decimal,
262 offset_type: TrailingOffsetType,
263 limit_offset: Decimal,
265 },
266}
267
268#[derive(
269 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
270)]
271pub enum TimeInForce {
272 GoodUntilCancelled {
273 post_only: bool,
274 },
275 GoodUntilEndOfDay,
276 FillOrKill,
277 ImmediateOrCancel,
278 #[display("GoodTillDate({expiry})")]
280 GoodTillDate {
281 expiry: DateTime<Utc>,
282 },
283 AtOpen,
285 AtClose,
287}
288
289impl<ExchangeKey, InstrumentKey> From<&OrderRequestOpen<ExchangeKey, InstrumentKey>>
290 for Order<ExchangeKey, InstrumentKey, ActiveOrderState>
291where
292 ExchangeKey: Clone,
293 InstrumentKey: Clone,
294{
295 fn from(value: &OrderRequestOpen<ExchangeKey, InstrumentKey>) -> Self {
296 let OrderRequestOpen {
297 key,
298 state:
299 RequestOpen {
300 side,
301 price,
302 quantity,
303 kind,
304 time_in_force,
305 position_id: _,
306 reduce_only: _, market: _, },
309 } = value;
310
311 Self {
312 key: key.clone(),
313 side: *side,
314 price: *price,
315 quantity: *quantity,
316 kind: *kind,
317 time_in_force: *time_in_force,
318 state: ActiveOrderState::OpenInFlight(OpenInFlight),
319 }
320 }
321}
322
323impl<ExchangeKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
324 for Order<ExchangeKey, InstrumentKey, ActiveOrderState>
325{
326 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
327 let Order {
328 key,
329 side,
330 price,
331 quantity,
332 kind,
333 time_in_force,
334 state,
335 } = value;
336
337 Self {
338 key,
339 side,
340 price,
341 quantity,
342 kind,
343 time_in_force,
344 state: ActiveOrderState::Open(state),
345 }
346 }
347}
348
349impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
350 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
351{
352 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
353 let Order {
354 key,
355 side,
356 price,
357 quantity,
358 kind,
359 time_in_force,
360 state,
361 } = value;
362
363 Self {
364 key,
365 side,
366 price,
367 quantity,
368 kind,
369 time_in_force,
370 state: OrderState::Active(ActiveOrderState::Open(state)),
371 }
372 }
373}
374
375impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Cancelled>>
376 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
377{
378 fn from(value: Order<ExchangeKey, InstrumentKey, Cancelled>) -> Self {
379 let Order {
380 key,
381 side,
382 price,
383 quantity,
384 kind,
385 time_in_force,
386 state,
387 } = value;
388
389 Self {
390 key,
391 side,
392 price,
393 quantity,
394 kind,
395 time_in_force,
396 state: OrderState::Inactive(InactiveOrderState::Cancelled(state)),
397 }
398 }
399}
400
401impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Expired>>
402 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
403{
404 fn from(value: Order<ExchangeKey, InstrumentKey, Expired>) -> Self {
405 let Order {
406 key,
407 side,
408 price,
409 quantity,
410 kind,
411 time_in_force,
412 state,
413 } = value;
414
415 Self {
416 key,
417 side,
418 price,
419 quantity,
420 kind,
421 time_in_force,
422 state: OrderState::Inactive(InactiveOrderState::Expired(state)),
423 }
424 }
425}
426
427#[cfg(test)]
428#[allow(clippy::unwrap_used)] mod tests {
430 use super::*;
431 use rust_decimal_macros::dec;
432
433 #[test]
434 fn order_kind_display_take_profit() {
435 let tp = OrderKind::TakeProfit {
436 trigger_price: dec!(100.50),
437 };
438 assert_eq!(tp.to_string(), "TakeProfit(100.50)");
439 }
440
441 #[test]
442 fn order_kind_display_take_profit_limit() {
443 let tpl = OrderKind::TakeProfitLimit {
444 trigger_price: dec!(200.25),
445 };
446 assert_eq!(tpl.to_string(), "TakeProfitLimit(200.25)");
447 }
448
449 #[test]
450 fn order_kind_serde_roundtrip_take_profit() {
451 let original = OrderKind::TakeProfit {
452 trigger_price: dec!(150.00),
453 };
454 let json = serde_json::to_string(&original).unwrap();
455 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
456 assert_eq!(original, deserialized);
457 }
458
459 #[test]
460 fn order_kind_serde_roundtrip_take_profit_limit() {
461 let original = OrderKind::TakeProfitLimit {
462 trigger_price: dec!(250.75),
463 };
464 let json = serde_json::to_string(&original).unwrap();
465 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
466 assert_eq!(original, deserialized);
467 }
468}