1use std::ops::{Deref, DerefMut};
17
18use indexmap::IndexMap;
19use nautilus_core::{UUID4, UnixNanos};
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use ustr::Ustr;
23
24use super::{Order, OrderAny, OrderCore, OrderError};
25use crate::{
26 enums::{
27 ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide,
28 TimeInForce, TrailingOffsetType, TriggerType,
29 },
30 events::{OrderEventAny, OrderInitialized, OrderUpdated},
31 identifiers::{
32 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
33 StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
34 },
35 types::{Currency, Money, Price, Quantity},
36};
37
38#[derive(Clone, Debug, Serialize, Deserialize)]
39#[cfg_attr(
40 feature = "python",
41 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
42)]
43pub struct LimitIfTouchedOrder {
44 pub price: Price,
45 pub trigger_price: Price,
46 pub trigger_type: TriggerType,
47 pub expire_time: Option<UnixNanos>,
48 pub is_post_only: bool,
49 pub display_qty: Option<Quantity>,
50 pub trigger_instrument_id: Option<InstrumentId>,
51 pub is_triggered: bool,
52 pub ts_triggered: Option<UnixNanos>,
53 core: OrderCore,
54}
55
56impl LimitIfTouchedOrder {
57 #[allow(clippy::too_many_arguments)]
59 pub fn new(
60 trader_id: TraderId,
61 strategy_id: StrategyId,
62 instrument_id: InstrumentId,
63 client_order_id: ClientOrderId,
64 order_side: OrderSide,
65 quantity: Quantity,
66 price: Price,
67 trigger_price: Price,
68 trigger_type: TriggerType,
69 time_in_force: TimeInForce,
70 expire_time: Option<UnixNanos>,
71 post_only: bool,
72 reduce_only: bool,
73 quote_quantity: bool,
74 display_qty: Option<Quantity>,
75 emulation_trigger: Option<TriggerType>,
76 trigger_instrument_id: Option<InstrumentId>,
77 contingency_type: Option<ContingencyType>,
78 order_list_id: Option<OrderListId>,
79 linked_order_ids: Option<Vec<ClientOrderId>>,
80 parent_order_id: Option<ClientOrderId>,
81 exec_algorithm_id: Option<ExecAlgorithmId>,
82 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
83 exec_spawn_id: Option<ClientOrderId>,
84 tags: Option<Vec<Ustr>>,
85 init_id: UUID4,
86 ts_init: UnixNanos,
87 ) -> Self {
88 let init_order = OrderInitialized::new(
89 trader_id,
90 strategy_id,
91 instrument_id,
92 client_order_id,
93 order_side,
94 OrderType::LimitIfTouched,
95 quantity,
96 time_in_force,
97 post_only,
98 reduce_only,
99 quote_quantity,
100 false,
101 init_id,
102 ts_init,
103 ts_init,
104 Some(price),
105 Some(trigger_price),
106 Some(trigger_type),
107 None,
108 None,
109 None,
110 expire_time,
111 display_qty,
112 emulation_trigger,
113 trigger_instrument_id,
114 contingency_type,
115 order_list_id,
116 linked_order_ids,
117 parent_order_id,
118 exec_algorithm_id,
119 exec_algorithm_params,
120 exec_spawn_id,
121 tags,
122 );
123 Self {
124 core: OrderCore::new(init_order),
125 price,
126 trigger_price,
127 trigger_type,
128 expire_time,
129 is_post_only: post_only,
130 display_qty,
131 trigger_instrument_id,
132 is_triggered: false,
133 ts_triggered: None,
134 }
135 }
136}
137
138impl Deref for LimitIfTouchedOrder {
139 type Target = OrderCore;
140
141 fn deref(&self) -> &Self::Target {
142 &self.core
143 }
144}
145
146impl DerefMut for LimitIfTouchedOrder {
147 fn deref_mut(&mut self) -> &mut Self::Target {
148 &mut self.core
149 }
150}
151
152impl Order for LimitIfTouchedOrder {
153 fn into_any(self) -> OrderAny {
154 OrderAny::LimitIfTouched(self)
155 }
156
157 fn status(&self) -> OrderStatus {
158 self.status
159 }
160
161 fn trader_id(&self) -> TraderId {
162 self.trader_id
163 }
164
165 fn strategy_id(&self) -> StrategyId {
166 self.strategy_id
167 }
168
169 fn instrument_id(&self) -> InstrumentId {
170 self.instrument_id
171 }
172
173 fn symbol(&self) -> Symbol {
174 self.instrument_id.symbol
175 }
176
177 fn venue(&self) -> Venue {
178 self.instrument_id.venue
179 }
180
181 fn client_order_id(&self) -> ClientOrderId {
182 self.client_order_id
183 }
184
185 fn venue_order_id(&self) -> Option<VenueOrderId> {
186 self.venue_order_id
187 }
188
189 fn position_id(&self) -> Option<PositionId> {
190 self.position_id
191 }
192
193 fn account_id(&self) -> Option<AccountId> {
194 self.account_id
195 }
196
197 fn last_trade_id(&self) -> Option<TradeId> {
198 self.last_trade_id
199 }
200
201 fn order_side(&self) -> OrderSide {
202 self.side
203 }
204
205 fn order_type(&self) -> OrderType {
206 self.order_type
207 }
208
209 fn quantity(&self) -> Quantity {
210 self.quantity
211 }
212
213 fn time_in_force(&self) -> TimeInForce {
214 self.time_in_force
215 }
216
217 fn expire_time(&self) -> Option<UnixNanos> {
218 self.expire_time
219 }
220
221 fn price(&self) -> Option<Price> {
222 Some(self.price)
223 }
224
225 fn trigger_price(&self) -> Option<Price> {
226 Some(self.trigger_price)
227 }
228
229 fn trigger_type(&self) -> Option<TriggerType> {
230 Some(self.trigger_type)
231 }
232
233 fn liquidity_side(&self) -> Option<LiquiditySide> {
234 self.liquidity_side
235 }
236
237 fn is_post_only(&self) -> bool {
238 self.is_post_only
239 }
240
241 fn is_reduce_only(&self) -> bool {
242 self.is_reduce_only
243 }
244
245 fn is_quote_quantity(&self) -> bool {
246 self.is_quote_quantity
247 }
248
249 fn has_price(&self) -> bool {
250 true
251 }
252
253 fn display_qty(&self) -> Option<Quantity> {
254 self.display_qty
255 }
256
257 fn limit_offset(&self) -> Option<Decimal> {
258 None
259 }
260
261 fn trailing_offset(&self) -> Option<Decimal> {
262 None
263 }
264
265 fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
266 None
267 }
268
269 fn emulation_trigger(&self) -> Option<TriggerType> {
270 self.emulation_trigger
271 }
272
273 fn trigger_instrument_id(&self) -> Option<InstrumentId> {
274 self.trigger_instrument_id
275 }
276
277 fn contingency_type(&self) -> Option<ContingencyType> {
278 self.contingency_type
279 }
280
281 fn order_list_id(&self) -> Option<OrderListId> {
282 self.order_list_id
283 }
284
285 fn linked_order_ids(&self) -> Option<&[ClientOrderId]> {
286 self.linked_order_ids.as_deref()
287 }
288
289 fn parent_order_id(&self) -> Option<ClientOrderId> {
290 self.parent_order_id
291 }
292
293 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
294 self.exec_algorithm_id
295 }
296
297 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>> {
298 self.exec_algorithm_params.as_ref()
299 }
300
301 fn exec_spawn_id(&self) -> Option<ClientOrderId> {
302 self.exec_spawn_id
303 }
304
305 fn tags(&self) -> Option<&[Ustr]> {
306 self.tags.as_deref()
307 }
308
309 fn filled_qty(&self) -> Quantity {
310 self.filled_qty
311 }
312
313 fn leaves_qty(&self) -> Quantity {
314 self.leaves_qty
315 }
316
317 fn avg_px(&self) -> Option<f64> {
318 self.avg_px
319 }
320
321 fn slippage(&self) -> Option<f64> {
322 self.slippage
323 }
324
325 fn init_id(&self) -> UUID4 {
326 self.init_id
327 }
328
329 fn ts_init(&self) -> UnixNanos {
330 self.ts_init
331 }
332
333 fn ts_submitted(&self) -> Option<UnixNanos> {
334 self.ts_submitted
335 }
336
337 fn ts_accepted(&self) -> Option<UnixNanos> {
338 self.ts_accepted
339 }
340
341 fn ts_closed(&self) -> Option<UnixNanos> {
342 self.ts_closed
343 }
344
345 fn ts_last(&self) -> UnixNanos {
346 self.ts_last
347 }
348
349 fn commissions(&self) -> &IndexMap<Currency, Money> {
350 &self.commissions
351 }
352
353 fn events(&self) -> Vec<&OrderEventAny> {
354 self.events.iter().collect()
355 }
356
357 fn venue_order_ids(&self) -> Vec<&VenueOrderId> {
358 self.venue_order_ids.iter().collect()
359 }
360
361 fn trade_ids(&self) -> Vec<&TradeId> {
362 self.trade_ids.iter().collect()
363 }
364
365 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
366 if let OrderEventAny::Updated(ref event) = event {
367 self.update(event);
368 };
369 let is_order_filled = matches!(event, OrderEventAny::Filled(_));
370
371 self.core.apply(event)?;
372
373 if is_order_filled {
374 self.core.set_slippage(self.price);
375 };
376
377 Ok(())
378 }
379
380 fn update(&mut self, event: &OrderUpdated) {
381 if let Some(price) = event.price {
382 self.price = price;
383 }
384
385 if let Some(trigger_price) = event.trigger_price {
386 self.trigger_price = trigger_price;
387 }
388
389 self.quantity = event.quantity;
390 self.leaves_qty = self.quantity - self.filled_qty;
391 }
392
393 fn is_triggered(&self) -> Option<bool> {
394 Some(self.is_triggered)
395 }
396
397 fn set_position_id(&mut self, position_id: Option<PositionId>) {
398 self.position_id = position_id;
399 }
400
401 fn set_quantity(&mut self, quantity: Quantity) {
402 self.quantity = quantity;
403 }
404
405 fn set_leaves_qty(&mut self, leaves_qty: Quantity) {
406 self.leaves_qty = leaves_qty;
407 }
408
409 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>) {
410 self.emulation_trigger = emulation_trigger;
411 }
412
413 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool) {
414 self.is_quote_quantity = is_quote_quantity;
415 }
416
417 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide) {
418 self.liquidity_side = Some(liquidity_side)
419 }
420
421 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
422 self.core.would_reduce_only(side, position_qty)
423 }
424
425 fn previous_status(&self) -> Option<OrderStatus> {
426 self.core.previous_status
427 }
428}
429
430impl From<OrderInitialized> for LimitIfTouchedOrder {
431 fn from(event: OrderInitialized) -> Self {
432 Self::new(
433 event.trader_id,
434 event.strategy_id,
435 event.instrument_id,
436 event.client_order_id,
437 event.order_side,
438 event.quantity,
439 event
440 .price .expect("Error initializing order: `price` was `None` for `LimitIfTouchedOrder"),
442 event
443 .trigger_price .expect(
445 "Error initializing order: `trigger_price` was `None` for `LimitIfTouchedOrder",
446 ),
447 event
448 .trigger_type
449 .expect("Error initializing order: `trigger_type` was `None`"),
450 event.time_in_force,
451 event.expire_time,
452 event.post_only,
453 event.reduce_only,
454 event.quote_quantity,
455 event.display_qty,
456 event.emulation_trigger,
457 event.trigger_instrument_id,
458 event.contingency_type,
459 event.order_list_id,
460 event.linked_order_ids,
461 event.parent_order_id,
462 event.exec_algorithm_id,
463 event.exec_algorithm_params,
464 event.exec_spawn_id,
465 event.tags,
466 event.event_id,
467 event.ts_event,
468 )
469 }
470}