orders: reject non-finite and malformed order parameters (ibx#263) - #392
Open
userFRM wants to merge 1 commit into
Open
orders: reject non-finite and malformed order parameters (ibx#263)#392userFRM wants to merge 1 commit into
userFRM wants to merge 1 commit into
Conversation
…rs (ibx#263) parse_algo_params read every algo key through a lookup that returned an empty string when a tag was absent, then parsed with unwrap_or fallbacks: a malformed numeric became 0.0, a malformed boolean became false, an unrecognized riskAversion became Neutral, and a malformed DarkIce displaySize became 100. The Adaptive branch in client_core.rs parsed adaptivePriority the same way. Because "NaN" and "inf" parse successfully as f64, a caller who passed either string as an algo parameter got a silently substituted value instead of an error, and a typo like riskAversion="Aggresive" submitted a Neutral arrival-price algo with no diagnostic. validate_order never checked any numeric order field for finiteness or range before the wire encoding cast it with as i64 or as u32. Rust's float to int cast saturates instead of panicking, so a NaN limit price encoded as 0, an infinite one as i64::MAX, and a negative total_quantity, display_size, min_qty, parent_id, or trailing_percent got clamped or reinterpreted instead of refused. None of those paths distinguished a malformed value from a legitimate one. parse_algo_params and the new parse_risk_aversion now return Err once the caller has actually supplied a value that fails to parse, is non-finite, or names an unrecognized enum member, keeping the existing default only for a key that was never set at all — a key present with an empty value is refused the same as any other value that fails to parse, not treated as absent. validate_order gained a set of checks, run once before any algo, adaptive, or what-if branch, that reject non-finite or out-of-range values for every price and amount field and negative values for the fields cast to an unsigned wire type. A shared require_finite_price helper also rejects a finite but oversized magnitude that would overflow the fixed-point i64 on the wire, reachable well within double precision given a PRICE_SCALE of 1e8: i64::MAX itself rounds up to 2^63 once represented as f64, so the check excludes that rounded value too, not just magnitudes strictly above it, or the largest representable price would pass the check and then saturate on the cast that follows. adaptive_priority centralizes the same fail-fast behavior for the Adaptive algo's priority tag and is called from both validate_order and build_order_request, so a bad value is caught before the instrument is registered. trailing_percent's basis-point wire granularity is documented at the truncating cast rather than changed, since a value between two representable basis points is a rounding rather than a coercion into a different value. Closes deepentropy#263.
userFRM
force-pushed
the
fix/algo-params-coerced
branch
from
July 31, 2026 12:52
d094144 to
e13d883
Compare
userFRM
added a commit
to userFRM/ibx
that referenced
this pull request
Aug 3, 2026
…tity rules and route adaptive through the shared encoder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Malformed and non-finite order parameters were coerced into valid-looking values rather than refused.
"NaN"and"inf"parse successfully asf64, and the float-to-int casts that follow are saturating: NaN becomes 0, infinity becomesi64::MAX, and a finite magnitude too large for the wire's fixed point saturates the same way.So an order carrying a nonsense price was accepted, silently turned into a different order, and sent.
What this changes
Algo parameters are refused when present but malformed: an unparseable or non-finite number, a boolean that is not
0/1/true/false, an unrecognisedriskAversionoradaptivePriority, adisplaySizethat is not a non-negative integer. A missing key still defaults exactly as before — only a present-but-malformed value is now an error.Numeric order fields are checked at
validate_order, which both the Rust and Pythonplace_orderalready call before any cast. Non-finite values are refused for every price and amount field, as is a finite magnitude whose fixed-point form would overflowi64; negative values are refused for quantity, display size, minimum quantity, parent id and trailing percent.The three fields that use
f64::MAXas their "not set" sentinel are only checked when they hold something else, so an unset field is not mistaken for an invalid one.trailing_percent's basis-point truncation is left alone and documented at the cast site: that is rounding a well-formed value, not coercing a nonsense one.Note on
aux_priceThe pre-existing zero-check on
aux_pricecannot see NaN, becauseNaN != 0.0. The blanket finiteness check is what catches it.Tests
Twenty-two, covering each rejection and each preserved default. Twelve production mutations were applied one at a time, each confirmed to compile, the matching test confirmed to fail by name, and the revert diffed byte-for-byte against a saved baseline to confirm it landed on the intended line — including
require_finite_price's overflow clause in isolation from its finiteness clause, so the range check is shown not to be redundant.Closes #263.