cTrader

AlgoWay Blog

Guides, manuals and platform references.

Core
JSON & Alerts
MT5 / Expert Advisors
Errors
Integrations
Other Publications
AlgoWay Strategy Lab

Python Trading Prices: Decimal Precision vs Tick Size

Conceptual order-price ladder with an off-grid marker and an aligned price marker

A price of 123.47 has two decimal places. It is still off-grid for an instrument that trades in increments of 0.25. Formatting the number neatly does not put it on the exchange's price ladder.

For an automated trading bot, separate three decisions: preserve the intended decimal value, align it to the instrument's permitted increment, and choose a direction that respects the order's purpose. In an AlgoWay Python webhook workflow, this belongs in the preparation and validation of the trading instruction. The transport example and the arithmetic example solve different problems.

Start with the decimal value you actually received

Python's Decimal documentation explains that constructing a Decimal from a float preserves that float's value, including its binary approximation. Constructing it from decimal text preserves the supplied decimal digits. Keep prices and increments as decimal text or Decimal values through the calculation when that is how the source provides them.

For example, Decimal("0.1") and Decimal(0.1) are not equal. Converting a calculation to text after it has already used floats does not recover any information lost earlier. Choose the representation at the input boundary.

Decimal places are not tick multiples

Decimal's quantize() matches an exponent. It does not snap a number to arbitrary multiples of its argument. For instance, Decimal("123.47").quantize(Decimal("0.25")) remains 123.47: both values already have two decimal places.

On our hypothetical 0.25 grid, the surrounding valid values are 123.25 and 123.50. The price lies 0.22 above the lower rung and 0.03 below the upper rung. Nearest rounding would choose 123.50, but nearest is not automatically the right trading decision.

A real example of an increment rule appears in the Binance Spot symbol filters: an enabled price tick rule requires the price to be a multiple of the tick size. The same documentation specifies separate price bounds and other filters. This is a Spot API example, not a universal formula for every broker or market.

Divide into ticks, round the count, multiply back

The following standalone illustration calculates both neighboring grid prices. It makes no network request and submits no order. Its assumptions are positive finite values, a fixed increment greater than zero, a grid starting at zero, and enough decimal precision for the supplied numbers.

from decimal import Decimal, ROUND_FLOOR, ROUND_CEILING, localcontext

# Hypothetical positive price and a fixed, zero-origin tick grid.
price = Decimal("123.47")
tick = Decimal("0.25")

with localcontext() as ctx:
    ctx.prec = 28  # Ample for these example values, not a universal bound.
    ticks = price / tick
    lower = ticks.to_integral_value(rounding=ROUND_FLOOR) * tick
    upper = ticks.to_integral_value(rounding=ROUND_CEILING) * tick
    assert lower <= price <= upper
    assert lower % tick == 0 and upper % tick == 0

print(format(lower, "f"))  # 123.25
print(format(upper, "f"))  # 123.50

Here the unrounded tick count is 493.88. Flooring that count gives 493 ticks, or 123.25; taking the ceiling gives 494 ticks, or 123.50. A price already on the grid, such as 123.50, produces the same value in both directions.

Do not silently reuse this calculation for a venue with a nonzero grid origin or price-dependent tick bands. Obtain the instrument's current rule and implement that rule. Also reject missing, nonfinite or nonpositive increments before division. Decimal arithmetic still has a precision context; choose and test it for the range of values your application accepts.

Choose the rounding direction from the order constraint

Intended constraintChoice in this exampleReason
Buy limit must not exceed 123.47123.25123.50 would exceed the maximum intended buying price.
Sell limit must not be below 123.47123.50123.25 would fall below the minimum intended selling price.

These are price constraints, not predictions about which order will fill. Moving a buy limit down may reduce its chance of execution. An aligned limit price also does not guarantee a fill; see why a touched limit can remain unfilled.

Protective stops require their own reasoning. Rounding a long position's stop down increases its distance from entry; rounding it up decreases that distance and may affect whether the broker accepts it. Recalculate planned loss after choosing the valid level, and check the destination's quote-side and minimum-stop-distance rules when using MT5. A universal “buy down, sell up” helper is too blunt for every field in an order.

Validate the final instruction, including serialization

Before transmission, check the aligned price against the current instrument rules, the intended price constraint and the resulting risk. Quantity increments, price limits and minimum order value remain separate checks. A tick-aligned number can pass one test and fail another.

The example prints fixed-point text to make its results visible. That is not a recommendation to change every API field into a JSON string. Follow the receiving API's required field types and inspect the serialized payload. Converting back to float carelessly can undo the reason for preserving decimals.

Useful test cases include a price exactly on a rung, one just above and below it, an increment such as 0.25 rather than only 0.01, and inputs outside the accepted numeric range. Record the original price, instrument increment, selected rounding direction and final price. Those four values make a rejected order much easier to explain than a log entry saying “rounding applied.”

Last updated: October 24, 2026