Skip to main content

rustrade_execution/
rules.rs

1//! An instrument's order rules (tick, lot step, minimums and maximums) and the check of an order
2//! against them before it is sent.
3//!
4//! A venue publishes, per instrument, the grid a price and a quantity must sit on and the range
5//! each must fall in. A client that loads those rules refuses an order that breaks them without
6//! sending it, so the caller gets a typed error that says which value and which limit
7//! ([`OrderError::InvalidPrecision`] for the grid, [`OrderError::OutOfBounds`] for the range)
8//! rather than a venue's text after a round trip. It never rounds: rounding would place a
9//! different order from the one asked for. A caller that sizes or prices an order puts it on the
10//! grid first, with [`ValueRule::round_down`] / [`ValueRule::round_up`], or
11//! [`OrderRules::round_quantity_down`] / [`OrderRules::round_quantity_up`] for a quantity.
12//!
13//! A pass is not a guarantee. The check covers the rules a client has loaded, from the last time
14//! it loaded them, and only the ones that can be read off the request. The venue has the last say.
15
16use crate::{
17    error::{Bound, BoundsViolation, OrderError, OrderField, PrecisionLimit, PrecisionViolation},
18    order::{OrderKind, TrailingOffsetType},
19};
20use rust_decimal::Decimal;
21use rustrade_instrument::Side;
22use serde::{Deserialize, Serialize};
23
24/// Whether a client checks an order against its instrument's [`OrderRules`] before sending it.
25#[non_exhaustive]
26#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
27#[serde(rename_all = "snake_case")]
28pub enum PreSendRules {
29    /// Refuse an order that breaks a rule, unsent. The default: a refusal costs no request, and
30    /// carries the limit that was broken.
31    #[default]
32    Enforce,
33    /// Send every order and leave the rules to the venue. Choose it when the loaded rules could
34    /// be wrong, for example because the venue changed them since they were loaded.
35    Off,
36}
37
38/// The grid and range of one kind of value: a price, a quantity or a notional.
39///
40/// Each part is optional. A venue that publishes a zero for one means the rule is off, and
41/// [`ValueRule::new`] reads zero that way.
42#[non_exhaustive]
43#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
44pub struct ValueRule {
45    /// The smallest value allowed.
46    pub min: Option<Decimal>,
47    /// The largest value allowed.
48    pub max: Option<Decimal>,
49    /// The value must be a whole multiple of this, counted from zero.
50    pub step: Option<Decimal>,
51}
52
53impl ValueRule {
54    /// A rule from the venue's `min`, `max` and `step`, each off when zero or negative.
55    pub fn new(min: Decimal, max: Decimal, step: Decimal) -> Self {
56        let on = |value: Decimal| (value > Decimal::ZERO).then_some(value);
57        Self {
58            min: on(min),
59            max: on(max),
60            step: on(step),
61        }
62    }
63
64    /// Whether every part is off.
65    pub fn is_off(&self) -> bool {
66        self.min.is_none() && self.max.is_none() && self.step.is_none()
67    }
68
69    /// The largest multiple of [`step`](Self::step) at or below `value`, counted from zero: the
70    /// grid the pre-send check uses. A value on the grid comes back unchanged, and so does any
71    /// value when there is no step.
72    ///
73    /// It does not clamp to [`min`](Self::min) or [`max`](Self::max): rounding down can take a
74    /// value below the minimum, down to zero, so check the result before sending it.
75    ///
76    /// A value already on the grid comes back as it is. One that has to move takes the scale of
77    /// `value` or `step`, whichever is finer, so it may carry trailing zeros: `0.19000` rounds
78    /// down to `0.15000` on a step of `0.05`.
79    ///
80    /// Returns `None` only when a [`Decimal`] cannot hold the result: within a step of the ends
81    /// of its range, or where the result needs more digits than it has.
82    #[must_use]
83    pub fn round_down(&self, value: Decimal) -> Option<Decimal> {
84        round_down_to(value, self.grid())
85    }
86
87    /// The smallest multiple of [`step`](Self::step) at or above `value`, counted from zero. As
88    /// [`round_down`](Self::round_down) otherwise: rounding up can take a value above the maximum.
89    ///
90    /// Returns `None` only when a [`Decimal`] cannot hold the result, as for `round_down`.
91    #[must_use]
92    pub fn round_up(&self, value: Decimal) -> Option<Decimal> {
93        round_up_to(value, self.grid())
94    }
95
96    /// The step when it makes a grid. A step set to zero or below by hand, not through `new`, is
97    /// no grid: `%` would panic on zero.
98    fn grid(&self) -> Option<Decimal> {
99        self.step.filter(|step| *step > Decimal::ZERO)
100    }
101
102    /// `Ok` when `value` is on the grid and in the range, else the first rule it breaks, grid
103    /// before range.
104    fn check<AssetKey, InstrumentKey>(
105        &self,
106        field: OrderField,
107        value: Decimal,
108    ) -> Result<(), OrderError<AssetKey, InstrumentKey>> {
109        if let Some(step) = self.grid()
110            && !(value % step).is_zero()
111        {
112            return Err(OrderError::InvalidPrecision(PrecisionViolation::new(
113                field,
114                value,
115                PrecisionLimit::NotOnGrid { step },
116            )));
117        }
118        if let Some(min) = self.min.filter(|min| value < *min) {
119            return Err(OrderError::OutOfBounds(BoundsViolation::new(
120                field,
121                Bound::Min,
122                min,
123                value,
124            )));
125        }
126        if let Some(max) = self.max.filter(|max| value > *max) {
127            return Err(OrderError::OutOfBounds(BoundsViolation::new(
128                field,
129                Bound::Max,
130                max,
131                value,
132            )));
133        }
134        Ok(())
135    }
136}
137
138/// The largest multiple of `step` at or below `value`, or `value` itself with no step.
139fn round_down_to(value: Decimal, step: Option<Decimal>) -> Option<Decimal> {
140    let Some(step) = step else {
141        return Some(value);
142    };
143    // `%` takes the sign of `value`, so `value - remainder` is the neighbour toward zero.
144    let remainder = value % step;
145    if remainder.is_zero() {
146        return Some(value);
147    }
148    let toward_zero = value - remainder;
149    let rounded = if remainder.is_sign_negative() {
150        toward_zero.checked_sub(step)?
151    } else {
152        toward_zero
153    };
154    on_grid(rounded, step)
155}
156
157/// The smallest multiple of `step` at or above `value`, or `value` itself with no step.
158fn round_up_to(value: Decimal, step: Option<Decimal>) -> Option<Decimal> {
159    let Some(step) = step else {
160        return Some(value);
161    };
162    let remainder = value % step;
163    if remainder.is_zero() {
164        return Some(value);
165    }
166    let toward_zero = value - remainder;
167    let rounded = if remainder.is_sign_negative() {
168        toward_zero
169    } else {
170        toward_zero.checked_add(step)?
171    };
172    on_grid(rounded, step)
173}
174
175/// `value` if it is a multiple of `step`. A difference that needs more digits than a `Decimal`
176/// holds is rounded, which can take it off the grid: then there is no answer.
177fn on_grid(value: Decimal, step: Decimal) -> Option<Decimal> {
178    (value % step).is_zero().then_some(value)
179}
180
181/// The smallest step whose grid lies on both `a`'s and `b`'s, their least common multiple, or
182/// `None` when a `Decimal` cannot hold it. Both must be positive.
183fn common_step(a: Decimal, b: Decimal) -> Option<Decimal> {
184    // As whole numbers of the finer unit, `10^-scale`.
185    let scale = a.scale().max(b.scale());
186    let whole = |step: Decimal| {
187        10_i128
188            .checked_pow(scale - step.scale())
189            .and_then(|factor| step.mantissa().checked_mul(factor))
190    };
191    let (a, b) = (whole(a)?, whole(b)?);
192    let (mut x, mut y) = (a, b);
193    while y != 0 {
194        (x, y) = (y, x % y);
195    }
196    let lcm = (a / x).checked_mul(b)?;
197    Decimal::try_from_i128_with_scale(lcm, scale)
198        .ok()
199        .map(|step| step.normalize())
200}
201
202/// One instrument's order rules.
203///
204/// Venue-agnostic: a client fills it from its venue's rules and calls [`check`](Self::check).
205/// [`Default`] is no rules at all, which passes every order.
206#[non_exhaustive]
207#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
208pub struct OrderRules {
209    /// The grid and range of a limit, stop and trigger price, and of a trailing order's
210    /// activation price.
211    pub price: ValueRule,
212    /// The grid and range of every order's quantity.
213    pub quantity: ValueRule,
214    /// The grid and range of a market order's quantity, on top of [`quantity`](Self::quantity).
215    pub market_quantity: ValueRule,
216    /// The range of the notional, a price times a quantity. Its `step` is not used.
217    pub notional: ValueRule,
218    /// The part of [`notional`](Self::notional) the venue also applies to a market order: off
219    /// where the venue exempts market orders. Its `step` is not used.
220    ///
221    /// [`check`](Self::check) never applies it: a market order has no price of its own, and the
222    /// venue measures its notional at a price of its own choosing, such as a reference price or
223    /// an average of recent trades, that the check cannot know. A caller that holds a price it
224    /// trusts can compare that price times the quantity with this rule's `min` and `max`.
225    #[serde(default)]
226    pub market_notional: ValueRule,
227    /// The range of a trailing order's offset, in basis points, when its trigger trails above the
228    /// market and fires on a rise: a buy trailing stop. Its `step` is not used.
229    #[serde(default)]
230    pub trailing_offset_above: ValueRule,
231    /// The range of a trailing order's offset, in basis points, when its trigger trails below the
232    /// market and fires on a fall: a sell trailing stop. Its `step` is not used.
233    #[serde(default)]
234    pub trailing_offset_below: ValueRule,
235}
236
237impl OrderRules {
238    /// Rules with `price`, `quantity`, `market_quantity` and `notional` as given, and the rest
239    /// off: add them with [`with_market_notional`](Self::with_market_notional) and
240    /// [`with_trailing_offsets`](Self::with_trailing_offsets).
241    pub fn new(
242        price: ValueRule,
243        quantity: ValueRule,
244        market_quantity: ValueRule,
245        notional: ValueRule,
246    ) -> Self {
247        Self {
248            price,
249            quantity,
250            market_quantity,
251            notional,
252            market_notional: ValueRule::default(),
253            trailing_offset_above: ValueRule::default(),
254            trailing_offset_below: ValueRule::default(),
255        }
256    }
257
258    /// These rules with [`market_notional`](Self::market_notional) as given.
259    #[must_use]
260    pub fn with_market_notional(self, market_notional: ValueRule) -> Self {
261        Self {
262            market_notional,
263            ..self
264        }
265    }
266
267    /// These rules with [`trailing_offset_above`](Self::trailing_offset_above) and
268    /// [`trailing_offset_below`](Self::trailing_offset_below) as given.
269    #[must_use]
270    pub fn with_trailing_offsets(self, above: ValueRule, below: ValueRule) -> Self {
271        Self {
272            trailing_offset_above: above,
273            trailing_offset_below: below,
274            ..self
275        }
276    }
277
278    /// Whether these rules check nothing.
279    pub fn is_off(&self) -> bool {
280        self.price.is_off()
281            && self.quantity.is_off()
282            && self.market_quantity.is_off()
283            && self.notional.is_off()
284            && self.market_notional.is_off()
285            && self.trailing_offset_above.is_off()
286            && self.trailing_offset_below.is_off()
287    }
288
289    /// The largest quantity at or below `quantity` that an order of `kind` may have on the grid:
290    /// a multiple of [`quantity`](Self::quantity)'s step and, for a market order, of
291    /// [`market_quantity`](Self::market_quantity)'s too, so a multiple of the smallest step both
292    /// grids share. As [`ValueRule::round_down`] otherwise: it does not clamp to either rule's
293    /// range, so check the result before sending it.
294    ///
295    /// Returns `None` when a [`Decimal`] cannot hold the result, as for
296    /// [`ValueRule::round_down`], or cannot hold the step of the grid the two steps share.
297    #[must_use]
298    pub fn round_quantity_down(&self, kind: OrderKind, quantity: Decimal) -> Option<Decimal> {
299        round_down_to(quantity, self.quantity_grid(kind)?)
300    }
301
302    /// The smallest quantity at or above `quantity` that an order of `kind` may have on the grid.
303    /// As [`round_quantity_down`](Self::round_quantity_down) otherwise.
304    #[must_use]
305    pub fn round_quantity_up(&self, kind: OrderKind, quantity: Decimal) -> Option<Decimal> {
306        round_up_to(quantity, self.quantity_grid(kind)?)
307    }
308
309    /// The step of the grid an order of `kind`'s quantity must sit on: `Some(None)` when there is
310    /// none, `None` when a `Decimal` cannot hold the step of the grid the two steps share.
311    fn quantity_grid(&self, kind: OrderKind) -> Option<Option<Decimal>> {
312        let quantity = self.quantity.grid();
313        if !matches!(kind, OrderKind::Market) {
314            return Some(quantity);
315        }
316        match (quantity, self.market_quantity.grid()) {
317            (Some(a), Some(b)) => common_step(a, b).map(Some),
318            (step, None) | (None, step) => Some(step),
319        }
320    }
321
322    /// Check an order on `side` of `kind`, with its limit `price` and `quantity`, against the
323    /// rules.
324    ///
325    /// Returns the first rule the order breaks, or `Ok` if it breaks none, checking in this order:
326    /// - **Prices**, the grid and then the range, each on its own value: the limit `price` of a
327    ///   limit, stop-limit, take-profit-limit or fixed-limit trailing order; the trigger price of
328    ///   a stop, stop-limit, take-profit or take-profit-limit order; and a trailing order's
329    ///   activation price when it has one. A market order has no price to check.
330    /// - **Trailing offset**, of a trailing stop of any kind: a buy's against
331    ///   [`trailing_offset_above`](Self::trailing_offset_above), a sell's against
332    ///   [`trailing_offset_below`](Self::trailing_offset_below). An offset in
333    ///   [`Percentage`](TrailingOffsetType::Percentage) is compared in percent, the limits divided
334    ///   by 100, so a refusal names both in the order's own unit. An
335    ///   [`Absolute`](TrailingOffsetType::Absolute) offset is not checked: its size in basis
336    ///   points depends on a price the check does not have.
337    /// - **Quantity**, against [`quantity`](Self::quantity) and, for a market order, then
338    ///   [`market_quantity`](Self::market_quantity).
339    /// - **Notional**, `price × quantity`, only where the price is the order's own limit: a limit,
340    ///   stop-limit, take-profit-limit or fixed-limit trailing order. Never a market, plain stop
341    ///   or trailing-stop order: the venue prices them at a reference price this check cannot
342    ///   know, which is also why [`market_notional`](Self::market_notional) is never checked. An
343    ///   order with no `price` skips it.
344    ///
345    /// A closing order is checked like any other: a venue that enforces a minimum notional
346    /// enforces it on a close too, so an exemption would move the refusal to the venue.
347    ///
348    /// # Errors
349    /// [`OrderError::InvalidPrecision`] with [`PrecisionLimit::NotOnGrid`] for a value off its
350    /// grid, [`OrderError::OutOfBounds`] for one outside its range.
351    pub fn check<AssetKey, InstrumentKey>(
352        &self,
353        side: Side,
354        kind: OrderKind,
355        price: Option<Decimal>,
356        quantity: Decimal,
357    ) -> Result<(), OrderError<AssetKey, InstrumentKey>> {
358        // A match with no wildcard, so that a new `OrderKind` fails to compile until it is
359        // classified here.
360        let (limit_price, trigger_price, has_own_limit) = match kind {
361            OrderKind::Market => (None, None, false),
362            OrderKind::Limit => (price, None, true),
363            OrderKind::Stop { trigger_price } | OrderKind::TakeProfit { trigger_price } => {
364                (None, Some(trigger_price), false)
365            }
366            OrderKind::StopLimit { trigger_price }
367            | OrderKind::TakeProfitLimit { trigger_price } => (price, Some(trigger_price), true),
368            OrderKind::TrailingStop {
369                activation_price, ..
370            } => (None, activation_price, false),
371            OrderKind::TrailingStopFixedLimit {
372                activation_price, ..
373            } => (price, activation_price, true),
374            // The venue prices it from its stop and offset: there is no price to read.
375            OrderKind::TrailingStopLimit { .. } => (None, None, false),
376            // Never placed, but its prices are the venue's and so on its grids.
377            OrderKind::ConditionalUnresolved {
378                family,
379                trigger_price,
380            } => (
381                price.filter(|_| family.has_limit_price()),
382                trigger_price,
383                family.has_limit_price(),
384            ),
385        };
386
387        if let Some(price) = limit_price {
388            self.price.check(OrderField::Price, price)?;
389        }
390        if let Some(trigger) = trigger_price {
391            self.price.check(OrderField::TriggerPrice, trigger)?;
392        }
393        self.check_trailing_offset(side, kind)?;
394        self.quantity.check(OrderField::Quantity, quantity)?;
395        if matches!(kind, OrderKind::Market) {
396            self.market_quantity.check(OrderField::Quantity, quantity)?;
397        }
398        if let Some(price) = limit_price.filter(|_| has_own_limit) {
399            // A product beyond `Decimal`'s range is beyond any maximum too.
400            let notional = price.saturating_mul(quantity);
401            self.notional.check(OrderField::Notional, notional)?;
402        }
403        Ok(())
404    }
405
406    /// Check a trailing order's offset against the range for its direction: see
407    /// [`check`](Self::check).
408    fn check_trailing_offset<AssetKey, InstrumentKey>(
409        &self,
410        side: Side,
411        kind: OrderKind,
412    ) -> Result<(), OrderError<AssetKey, InstrumentKey>> {
413        let (offset, offset_type) = match kind {
414            OrderKind::TrailingStop {
415                offset,
416                offset_type,
417                ..
418            }
419            | OrderKind::TrailingStopLimit {
420                offset,
421                offset_type,
422                ..
423            }
424            | OrderKind::TrailingStopFixedLimit {
425                offset,
426                offset_type,
427                ..
428            } => (offset, offset_type),
429            // No offset to check, and never placed: an unresolved kind's offset is not reported.
430            OrderKind::Market
431            | OrderKind::Limit
432            | OrderKind::Stop { .. }
433            | OrderKind::StopLimit { .. }
434            | OrderKind::TakeProfit { .. }
435            | OrderKind::TakeProfitLimit { .. }
436            | OrderKind::ConditionalUnresolved { .. } => return Ok(()),
437        };
438        // Every trailing kind is a stop: a buy's trigger trails above the market, a sell's below.
439        let basis_points = match side {
440            Side::Buy => &self.trailing_offset_above,
441            Side::Sell => &self.trailing_offset_below,
442        };
443        // A percent is 100 basis points. Dividing by 100 cannot overflow.
444        let percent = |bound: Option<Decimal>| bound.map(|bound| bound / Decimal::ONE_HUNDRED);
445        // The range alone: the step is not used.
446        let range = match offset_type {
447            TrailingOffsetType::BasisPoints => ValueRule {
448                step: None,
449                ..*basis_points
450            },
451            TrailingOffsetType::Percentage => ValueRule {
452                min: percent(basis_points.min),
453                max: percent(basis_points.max),
454                step: None,
455            },
456            TrailingOffsetType::Absolute => return Ok(()),
457        };
458        range.check(OrderField::TrailingOffset, offset)
459    }
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465    use crate::order::{ConditionalFamily, TrailingOffsetType};
466    use rust_decimal_macros::dec;
467
468    type Error = OrderError<(), ()>;
469
470    /// BTCUSDT-like: tick 0.01, lot 0.001 in 0.001..=100, market lot up to 10, notional 5..=9e6.
471    fn rules() -> OrderRules {
472        OrderRules::new(
473            ValueRule::new(dec!(0.01), dec!(1000000), dec!(0.01)),
474            ValueRule::new(dec!(0.001), dec!(100), dec!(0.001)),
475            ValueRule::new(dec!(0), dec!(10), dec!(0)),
476            ValueRule::new(dec!(5), dec!(9000000), dec!(0)),
477        )
478    }
479
480    fn check(kind: OrderKind, price: Option<Decimal>, quantity: Decimal) -> Result<(), Error> {
481        rules().check(Side::Buy, kind, price, quantity)
482    }
483
484    fn bounds(field: OrderField, bound: Bound, limit: Decimal, value: Decimal) -> Error {
485        OrderError::OutOfBounds(BoundsViolation::new(field, bound, limit, value))
486    }
487
488    fn off_grid(field: OrderField, value: Decimal, step: Decimal) -> Error {
489        OrderError::InvalidPrecision(PrecisionViolation::new(
490            field,
491            value,
492            PrecisionLimit::NotOnGrid { step },
493        ))
494    }
495
496    #[test]
497    fn zero_or_negative_means_the_part_is_off() {
498        let rule = ValueRule::new(dec!(0), dec!(-1), dec!(0));
499        assert!(rule.is_off());
500        assert!(OrderRules::default().is_off());
501        assert!(!rules().is_off());
502        // One part on is enough for the rules to be on.
503        let on = ValueRule::new(dec!(1), dec!(0), dec!(0));
504        let off = ValueRule::default();
505        assert!(!OrderRules::new(on, off, off, off).is_off());
506        assert!(!OrderRules::new(off, on, off, off).is_off());
507        assert!(!OrderRules::new(off, off, on, off).is_off());
508        assert!(!OrderRules::new(off, off, off, on).is_off());
509        assert_eq!(
510            OrderRules::default().check::<(), ()>(
511                Side::Buy,
512                OrderKind::Limit,
513                Some(dec!(0.0001)),
514                dec!(7.77)
515            ),
516            Ok(())
517        );
518    }
519
520    #[test]
521    fn a_step_of_zero_set_by_hand_is_no_grid_not_a_panic() {
522        let mut rules = OrderRules::default();
523        rules.quantity.step = Some(Decimal::ZERO);
524        assert_eq!(
525            rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(1.5)),
526            Ok(())
527        );
528    }
529
530    #[test]
531    fn a_limit_order_on_the_grid_and_in_range_passes() {
532        assert_eq!(
533            check(OrderKind::Limit, Some(dec!(50000.12)), dec!(0.5)),
534            Ok(())
535        );
536    }
537
538    #[test]
539    fn a_price_off_the_tick_grid_is_refused_with_the_step() {
540        assert_eq!(
541            check(OrderKind::Limit, Some(dec!(50000.123)), dec!(0.5)),
542            Err(off_grid(OrderField::Price, dec!(50000.123), dec!(0.01)))
543        );
544    }
545
546    #[test]
547    fn a_quantity_off_the_lot_grid_is_refused_with_the_step() {
548        assert_eq!(
549            check(OrderKind::Limit, Some(dec!(50000)), dec!(0.0015)),
550            Err(off_grid(OrderField::Quantity, dec!(0.0015), dec!(0.001)))
551        );
552    }
553
554    #[test]
555    fn a_grid_is_counted_from_zero_not_from_the_minimum() {
556        // 0.003 is 1.5 steps of 0.002: off a grid counted from zero, though a minimum of 0.002
557        // would put an origin-at-minimum grid's next point at 0.004 as well.
558        let rules = OrderRules::new(
559            ValueRule::default(),
560            ValueRule::new(dec!(0.002), dec!(0), dec!(0.002)),
561            ValueRule::default(),
562            ValueRule::default(),
563        );
564        assert_eq!(
565            rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(0.003)),
566            Err(off_grid(OrderField::Quantity, dec!(0.003), dec!(0.002)))
567        );
568        assert_eq!(
569            rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(0.004)),
570            Ok(())
571        );
572    }
573
574    #[test]
575    fn a_price_beyond_either_bound_is_refused_with_the_limit() {
576        // On the grid, but beyond a bound.
577        let rules = OrderRules::new(
578            ValueRule::new(dec!(1), dec!(10), dec!(0.5)),
579            ValueRule::default(),
580            ValueRule::default(),
581            ValueRule::default(),
582        );
583        assert_eq!(
584            rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(0.5)), dec!(1)),
585            Err(bounds(OrderField::Price, Bound::Min, dec!(1), dec!(0.5)))
586        );
587        assert_eq!(
588            rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(10.5)), dec!(1)),
589            Err(bounds(OrderField::Price, Bound::Max, dec!(10), dec!(10.5)))
590        );
591        assert_eq!(
592            rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(1)), dec!(1)),
593            Ok(())
594        );
595        assert_eq!(
596            rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(dec!(10)), dec!(1)),
597            Ok(())
598        );
599    }
600
601    #[test]
602    fn a_quantity_beyond_either_bound_is_refused_and_the_bounds_are_inclusive() {
603        assert_eq!(
604            check(OrderKind::Limit, Some(dec!(50000)), dec!(100.001)),
605            Err(bounds(
606                OrderField::Quantity,
607                Bound::Max,
608                dec!(100),
609                dec!(100.001)
610            ))
611        );
612        assert_eq!(
613            check(OrderKind::Limit, Some(dec!(50000)), dec!(0)),
614            Err(bounds(
615                OrderField::Quantity,
616                Bound::Min,
617                dec!(0.001),
618                dec!(0)
619            ))
620        );
621        assert_eq!(
622            check(OrderKind::Limit, Some(dec!(50000)), dec!(100)),
623            Ok(())
624        );
625        assert_eq!(
626            check(OrderKind::Limit, Some(dec!(50000)), dec!(0.001)),
627            Ok(())
628        );
629    }
630
631    #[test]
632    fn the_market_quantity_rule_applies_to_a_market_order_only() {
633        assert_eq!(
634            check(OrderKind::Market, None, dec!(10.5)),
635            Err(bounds(
636                OrderField::Quantity,
637                Bound::Max,
638                dec!(10),
639                dec!(10.5)
640            ))
641        );
642        assert_eq!(
643            check(OrderKind::Limit, Some(dec!(50000)), dec!(10.5)),
644            Ok(())
645        );
646    }
647
648    #[test]
649    fn a_market_order_also_obeys_the_ordinary_quantity_rule() {
650        assert_eq!(
651            check(OrderKind::Market, None, dec!(0.0005)),
652            Err(off_grid(OrderField::Quantity, dec!(0.0005), dec!(0.001)))
653        );
654    }
655
656    #[test]
657    fn the_notional_of_a_limit_order_is_its_price_times_its_quantity() {
658        assert_eq!(
659            check(OrderKind::Limit, Some(dec!(1000)), dec!(0.004)),
660            Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(4)))
661        );
662        assert_eq!(
663            check(OrderKind::Limit, Some(dec!(1000)), dec!(0.005)),
664            Ok(())
665        );
666        assert_eq!(
667            check(OrderKind::Limit, Some(dec!(1000000)), dec!(10)),
668            Err(bounds(
669                OrderField::Notional,
670                Bound::Max,
671                dec!(9000000),
672                dec!(10000000)
673            ))
674        );
675    }
676
677    #[test]
678    fn a_notional_beyond_the_decimal_range_is_above_the_maximum_not_a_panic() {
679        let rules = OrderRules::new(
680            ValueRule::default(),
681            ValueRule::default(),
682            ValueRule::default(),
683            ValueRule::new(dec!(0), dec!(1), dec!(0)),
684        );
685        assert!(matches!(
686            rules.check::<(), ()>(Side::Buy, OrderKind::Limit, Some(Decimal::MAX), Decimal::MAX),
687            Err(OrderError::OutOfBounds(v)) if v.bound == Bound::Max
688        ));
689    }
690
691    #[test]
692    fn a_stop_limit_checks_the_price_the_trigger_and_the_notional() {
693        let kind = |trigger_price| OrderKind::StopLimit { trigger_price };
694        assert_eq!(
695            check(kind(dec!(100.001)), Some(dec!(100)), dec!(1)),
696            Err(off_grid(
697                OrderField::TriggerPrice,
698                dec!(100.001),
699                dec!(0.01)
700            ))
701        );
702        assert_eq!(
703            check(kind(dec!(100)), Some(dec!(100.001)), dec!(1)),
704            Err(off_grid(OrderField::Price, dec!(100.001), dec!(0.01)))
705        );
706        assert_eq!(
707            check(kind(dec!(100)), Some(dec!(1)), dec!(1)),
708            Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(1)))
709        );
710        assert_eq!(check(kind(dec!(100)), Some(dec!(100)), dec!(1)), Ok(()));
711    }
712
713    #[test]
714    fn a_take_profit_limit_is_checked_like_a_stop_limit() {
715        assert_eq!(
716            check(
717                OrderKind::TakeProfitLimit {
718                    trigger_price: dec!(100.001)
719                },
720                Some(dec!(100)),
721                dec!(1)
722            ),
723            Err(off_grid(
724                OrderField::TriggerPrice,
725                dec!(100.001),
726                dec!(0.01)
727            ))
728        );
729    }
730
731    #[test]
732    fn a_plain_stop_checks_its_trigger_and_never_a_notional() {
733        // A 1 USDT order would break the minimum notional if the trigger priced it.
734        for kind in [
735            OrderKind::Stop {
736                trigger_price: dec!(1),
737            },
738            OrderKind::TakeProfit {
739                trigger_price: dec!(1),
740            },
741        ] {
742            assert_eq!(check(kind, None, dec!(1)), Ok(()), "{kind:?}");
743        }
744        assert_eq!(
745            check(
746                OrderKind::Stop {
747                    trigger_price: dec!(1.001)
748                },
749                None,
750                dec!(1)
751            ),
752            Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
753        );
754    }
755
756    #[test]
757    fn a_market_order_never_checks_a_notional_or_a_price() {
758        // A request price on a market order is ignored.
759        assert_eq!(
760            check(OrderKind::Market, Some(dec!(0.001)), dec!(0.001)),
761            Ok(())
762        );
763    }
764
765    #[test]
766    fn a_trailing_stop_checks_its_activation_price_only() {
767        let kind = |activation_price| OrderKind::TrailingStop {
768            offset: dec!(50),
769            offset_type: TrailingOffsetType::BasisPoints,
770            activation_price,
771        };
772        assert_eq!(check(kind(None), None, dec!(1)), Ok(()));
773        assert_eq!(check(kind(Some(dec!(1))), None, dec!(1)), Ok(()));
774        assert_eq!(
775            check(kind(Some(dec!(1.001))), None, dec!(1)),
776            Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
777        );
778    }
779
780    #[test]
781    fn a_fixed_limit_trailing_stop_checks_its_limit_activation_and_notional() {
782        let kind = |activation_price| OrderKind::TrailingStopFixedLimit {
783            offset: dec!(50),
784            offset_type: TrailingOffsetType::BasisPoints,
785            activation_price,
786        };
787        assert_eq!(
788            check(kind(None), Some(dec!(100.001)), dec!(1)),
789            Err(off_grid(OrderField::Price, dec!(100.001), dec!(0.01)))
790        );
791        assert_eq!(
792            check(kind(Some(dec!(1.001))), Some(dec!(100)), dec!(1)),
793            Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
794        );
795        assert_eq!(
796            check(kind(None), Some(dec!(1)), dec!(1)),
797            Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(1)))
798        );
799        assert_eq!(check(kind(None), Some(dec!(100)), dec!(1)), Ok(()));
800    }
801
802    /// An unresolved kind is checked as its family: a limit family's price and notional, and
803    /// any trigger. A stop family ignores a request price.
804    #[test]
805    fn an_unresolved_kind_is_checked_as_its_family() {
806        let kind = |family, trigger_price| OrderKind::ConditionalUnresolved {
807            family,
808            trigger_price,
809        };
810        let stop_limit = |trigger| kind(ConditionalFamily::StopLimit, trigger);
811        assert_eq!(
812            check(stop_limit(Some(dec!(100.001))), Some(dec!(100)), dec!(1)),
813            Err(off_grid(
814                OrderField::TriggerPrice,
815                dec!(100.001),
816                dec!(0.01)
817            ))
818        );
819        assert_eq!(
820            check(stop_limit(None), Some(dec!(100.001)), dec!(1)),
821            Err(off_grid(OrderField::Price, dec!(100.001), dec!(0.01)))
822        );
823        assert_eq!(
824            check(stop_limit(None), Some(dec!(1)), dec!(1)),
825            Err(bounds(OrderField::Notional, Bound::Min, dec!(5), dec!(1)))
826        );
827        assert_eq!(check(stop_limit(None), Some(dec!(100)), dec!(1)), Ok(()));
828        // A stop family carries no limit: an off-grid request price is not read, and no notional.
829        assert_eq!(
830            check(
831                kind(ConditionalFamily::Stop, Some(dec!(1))),
832                Some(dec!(1.001)),
833                dec!(1)
834            ),
835            Ok(())
836        );
837    }
838
839    #[test]
840    fn a_limit_order_without_a_price_skips_the_price_and_notional_checks() {
841        assert_eq!(check(OrderKind::Limit, None, dec!(1)), Ok(()));
842    }
843
844    #[test]
845    fn a_grid_break_is_reported_before_a_range_break_of_the_same_value() {
846        // 0.0005 is below the minimum quantity and off the grid: the grid is reported.
847        assert_eq!(
848            check(OrderKind::Limit, Some(dec!(50000)), dec!(0.0005)),
849            Err(off_grid(OrderField::Quantity, dec!(0.0005), dec!(0.001)))
850        );
851    }
852
853    #[test]
854    fn a_pre_send_rules_deserializes_from_snake_case_and_defaults_to_enforce() {
855        assert_eq!(PreSendRules::default(), PreSendRules::Enforce);
856        assert_eq!(
857            serde_json::from_str::<PreSendRules>("\"off\"").ok(),
858            Some(PreSendRules::Off)
859        );
860    }
861
862    #[test]
863    fn the_refusals_name_the_value_and_the_limit() {
864        let text = |result: Result<(), Error>| match result {
865            Err(OrderError::OutOfBounds(violation)) => violation.to_string(),
866            Err(OrderError::InvalidPrecision(violation)) => violation.to_string(),
867            other => panic!("expected a refusal, got {other:?}"),
868        };
869        assert_eq!(
870            text(check(OrderKind::Limit, Some(dec!(1000)), dec!(0.004))),
871            "notional 4.000 is below the minimum 5"
872        );
873        assert_eq!(
874            text(check(OrderKind::Limit, Some(dec!(1000)), dec!(101))),
875            "quantity 101 is above the maximum 100"
876        );
877        assert_eq!(
878            text(check(OrderKind::Limit, Some(dec!(1000.001)), dec!(1))),
879            "price 1000.001 is not a multiple of 0.01"
880        );
881    }
882
883    fn stepped(step: Decimal) -> ValueRule {
884        ValueRule::new(dec!(0), dec!(0), step)
885    }
886
887    #[test]
888    fn a_value_on_the_grid_rounds_to_itself_either_way() {
889        for (step, value) in [
890            (dec!(0.01), dec!(50000.12)),
891            (dec!(0.05), dec!(1.15)),
892            (dec!(0.05), dec!(-1.15)),
893            (dec!(0.001), dec!(0)),
894        ] {
895            assert_eq!(
896                stepped(step).round_down(value),
897                Some(value),
898                "{step} {value}"
899            );
900            assert_eq!(stepped(step).round_up(value), Some(value), "{step} {value}");
901        }
902    }
903
904    #[test]
905    fn a_value_off_the_grid_rounds_to_the_neighbour_below_or_above() {
906        let rule = stepped(dec!(0.01));
907        assert_eq!(rule.round_down(dec!(50000.129)), Some(dec!(50000.12)));
908        assert_eq!(rule.round_up(dec!(50000.121)), Some(dec!(50000.13)));
909    }
910
911    #[test]
912    fn a_step_that_is_not_a_power_of_ten_rounds_to_its_own_multiples() {
913        let rule = stepped(dec!(0.05));
914        assert_eq!(rule.round_down(dec!(1.07)), Some(dec!(1.05)));
915        assert_eq!(rule.round_up(dec!(1.07)), Some(dec!(1.10)));
916        assert_eq!(rule.round_down(dec!(0.04)), Some(dec!(0)));
917        assert_eq!(rule.round_up(dec!(0.01)), Some(dec!(0.05)));
918        // A value that moves takes the finer scale; one on the grid keeps its own.
919        assert_eq!(
920            rule.round_down(dec!(0.19000))
921                .map(|value| value.to_string()),
922            Some("0.15000".to_owned())
923        );
924        assert_eq!(
925            stepped(dec!(0.005))
926                .round_down(dec!(0.10))
927                .map(|value| value.to_string()),
928            Some("0.10".to_owned())
929        );
930    }
931
932    #[test]
933    fn a_negative_value_rounds_down_away_from_zero_and_up_toward_it() {
934        let rule = stepped(dec!(0.05));
935        assert_eq!(rule.round_down(dec!(-1.07)), Some(dec!(-1.10)));
936        assert_eq!(rule.round_up(dec!(-1.07)), Some(dec!(-1.05)));
937        assert_eq!(rule.round_up(dec!(-0.01)), Some(dec!(0)));
938    }
939
940    #[test]
941    fn with_no_step_a_value_comes_back_unchanged() {
942        let mut rule = ValueRule::new(dec!(1), dec!(10), dec!(0));
943        assert_eq!(rule.round_down(dec!(1.2345)), Some(dec!(1.2345)));
944        assert_eq!(rule.round_up(dec!(1.2345)), Some(dec!(1.2345)));
945        // A zero step set by hand is no grid either, not a division by zero.
946        rule.step = Some(Decimal::ZERO);
947        assert_eq!(rule.round_down(dec!(1.2345)), Some(dec!(1.2345)));
948    }
949
950    #[test]
951    fn rounding_does_not_clamp_to_the_range() {
952        let rule = ValueRule::new(dec!(0.002), dec!(1), dec!(0.002));
953        assert_eq!(rule.round_down(dec!(0.001)), Some(dec!(0)));
954        assert_eq!(rule.round_up(dec!(1.001)), Some(dec!(1.002)));
955    }
956
957    #[test]
958    fn a_result_beyond_the_decimal_range_is_none_not_a_panic() {
959        // Decimal::MAX is odd, so the multiple of 2 above it is out of range; the one below is not.
960        let rule = stepped(dec!(2));
961        assert_eq!(rule.round_up(Decimal::MAX), None);
962        assert_eq!(rule.round_down(Decimal::MAX), Some(Decimal::MAX - dec!(1)));
963        assert_eq!(rule.round_down(Decimal::MIN), None);
964        assert_eq!(rule.round_up(Decimal::MIN), Some(Decimal::MIN + dec!(1)));
965    }
966
967    #[test]
968    fn a_result_that_needs_more_digits_than_a_decimal_holds_is_none() {
969        // Decimal::MAX is a multiple of 3, so of 0.3; one less is not, and its neighbours on the
970        // grid of 0.3 need a decimal place a Decimal that large has no room for.
971        let rule = stepped(dec!(0.3));
972        assert_eq!(rule.round_down(Decimal::MAX), Some(Decimal::MAX));
973        assert_eq!(rule.round_down(Decimal::MAX - dec!(1)), None);
974        assert_eq!(rule.round_up(Decimal::MAX - dec!(1)), None);
975    }
976
977    /// Quantity step 0.05, market quantity step 0.02: a market order's grid is 0.1.
978    fn two_quantity_steps() -> OrderRules {
979        OrderRules::new(
980            ValueRule::default(),
981            stepped(dec!(0.05)),
982            stepped(dec!(0.02)),
983            ValueRule::default(),
984        )
985    }
986
987    #[test]
988    fn a_market_quantity_rounds_to_the_grid_both_quantity_steps_share() {
989        let rules = two_quantity_steps();
990        assert_eq!(
991            rules.round_quantity_down(OrderKind::Market, dec!(0.19)),
992            Some(dec!(0.1))
993        );
994        assert_eq!(
995            rules.round_quantity_up(OrderKind::Market, dec!(0.11)),
996            Some(dec!(0.2))
997        );
998        // The result passes both grids in the check.
999        assert_eq!(
1000            rules.check::<(), ()>(Side::Buy, OrderKind::Market, None, dec!(0.1)),
1001            Ok(())
1002        );
1003    }
1004
1005    #[test]
1006    fn any_other_quantity_rounds_to_the_quantity_step_alone() {
1007        let rules = two_quantity_steps();
1008        assert_eq!(
1009            rules.round_quantity_down(OrderKind::Limit, dec!(0.19)),
1010            Some(dec!(0.15))
1011        );
1012        assert_eq!(
1013            rules.round_quantity_up(
1014                OrderKind::Stop {
1015                    trigger_price: dec!(1)
1016                },
1017                dec!(0.11)
1018            ),
1019            Some(dec!(0.15))
1020        );
1021    }
1022
1023    #[test]
1024    fn a_market_quantity_with_one_step_off_rounds_to_the_other() {
1025        let quantity_only = OrderRules::new(
1026            ValueRule::default(),
1027            stepped(dec!(0.05)),
1028            ValueRule::default(),
1029            ValueRule::default(),
1030        );
1031        assert_eq!(
1032            quantity_only.round_quantity_down(OrderKind::Market, dec!(0.19)),
1033            Some(dec!(0.15))
1034        );
1035        let market_only = OrderRules::new(
1036            ValueRule::default(),
1037            ValueRule::default(),
1038            stepped(dec!(0.02)),
1039            ValueRule::default(),
1040        );
1041        assert_eq!(
1042            market_only.round_quantity_down(OrderKind::Market, dec!(0.19)),
1043            Some(dec!(0.18))
1044        );
1045        assert_eq!(
1046            OrderRules::default().round_quantity_up(OrderKind::Market, dec!(0.19)),
1047            Some(dec!(0.19))
1048        );
1049    }
1050
1051    #[test]
1052    fn the_shared_grid_is_the_least_common_multiple_of_the_steps() {
1053        assert_eq!(common_step(dec!(0.05), dec!(0.02)), Some(dec!(0.1)));
1054        assert_eq!(common_step(dec!(0.001), dec!(0.01)), Some(dec!(0.01)));
1055        assert_eq!(common_step(dec!(0.01), dec!(0.001)), Some(dec!(0.01)));
1056        assert_eq!(common_step(dec!(0.4), dec!(0.6)), Some(dec!(1.2)));
1057        assert_eq!(common_step(dec!(0.4), dec!(6)), Some(dec!(6)));
1058        assert_eq!(common_step(dec!(3), dec!(0.5)), Some(dec!(3)));
1059        assert_eq!(common_step(dec!(0.25), dec!(0.25)), Some(dec!(0.25)));
1060    }
1061
1062    #[test]
1063    fn steps_whose_shared_grid_a_decimal_cannot_hold_round_to_none() {
1064        // Two near-coprime 28-digit steps: their least common multiple has some 56 digits.
1065        let a = Decimal::from_i128_with_scale(7_922_816_251_426_433_759_354_395_033, 27);
1066        let b = Decimal::from_i128_with_scale(7_922_816_251_426_433_759_354_395_031, 27);
1067        assert_eq!(common_step(a, b), None);
1068        let rules = OrderRules::new(
1069            ValueRule::default(),
1070            stepped(a),
1071            stepped(b),
1072            ValueRule::default(),
1073        );
1074        assert_eq!(rules.round_quantity_down(OrderKind::Market, dec!(1)), None);
1075        assert_eq!(rules.round_quantity_up(OrderKind::Market, dec!(1)), None);
1076        // A limit order needs only one of them.
1077        assert_eq!(rules.round_quantity_down(OrderKind::Limit, a), Some(a));
1078    }
1079
1080    /// Trailing offsets of 10..=2000 basis points above the market and 20..=1000 below.
1081    fn trailing_rules() -> OrderRules {
1082        OrderRules::default().with_trailing_offsets(
1083            ValueRule::new(dec!(10), dec!(2000), dec!(0)),
1084            ValueRule::new(dec!(20), dec!(1000), dec!(0)),
1085        )
1086    }
1087
1088    fn trailing_stop(offset: Decimal, offset_type: TrailingOffsetType) -> OrderKind {
1089        OrderKind::TrailingStop {
1090            offset,
1091            offset_type,
1092            activation_price: None,
1093        }
1094    }
1095
1096    #[test]
1097    fn a_buy_trailing_offset_is_checked_against_the_range_above_and_a_sell_against_below() {
1098        let check = |side, offset| {
1099            trailing_rules().check::<(), ()>(
1100                side,
1101                trailing_stop(offset, TrailingOffsetType::BasisPoints),
1102                None,
1103                dec!(1),
1104            )
1105        };
1106        // 15 is inside the range above (from 10) but under the range below (from 20).
1107        assert_eq!(check(Side::Buy, dec!(15)), Ok(()));
1108        assert_eq!(
1109            check(Side::Sell, dec!(15)),
1110            Err(bounds(
1111                OrderField::TrailingOffset,
1112                Bound::Min,
1113                dec!(20),
1114                dec!(15)
1115            ))
1116        );
1117        // 1500 is inside the range above (to 2000) but over the range below (to 1000).
1118        assert_eq!(check(Side::Buy, dec!(1500)), Ok(()));
1119        assert_eq!(
1120            check(Side::Sell, dec!(1500)),
1121            Err(bounds(
1122                OrderField::TrailingOffset,
1123                Bound::Max,
1124                dec!(1000),
1125                dec!(1500)
1126            ))
1127        );
1128        assert_eq!(
1129            check(Side::Buy, dec!(9)),
1130            Err(bounds(
1131                OrderField::TrailingOffset,
1132                Bound::Min,
1133                dec!(10),
1134                dec!(9)
1135            ))
1136        );
1137        assert_eq!(
1138            check(Side::Buy, dec!(2001)),
1139            Err(bounds(
1140                OrderField::TrailingOffset,
1141                Bound::Max,
1142                dec!(2000),
1143                dec!(2001)
1144            ))
1145        );
1146        // The bounds are inclusive.
1147        for (side, offset) in [
1148            (Side::Buy, dec!(10)),
1149            (Side::Buy, dec!(2000)),
1150            (Side::Sell, dec!(20)),
1151            (Side::Sell, dec!(1000)),
1152        ] {
1153            assert_eq!(check(side, offset), Ok(()), "{side:?} {offset}");
1154        }
1155    }
1156
1157    #[test]
1158    fn a_percentage_offset_is_checked_and_reported_in_percent() {
1159        let check = |offset| {
1160            trailing_rules().check::<(), ()>(
1161                Side::Buy,
1162                trailing_stop(offset, TrailingOffsetType::Percentage),
1163                None,
1164                dec!(1),
1165            )
1166        };
1167        // 10..=2000 basis points is 0.1..=20 percent.
1168        assert_eq!(check(dec!(0.1)), Ok(()));
1169        assert_eq!(check(dec!(20)), Ok(()));
1170        assert_eq!(
1171            check(dec!(0.09)),
1172            Err(bounds(
1173                OrderField::TrailingOffset,
1174                Bound::Min,
1175                dec!(0.1),
1176                dec!(0.09)
1177            ))
1178        );
1179        assert_eq!(
1180            check(dec!(20.5)),
1181            Err(bounds(
1182                OrderField::TrailingOffset,
1183                Bound::Max,
1184                dec!(20),
1185                dec!(20.5)
1186            ))
1187        );
1188    }
1189
1190    #[test]
1191    fn an_absolute_offset_is_not_checked() {
1192        assert_eq!(
1193            trailing_rules().check::<(), ()>(
1194                Side::Buy,
1195                trailing_stop(dec!(0.0001), TrailingOffsetType::Absolute),
1196                None,
1197                dec!(1),
1198            ),
1199            Ok(())
1200        );
1201    }
1202
1203    #[test]
1204    fn every_trailing_kind_checks_its_offset() {
1205        let offset = dec!(5);
1206        for kind in [
1207            OrderKind::TrailingStopLimit {
1208                offset,
1209                offset_type: TrailingOffsetType::BasisPoints,
1210                stop_price: dec!(100),
1211                limit_offset: dec!(1),
1212            },
1213            OrderKind::TrailingStopFixedLimit {
1214                offset,
1215                offset_type: TrailingOffsetType::BasisPoints,
1216                activation_price: None,
1217            },
1218        ] {
1219            assert_eq!(
1220                trailing_rules().check::<(), ()>(Side::Sell, kind, Some(dec!(100)), dec!(1)),
1221                Err(bounds(
1222                    OrderField::TrailingOffset,
1223                    Bound::Min,
1224                    dec!(20),
1225                    offset
1226                )),
1227                "{kind:?}"
1228            );
1229        }
1230    }
1231
1232    #[test]
1233    fn a_trailing_offset_is_checked_after_the_prices_and_before_the_quantity() {
1234        let rules = rules().with_trailing_offsets(
1235            ValueRule::new(dec!(10), dec!(0), dec!(0)),
1236            ValueRule::default(),
1237        );
1238        let kind = |activation_price| OrderKind::TrailingStop {
1239            offset: dec!(1),
1240            offset_type: TrailingOffsetType::BasisPoints,
1241            activation_price,
1242        };
1243        assert_eq!(
1244            rules.check::<(), ()>(Side::Buy, kind(Some(dec!(1.001))), None, dec!(0.0005)),
1245            Err(off_grid(OrderField::TriggerPrice, dec!(1.001), dec!(0.01)))
1246        );
1247        assert_eq!(
1248            rules.check::<(), ()>(Side::Buy, kind(None), None, dec!(0.0005)),
1249            Err(bounds(
1250                OrderField::TrailingOffset,
1251                Bound::Min,
1252                dec!(10),
1253                dec!(1)
1254            ))
1255        );
1256    }
1257
1258    #[test]
1259    fn a_trailing_rules_step_is_not_used() {
1260        let rules = OrderRules::default().with_trailing_offsets(
1261            ValueRule::new(dec!(0), dec!(0), dec!(10)),
1262            ValueRule::new(dec!(0), dec!(0), dec!(10)),
1263        );
1264        for side in [Side::Buy, Side::Sell] {
1265            assert_eq!(
1266                rules.check::<(), ()>(
1267                    side,
1268                    trailing_stop(dec!(15), TrailingOffsetType::BasisPoints),
1269                    None,
1270                    dec!(1)
1271                ),
1272                Ok(()),
1273                "{side:?}"
1274            );
1275        }
1276    }
1277
1278    #[test]
1279    fn the_trailing_ranges_leave_other_kinds_alone() {
1280        let rules = OrderRules::default().with_trailing_offsets(
1281            ValueRule::new(dec!(10), dec!(20), dec!(0)),
1282            ValueRule::new(dec!(10), dec!(20), dec!(0)),
1283        );
1284        for kind in [
1285            OrderKind::Market,
1286            OrderKind::Limit,
1287            OrderKind::Stop {
1288                trigger_price: dec!(1),
1289            },
1290            OrderKind::ConditionalUnresolved {
1291                family: ConditionalFamily::Stop,
1292                trigger_price: None,
1293            },
1294        ] {
1295            assert_eq!(
1296                rules.check::<(), ()>(Side::Sell, kind, Some(dec!(1)), dec!(1)),
1297                Ok(()),
1298                "{kind:?}"
1299            );
1300        }
1301    }
1302
1303    #[test]
1304    fn the_market_notional_is_never_checked() {
1305        let rules =
1306            OrderRules::default().with_market_notional(ValueRule::new(dec!(5), dec!(10), dec!(0)));
1307        for (kind, price) in [
1308            (OrderKind::Market, Some(dec!(1))),
1309            (OrderKind::Limit, Some(dec!(1))),
1310        ] {
1311            assert_eq!(
1312                rules.check::<(), ()>(Side::Buy, kind, price, dec!(1)),
1313                Ok(()),
1314                "{kind:?}"
1315            );
1316        }
1317    }
1318
1319    #[test]
1320    fn the_new_rules_count_towards_being_on_and_new_leaves_them_off() {
1321        let on = ValueRule::new(dec!(1), dec!(0), dec!(0));
1322        let off = ValueRule::default();
1323        assert!(!OrderRules::default().with_market_notional(on).is_off());
1324        assert!(
1325            !OrderRules::default()
1326                .with_trailing_offsets(on, off)
1327                .is_off()
1328        );
1329        assert!(
1330            !OrderRules::default()
1331                .with_trailing_offsets(off, on)
1332                .is_off()
1333        );
1334        let built = rules();
1335        assert!(built.market_notional.is_off());
1336        assert!(built.trailing_offset_above.is_off());
1337        assert!(built.trailing_offset_below.is_off());
1338        // The builders leave the other parts as they were.
1339        let with = built
1340            .with_market_notional(on)
1341            .with_trailing_offsets(on, off);
1342        assert_eq!(with.price, built.price);
1343        assert_eq!(with.notional, built.notional);
1344        assert_eq!(with.market_notional, on);
1345        assert_eq!(with.trailing_offset_above, on);
1346        assert_eq!(with.trailing_offset_below, off);
1347    }
1348
1349    /// Rules serialised before the market notional and trailing offsets were added still load,
1350    /// with those parts off.
1351    #[test]
1352    fn rules_serialised_without_the_newer_parts_load_with_them_off() {
1353        let rule = r#"{"min":"1","max":null,"step":null}"#;
1354        let json = format!(
1355            r#"{{"price":{rule},"quantity":{rule},"market_quantity":{rule},"notional":{rule}}}"#
1356        );
1357        let loaded: OrderRules = match serde_json::from_str(&json) {
1358            Ok(loaded) => loaded,
1359            Err(error) => panic!("older rules should load: {error}"),
1360        };
1361        let on = ValueRule::new(dec!(1), dec!(0), dec!(0));
1362        assert_eq!(loaded, OrderRules::new(on, on, on, on));
1363    }
1364}