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>,
135 pub quantity: Decimal,
136 pub kind: OrderKind,
137 pub time_in_force: TimeInForce,
138 pub state: State,
139}
140
141impl<ExchangeKey, InstrumentKey, State> Order<ExchangeKey, InstrumentKey, State> {
142 pub fn map_state<NewState>(
144 self,
145 f: impl FnOnce(State) -> NewState,
146 ) -> Order<ExchangeKey, InstrumentKey, NewState> {
147 let Order {
148 key,
149 side,
150 price,
151 quantity,
152 kind,
153 time_in_force,
154 state,
155 } = self;
156
157 Order {
158 key,
159 side,
160 price,
161 quantity,
162 kind,
163 time_in_force,
164 state: f(state),
165 }
166 }
167}
168
169impl<ExchangeKey, AssetKey, InstrumentKey>
170 Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
171{
172 pub fn to_active(&self) -> Option<Order<ExchangeKey, InstrumentKey, ActiveOrderState>>
173 where
174 ExchangeKey: Clone,
175 InstrumentKey: Clone,
176 {
177 let OrderState::Active(state) = &self.state else {
178 return None;
179 };
180
181 Some(Order {
182 key: self.key.clone(),
183 side: self.side,
184 price: self.price,
185 quantity: self.quantity,
186 kind: self.kind,
187 time_in_force: self.time_in_force,
188 state: state.clone(),
189 })
190 }
191
192 pub fn to_inactive(
193 &self,
194 ) -> Option<Order<ExchangeKey, InstrumentKey, InactiveOrderState<AssetKey, InstrumentKey>>>
195 where
196 ExchangeKey: Clone,
197 AssetKey: Clone,
198 InstrumentKey: Clone,
199 {
200 let OrderState::Inactive(state) = &self.state else {
201 return None;
202 };
203
204 Some(Order {
205 key: self.key.clone(),
206 side: self.side,
207 price: self.price,
208 quantity: self.quantity,
209 kind: self.kind,
210 time_in_force: self.time_in_force,
211 state: state.clone(),
212 })
213 }
214}
215
216impl<ExchangeKey, InstrumentKey> Order<ExchangeKey, InstrumentKey, ActiveOrderState>
217where
218 ExchangeKey: Clone,
219 InstrumentKey: Clone,
220{
221 pub fn to_request_cancel(&self) -> Option<OrderRequestCancel<ExchangeKey, InstrumentKey>> {
222 let Order { key, state, .. } = self;
223
224 let request_cancel = match state {
225 ActiveOrderState::OpenInFlight(_) => RequestCancel { id: None },
226 ActiveOrderState::Open(open) => RequestCancel {
227 id: Some(open.id.clone()),
228 },
229 _ => return None,
230 };
231
232 Some(OrderRequestCancel {
233 key: key.clone(),
234 state: request_cancel,
235 })
236 }
237}
238
239#[derive(
246 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
247)]
248pub enum TrailingOffsetType {
249 Absolute,
251 Percentage,
253 BasisPoints,
255}
256
257#[derive(
258 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
259)]
260#[non_exhaustive]
261pub enum OrderKind {
262 Market,
263 Limit,
264 #[display("Stop({trigger_price})")]
266 Stop {
267 trigger_price: Decimal,
268 },
269 #[display("StopLimit({trigger_price})")]
271 StopLimit {
272 trigger_price: Decimal,
273 },
274 #[display("TakeProfit({trigger_price})")]
277 TakeProfit {
278 trigger_price: Decimal,
279 },
280 #[display("TakeProfitLimit({trigger_price})")]
282 TakeProfitLimit {
283 trigger_price: Decimal,
284 },
285 #[display("TrailingStop({offset}, {offset_type})")]
287 TrailingStop {
288 offset: Decimal,
289 offset_type: TrailingOffsetType,
290 },
291 #[display("TrailingStopLimit({offset}, {offset_type}, {stop_price}, {limit_offset})")]
295 TrailingStopLimit {
296 offset: Decimal,
297 offset_type: TrailingOffsetType,
298 stop_price: Decimal,
306 limit_offset: Decimal,
308 },
309}
310
311#[derive(
312 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Display,
313)]
314pub enum TimeInForce {
315 GoodUntilCancelled {
316 post_only: bool,
317 },
318 GoodUntilEndOfDay,
319 FillOrKill,
320 ImmediateOrCancel,
321 #[display("GoodTillDate({expiry})")]
323 GoodTillDate {
324 expiry: DateTime<Utc>,
325 },
326 AtOpen,
328 AtClose,
330}
331
332impl<ExchangeKey, InstrumentKey> Order<ExchangeKey, InstrumentKey, ActiveOrderState>
333where
334 ExchangeKey: Clone,
335 InstrumentKey: Clone,
336{
337 pub fn open_in_flight(
339 request: &OrderRequestOpen<ExchangeKey, InstrumentKey>,
340 time_sent: DateTime<Utc>,
341 ) -> Self {
342 let OrderRequestOpen {
343 key,
344 state:
345 RequestOpen {
346 side,
347 price,
348 quantity,
349 kind,
350 time_in_force,
351 position_id: _,
352 reduce_only: _, market: _, },
355 } = request;
356
357 Self {
358 key: key.clone(),
359 side: *side,
360 price: *price,
361 quantity: *quantity,
362 kind: *kind,
363 time_in_force: *time_in_force,
364 state: ActiveOrderState::OpenInFlight(OpenInFlight::new(time_sent)),
365 }
366 }
367}
368
369impl<ExchangeKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
370 for Order<ExchangeKey, InstrumentKey, ActiveOrderState>
371{
372 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
373 value.map_state(ActiveOrderState::Open)
374 }
375}
376
377impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Open>>
378 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
379{
380 fn from(value: Order<ExchangeKey, InstrumentKey, Open>) -> Self {
381 value.map_state(|state| OrderState::Active(ActiveOrderState::Open(state)))
382 }
383}
384
385impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Cancelled>>
386 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
387{
388 fn from(value: Order<ExchangeKey, InstrumentKey, Cancelled>) -> Self {
389 value.map_state(|state| OrderState::Inactive(InactiveOrderState::Cancelled(state)))
390 }
391}
392
393impl<ExchangeKey, AssetKey, InstrumentKey> From<Order<ExchangeKey, InstrumentKey, Expired>>
394 for Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>
395{
396 fn from(value: Order<ExchangeKey, InstrumentKey, Expired>) -> Self {
397 value.map_state(|state| OrderState::Inactive(InactiveOrderState::Expired(state)))
398 }
399}
400
401#[cfg(test)]
402#[allow(clippy::unwrap_used)] mod tests {
404 use super::*;
405 use rust_decimal_macros::dec;
406
407 #[test]
408 fn order_kind_display_take_profit() {
409 let tp = OrderKind::TakeProfit {
410 trigger_price: dec!(100.50),
411 };
412 assert_eq!(tp.to_string(), "TakeProfit(100.50)");
413 }
414
415 #[test]
416 fn order_kind_display_take_profit_limit() {
417 let tpl = OrderKind::TakeProfitLimit {
418 trigger_price: dec!(200.25),
419 };
420 assert_eq!(tpl.to_string(), "TakeProfitLimit(200.25)");
421 }
422
423 #[test]
424 fn order_kind_serde_roundtrip_take_profit() {
425 let original = OrderKind::TakeProfit {
426 trigger_price: dec!(150.00),
427 };
428 let json = serde_json::to_string(&original).unwrap();
429 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
430 assert_eq!(original, deserialized);
431 }
432
433 #[test]
434 fn order_kind_serde_roundtrip_take_profit_limit() {
435 let original = OrderKind::TakeProfitLimit {
436 trigger_price: dec!(250.75),
437 };
438 let json = serde_json::to_string(&original).unwrap();
439 let deserialized: OrderKind = serde_json::from_str(&json).unwrap();
440 assert_eq!(original, deserialized);
441 }
442}