{"record":{"id":"7fef421c80ab5baf","repo":"nautechsystems/nautilus_trader","slug":"canonical-projection-contains-an-unencoded-floatin","errorCode":null,"errorMessage":"canonical projection contains an unencoded floating-point value","messagePattern":"canonical projection contains an unencoded floating-point value","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/result.rs","lineNumber":993,"sourceCode":"        \"venue_order_ids\" => Some(IdentityClass::VenueOrder),\n        _ => None,\n    }\n}\n\nfn stringify_numbers(value: &mut Value) -> anyhow::Result<()> {\n    match value {\n        Value::Array(values) => {\n            for value in values {\n                stringify_numbers(value)?;\n            }\n        }\n        Value::Object(object) => {\n            for value in object.values_mut() {\n                stringify_numbers(value)?;\n            }\n        }\n        Value::Number(number) => {\n            anyhow::ensure!(\n                number.is_i64() || number.is_u64(),\n                \"canonical projection contains an unencoded floating-point value\"\n            );\n            *value = Value::String(number.to_string());\n        }\n        _ => {}\n    }\n    Ok(())\n}\n\nfn first_divergence(\n    expected: &Value,\n    actual: &Value,\n    path: String,\n) -> Option<CanonicalResultDivergence> {\n    match (expected, actual) {\n        (Value::Object(expected), Value::Object(actual)) => {\n            let keys = expected","sourceCodeStart":975,"sourceCodeEnd":1011,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/result.rs#L975-L1011","documentation":"Raised by `stringify_numbers` during canonical-value preparation. Any `Value::Number` that is neither i64 nor u64 is a float, which would leak nondeterministic `f64` formatting into the canonical projection; the code requires all numerics to be integral before encoding them as strings.","triggerScenarios":"A serialized order/position/account/event value contains an `f64`-backed JSON number — i.e. some field (price, quantity, money) was serialized from `f64` instead of a Decimal/int, or serde emitted a float during canonicalization.","commonSituations":"Using plain `f64` fields instead of the project's `Price`/`Quantity`/`Money` types in a struct that flows into backtest result output; calling `serde_json::to_value` on types that don't implement decimal-as-string serialization.","solutions":["Replace raw `f64` fields with domain types (`Price`, `Quantity`, `Money`) or `Decimal` so they serialize as strings.","Use serde stringification (e.g. `#[serde(with = ...)]` or `as_raw().to_string()`) for any numeric that must survive canonicalization.","Reproduce by running the failing backtest and dumping the canonical JSON to find the offending key.","Add/refresh unit tests asserting no floats appear in canonical output."],"exampleFix":"// before\npub quantity: f64,\n// after\npub quantity: Quantity,  // serializes as string \"100\"","handlingStrategy":"validation","validationCode":"let v = serde_json::to_value(&output)?;\nfn has_floats(v: &serde_json::Value) -> bool {\n    match v {\n        Value::Number(n) => !(n.is_i64() || n.is_u64()),\n        Value::Array(a) => a.iter().any(has_floats),\n        Value::Object(o) => o.values().any(has_floats),\n        _ => false,\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use Price/Quantity/Money domain types instead of f64 in output structs.","Enforce string-encoded decimals in serde with custom (de)serializers."],"tags":["serialization","floating-point","determinism"],"backgroundTag":"schema-validation-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}