Skip to content

Commit 5590654

Browse files
author
Vadim Nicolai
committed
nautilus-limitiftouched-validation
1 parent 7f47f7c commit 5590654

File tree

1 file changed

+94
-0
lines changed
  • blog/nautilus-limitiftouched-validation

1 file changed

+94
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
slug: nautilus-limitiftouched-validation
3+
title: Contributing a Safer `LimitIfTouchedOrder` to Nautilus Trader — A Small Open-Source Win for Rust Trading
4+
date: 2025-05-03
5+
authors: [nicolad]
6+
---
7+
8+
## Introduction
9+
10+
`LimitIfTouchedOrder` (LIT) is a conditional order that sits between a simple limit order and a stop-limit order: it rests _inactive_ until a **trigger price** is touched, then converts into a plain limit at the specified **limit price**.
11+
Because it straddles two distinct price levels and multiple conditional flags, _robust validation_ is critical—any silent mismatch can manifest as unwanted executions in live trading.
12+
13+
Pull Request [#2533](https://github.com/nautechsystems/nautilus_trader/pull/2533) standardises and hardens the validation logic for LIT orders, bringing it up to the same quality bar as `MarketOrder` and `LimitOrder`. The PR was merged into `develop` on **May 1 2025** by @cjdsellers (+207 / −9 across one file). ([GitHub][1], [GitHub][2])
14+
15+
---
16+
17+
## Why the Change Was Needed
18+
19+
- **Inconsistent invariants**`quantity`, `price`, and `trigger_price` were _not_ always checked for positivity.
20+
- **Edge-case foot-guns**`TimeInForce::Gtd` could be set with a zero `expire_time`, silently turning a “good-til-date” order into “good-til-cancel”.
21+
- **Side/trigger mismatch** – A BUY order with a trigger _above_ the limit price (or SELL with trigger _below_ limit) yielded undefined behaviour.
22+
- **Developer frustration** – Consumers of the SDK had to replicate guard clauses externally; a single canonical constructor removes that burden.
23+
24+
---
25+
26+
## Key Enhancements
27+
28+
| Area | Before | After |
29+
| ----------------- | ---------------------- | ------------------------------------------------------------------------------- |
30+
| Constructor API | `new` (panic-on-error) | `new_checked` (returns `Result`) + `new` now wraps it |
31+
| Positivity checks | Only partial | Guaranteed for `quantity`, `price`, `trigger_price`, and optional `display_qty` |
32+
| Display quantity | Not validated | Must be ≤ `quantity` |
33+
| GTD orders | No expire validation | Must supply `expire_time` when `TimeInForce::Gtd` |
34+
| Side/trigger rule | Undefined | `BUY ⇒ trigger ≤ price`, `SELL ⇒ trigger ≥ price` |
35+
| Unit-tests | 0 dedicated tests | 5 focused tests (happy-path + 4 failure modes) |
36+
37+
---
38+
39+
## Implementation Highlights
40+
41+
1. **`new_checked`** – a fallible constructor returning `anyhow::Result<Self>`. All invariants live here.
42+
2. **Guard helpers** – leverages `check_positive_quantity`, `check_positive_price`, and `check_predicate_false` from `nautilus_core::correctness`.
43+
3. **Legacy behaviour preserved** – the original `new` now calls `new_checked().expect("FAILED")`, so downstream crates that relied on panics keep working.
44+
4. **Concise `Display` impl** – human-readable string that shows side, quantity, instrument, prices, trigger type, TIF, and status for quick debugging.
45+
5. **Test suite** – written with _rstest_; covers `ok`, `quantity_zero`, `gtd_without_expire`, `buy_trigger_gt_price`, and `sell_trigger_lt_price`.
46+
47+
Code diff stats: **207 additions**, **9 deletions**, affecting `crates/model/src/orders/limit_if_touched.rs`. ([GitHub][2])
48+
49+
---
50+
51+
## Impact on Integrators
52+
53+
_If you only called_ `LimitIfTouchedOrder::new` **nothing breaks**—you’ll merely enjoy better error messages if you misuse the API.
54+
For stricter compile-time safety, switch to the new `new_checked` constructor and handle `Result<T>` explicitly.
55+
56+
```rust
57+
let order = LimitIfTouchedOrder::new_checked(
58+
trader_id,
59+
strategy_id,
60+
instrument_id,
61+
client_order_id,
62+
OrderSide::Buy,
63+
qty,
64+
limit_price,
65+
trigger_price,
66+
TriggerType::LastPrice,
67+
TimeInForce::Gtc,
68+
None, // expire_time
69+
false, false, // post_only, reduce_only
70+
false, None, // quote_qty, display_qty
71+
None, None, // emulation_trigger, trigger_instrument_id
72+
None, None, // contingency_type, order_list_id
73+
None, // linked_order_ids
74+
None, // parent_order_id
75+
None, None, // exec_algorithm_id, params
76+
None, // exec_spawn_id
77+
None, // tags
78+
init_id,
79+
ts_init,
80+
)?;
81+
```
82+
83+
---
84+
85+
## Conclusion
86+
87+
PR \[#2533] dramatically reduces the surface area for invalid LIT orders by centralising all domain rules in a single, auditable place.
88+
Whether you’re building discretionary tooling or a fully automated strategy on top of **Nautilus Trader**, you now get _fail-fast_ behaviour with precise error semantics—no more mystery fills in production.
89+
90+
> **Next steps:** adopt `new_checked`, make your own wrappers return `Result`, and enjoy safer trading.
91+
92+
---
93+
94+
[1]: https://github.com/nautechsystems/nautilus_trader/pull/2533 "Improve validations for LimitIfTouchedOrder by nicolad · Pull Request #2533 · nautechsystems/nautilus_trader · GitHub"

0 commit comments

Comments
 (0)