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::{ActiveOrderState, Cancelled, InactiveOrderState, Open, OpenInFlight, OrderState};
18
19pub mod id;
21
22pub mod state;
26
27pub mod request;
31
32pub mod bracket;
34
35pub type UnindexedOrder = Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>;
37
38pub type UnindexedOrderKey = OrderKey<ExchangeId, InstrumentNameExchange>;
41
42pub type UnindexedOrderSnapshot = Order<
45 ExchangeId,
46 InstrumentNameExchange,
47 OrderState<AssetNameExchange, InstrumentNameExchange>,
48>;
49
50pub type OrderSnapshot<
52 ExchangeKey = ExchangeIndex,
53 AssetKey = AssetIndex,
54 InstrumentKey = InstrumentIndex,
55> = Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>;
56
57#[derive(
58 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
59)]
60
61pub struct OrderEvent<State, ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> {
62 pub key: OrderKey<ExchangeKey, InstrumentKey>,
63 pub state: State,
64}
65
66#[derive(
67 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
68)]
69pub struct OrderKey<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> {
70 pub exchange: ExchangeKey,
71 pub instrument: InstrumentKey,
72 pub strategy: StrategyId,
73 pub cid: ClientOrderId,
74}
75
76#[derive(
77 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
78)]
79pub struct Order<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex, State = OrderState> {
80 pub key: OrderKey<ExchangeKey, InstrumentKey>,
81 pub side: Side,
82 pub price: Option<Decimal>,
84 pub quantity: Decimal,
85 pub kind: OrderKind,
86 pub time_in_force: TimeInForce,
87 pub state: State,
88}
89
90impl<ExchangeKey, AssetKey, InstrumentKey>
91 Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
92{
93 pub fn to_active(&self) -> Option<Order<ExchangeKey, InstrumentKey, ActiveOrderState>>
94 where
95 ExchangeKey: Clone,
96 InstrumentKey: Clone,
97 {
98 let OrderState::Active(state) = &self.state else {
99 return None;
100 };
101
102 Some(Order {
103 key: self.key.clone(),
104 side: self.side,
105 price: self.price,
106 quantity: self.quantity,
107 kind: self.kind,
108 time_in_force: self.time_in_force,
109 state: state.clone(),
110 })
111 }
112
113 pub fn to_inactive(
114 &self,
115 ) -> Option<Order<ExchangeKey, InstrumentKey, InactiveOrderState<AssetKey, InstrumentKey>>>
116 where
117 ExchangeKey: Clone,
118 AssetKey: Clone,
119 InstrumentKey: Clone,
120 {
121 let OrderState::Inactive(state) = &self.state else {
122 return None;
123 };
124
125 Some(Order {
126 key: self.key.clone(),
127 side: self.side,
128 price: self.price,
129 quantity: self.quantity,
130 kind: self.kind,
131 time_in_force: self.time_in_force,
132 state: state.clone(),
133 })
134 }
135}
136
137impl<ExchangeKey, InstrumentKey> Order<ExchangeKey, InstrumentKey, ActiveOrderState>
138where
139 ExchangeKey: Clone,
140 InstrumentKey: Clone,
141{
142 pub fn to_request_cancel(&self) -> Option<OrderRequestCancel<ExchangeKey, InstrumentKey>> {
143 let Order { key, state, .. } = self;
144
145 let request_cancel = match state {
146 ActiveOrderState::OpenInFlight(_) => RequestCancel { id: None },
147 ActiveOrderState::Open(open) => RequestCancel {
148 id: Some(open.id.clone()),
149 },
150 _ => return None,
151 };
152
153 Some(OrderRequestCancel {
154 key: key.clone(),
155 state: request_cancel,
156 })
157 }
158}
159
160#[derive(
167 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
168)]
169pub enum TrailingOffsetType {
170 Absolute,
172 Percentage,
174 BasisPoints,
176}
177
178#[derive(
179 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
180)]
181#[non_exhaustive]
182pub enum OrderKind {
183 Market,
184 Limit,
185 #[display("Stop({trigger_price})")]
187 Stop {
188 trigger_price: Decimal,
189 },
190 #[display("StopLimit({trigger_price})")]
192 StopLimit {
193 trigger_price: Decimal,
194 },
195 #[display("TakeProfit({trigger_price})")]
198 TakeProfit {
199 trigger_price: Decimal,
200 },
201 #[display("TakeProfitLimit({trigger_price})")]
203 TakeProfitLimit {
204 trigger_price: Decimal,
205 },
206 #[display("TrailingStop({offset}, {offset_type})")]
208 TrailingStop {
209 offset: Decimal,
210 offset_type: TrailingOffsetType,
211 },
212 #[display("TrailingStopLimit({offset}, {offset_type}, {limit_offset})")]
214 TrailingStopLimit {
215 offset: Decimal,
216 offset_type: TrailingOffsetType,
217 limit_offset: Decimal,
219 },
220}
221
222#[derive(
223 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
224)]
225pub enum TimeInForce {
226 GoodUntilCancelled {
227 post_only: bool,
228 },
229 GoodUntilEndOfDay,
230 FillOrKill,
231 ImmediateOrCancel,
232 #[display("GoodTillDate({expiry})")]
234 GoodTillDate {
235 expiry: DateTime<Utc>,
236 },
237 AtOpen,
239 AtClose,
241}
242
243impl<ExchangeKey, InstrumentKey> From<&OrderRequestOpen<ExchangeKey, InstrumentKey>>
244 for Order<ExchangeKey, InstrumentKey, ActiveOrderState>
245where
246 ExchangeKey: Clone,
247 InstrumentKey: Clone,
248{
249 fn from(value: &OrderRequestOpen<ExchangeKey, InstrumentKey>) -> Self {
250 let OrderRequestOpen {
251 key,
252 state:
253 RequestOpen {
254 side,
255 price,
256 quantity,
257 kind,
258 time_in_force,
259 position_id: _,
260 reduce_only: _, },
262 } = value;
263
264 Self {
265 key: key.clone(),
266 side: *side,
267 price: *price,
268 quantity: *quantity,
269 kind: *kind,
270 time_in_force: *time_in_force,
271 state: ActiveOrderState::OpenInFlight(OpenInFlight),
272 }
273 }
274}
275
276impl<ExchangeKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
277 for Order<ExchangeKey, InstrumentKey, ActiveOrderState>
278{
279 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
280 let Order {
281 key,
282 side,
283 price,
284 quantity,
285 kind,
286 time_in_force,
287 state,
288 } = value;
289
290 Self {
291 key,
292 side,
293 price,
294 quantity,
295 kind,
296 time_in_force,
297 state: ActiveOrderState::Open(state),
298 }
299 }
300}
301
302impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
303 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
304{
305 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
306 let Order {
307 key,
308 side,
309 price,
310 quantity,
311 kind,
312 time_in_force,
313 state,
314 } = value;
315
316 Self {
317 key,
318 side,
319 price,
320 quantity,
321 kind,
322 time_in_force,
323 state: OrderState::Active(ActiveOrderState::Open(state)),
324 }
325 }
326}
327
328impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Cancelled>>
329 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
330{
331 fn from(value: Order<ExchangeKey, InstrumentKey, Cancelled>) -> Self {
332 let Order {
333 key,
334 side,
335 price,
336 quantity,
337 kind,
338 time_in_force,
339 state,
340 } = value;
341
342 Self {
343 key,
344 side,
345 price,
346 quantity,
347 kind,
348 time_in_force,
349 state: OrderState::Inactive(InactiveOrderState::Cancelled(state)),
350 }
351 }
352}
353
354#[cfg(test)]
355#[allow(clippy::unwrap_used)] mod tests {
357 use super::*;
358 use rust_decimal_macros::dec;
359
360 #[test]
361 fn order_kind_display_take_profit() {
362 let tp = OrderKind::TakeProfit {
363 trigger_price: dec!(100.50),
364 };
365 assert_eq!(tp.to_string(), "TakeProfit(100.50)");
366 }
367
368 #[test]
369 fn order_kind_display_take_profit_limit() {
370 let tpl = OrderKind::TakeProfitLimit {
371 trigger_price: dec!(200.25),
372 };
373 assert_eq!(tpl.to_string(), "TakeProfitLimit(200.25)");
374 }
375
376 #[test]
377 fn order_kind_serde_roundtrip_take_profit() {
378 let original = OrderKind::TakeProfit {
379 trigger_price: dec!(150.00),
380 };
381 let json = serde_json::to_string(&original).unwrap();
382 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
383 assert_eq!(original, deserialized);
384 }
385
386 #[test]
387 fn order_kind_serde_roundtrip_take_profit_limit() {
388 let original = OrderKind::TakeProfitLimit {
389 trigger_price: dec!(250.75),
390 };
391 let json = serde_json::to_string(&original).unwrap();
392 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
393 assert_eq!(original, deserialized);
394 }
395}