{"record":{"id":"653e3f5d7ee4877e","repo":"nautechsystems/nautilus_trader","slug":"ticks-tick-lower-and-tick-upper-must-be-multip","errorCode":null,"errorMessage":"Ticks {tick_lower} and {tick_upper} must be multiples of the tick spacing","messagePattern":"Ticks (.+?) and (.+?) must be multiples of the tick spacing","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/defi/pool_analysis/profiler.rs","lineNumber":1628,"sourceCode":"    ///\n    /// Ensures ticks are properly ordered, aligned to tick spacing, and within\n    /// valid bounds. Used by all position-related operations.\n    ///\n    /// # Errors\n    ///\n    /// This function returns an error if:\n    /// - `tick_lower >= tick_upper` (invalid range).\n    /// - Ticks are not multiples of pool's tick spacing.\n    /// - Ticks are outside `MIN_TICK/MAX_TICK` bounds.\n    fn validate_ticks(&self, tick_lower: i32, tick_upper: i32) -> anyhow::Result<()> {\n        if tick_lower >= tick_upper {\n            anyhow::bail!(\"Invalid tick range: {tick_lower} >= {tick_upper}\")\n        }\n\n        if tick_lower % self.pool.tick_spacing.unwrap() as i32 != 0\n            || tick_upper % self.pool.tick_spacing.unwrap() as i32 != 0\n        {\n            anyhow::bail!(\n                \"Ticks {tick_lower} and {tick_upper} must be multiples of the tick spacing\"\n            )\n        }\n\n        if tick_lower < PoolTick::MIN_TICK || tick_upper > PoolTick::MAX_TICK {\n            anyhow::bail!(\"Invalid tick bounds for {tick_lower} and {tick_upper}\");\n        }\n        Ok(())\n    }\n\n    /// Updates all liquidity analytics.\n    fn update_liquidity_analytics(&mut self) {\n        self.analytics.liquidity_utilization_rate = self.liquidity_utilization_rate();\n    }\n\n    /// Returns the pool's active liquidity tracked by the tick map.\n    ///\n    /// This represents the effective liquidity available for trading at the current price.","sourceCodeStart":1610,"sourceCodeEnd":1646,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/defi/pool_analysis/profiler.rs#L1610-L1646","documentation":"This error is thrown by `validate_ticks` in the liquidity profiler when either the lower or upper tick of a position is not an integer multiple of the pool's tick spacing. Concentrated-liquidity pools (e.g. Uniswap v3) only accept positions whose tick boundaries align to the pool's configured tick spacing, so the profiler rejects any mint/burn with misaligned ticks before analytics are updated.","triggerScenarios":"Calling `process_mint`, `execute_mint`, `process_burn`, or `execute_burn` with `tick_lower` or `tick_upper` where `tick % tick_spacing != 0` (tick_spacing read from `self.pool.tick_spacing`, which is also unwrapped and would panic if None). The ticks may individually be valid but still misaligned, e.g. lower=10, upper=210 with tick_spacing 60.","commonSituations":"Parsing on-chain Mint/Burn events from pools with non-standard tick spacing; hardcoding tick ranges copied from a pool with a different fee tier/spacing; computing ticks via arithmetic that doesn't round to spacing (e.g. price-to-tick conversion without rounding down to the nearest valid tick).","solutions":["Round tick_lower/tick_upper to the pool's tick spacing (floor toward MIN_TICK for lower, ceiling toward MAX_TICK for upper) before calling mint/burn handlers.","Fetch the correct `tick_spacing` for the specific pool rather than assuming a value; ensure `pool.tick_spacing` is populated (it is unwrapped here).","Use existing tick-math helpers (e.g. nearest usable tick functions) to derive ticks from a price range.","Log the offending ticks and spacing to confirm alignment before retrying the event processing."],"exampleFix":"// before\nlet (tick_lower, tick_upper) = (lower_from_price, upper_from_price);\nprofiler.execute_mint(owner, tick_lower, tick_upper, amount);\n// after\nlet spacing = pool.tick_spacing.unwrap() as i32;\nlet tick_lower = (lower_from_price / spacing) * spacing;\nlet tick_upper = ((upper_from_price + spacing - 1) / spacing) * spacing;\nprofiler.execute_mint(owner, tick_lower, tick_upper, amount);","handlingStrategy":"validation","validationCode":"fn ticks_aligned(tick_lower: i32, tick_upper: i32, tick_spacing: i32) -> bool {\n    tick_spacing > 0\n        && tick_lower % tick_spacing == 0\n        && tick_upper % tick_spacing == 0\n        && tick_lower < tick_upper\n}","typeGuard":null,"tryCatchPattern":"match profiler.execute_mint(owner, tick_lower, tick_upper, amount) {\n    Ok(_) => {},\n    Err(e) if e.to_string().contains(\"must be multiples of the tick spacing\") => {\n        // recompute ticks rounded to spacing and retry\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Always derive ticks from prices using spacing-aware rounding helpers, never raw tick math.","Look up the pool's actual tick_spacing per pool instead of hardcoding a value.","Unit-test mint/burn event handlers against pools of several fee tiers/spacings.","Ensure tick_spacing is populated before processing events (it is unwrapped in the check)."],"tags":["validation","defi","ticks","concentrated-liquidity"],"backgroundTag":"invalid-argument-value","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"}