18

I have a BigDecimal calculation result which I need to round to the nearest specified interval (in this case it's the financial market tick size).

e.g. Price [Tick Size] -> Rounded Price

100.1 [0.25] -> 100
100.2 [0.25] -> 100.25
100.1 [0.125] -> 100.125
100.2 [0.125] -> 100.25

Thanks.

Update: schnaader's solution, translated into Java/BigDecimal terms:

price = price.divide(tick).setScale(0, RoundingMode.HALF_UP).multiply(tick)
Jon
  • 5,247
  • 6
  • 30
  • 38
  • 1
    For searchability, this technique can be used to round a percentage value to some arbitrary fraction of a basis point. – Robert Atkins Jan 06 '11 at 04:10
  • the `divide()` function takes the scale as arguments as well: `divide(tick, 0, RoundingMode.HALF_UP)` – Dula Dec 23 '21 at 03:50

3 Answers3

14

You could normalize tick size and then use the usual rounding methods:

100.1 [0.25] -> * (1/0.25) -> 400.4 [1]  -> round -> 400 -> / (1/0.25) -> 100
100.2 [0.25] -> * (1/0.25) -> 400.8 [1] -> round -> 401 -> / (1/0.25) -> 100.25

So it should be:

Price = Round(Price / Tick) * Tick;

Also note that you seem to have to set the correct rounding mode for BigDecimals. See BigDecimal Docs for example. So you should be sure to set this correct and write some tests to check the correctness of your code.

schnaader
  • 49,103
  • 10
  • 104
  • 136
  • Looks good thanks! In Java/BigDecimal terms, this translates into the rather ugly: price.divide(tick).setScale(0, RoundingMode.HALF_UP).multiply(tick) – Jon Mar 13 '09 at 09:10
2

This is more related to "Rounding a stock price to it's nearest tick size".

Answer provided by schnaader is correct, however there are a few things missing.

  • First, you must check whether stock price needs rounding. Unnecessary rounding messes up the fractional part.
  • Then you also need to handle ArithmeticException that might come during BigDecimal divide function

Here's my solution. It would take a lot of time to explain it. I'd recommend to try it with some samples to get a feel. Look for the function roundTick().

import static java.math.RoundingMode.HALF_UP;

import java.math.BigDecimal;

/**
 * Utility class for stock price related operations.
 */
public final class PriceFormatter {

    public static final float DELTA = 0.0001f;

    private PriceFormatter() {
    }

    /**
     * Rounds the price to the nearest tick size.
     *
     * @param price    price
     * @param tickSize tick size
     * @return price rounded to the nearest tick size
     */
    public static final float roundTick(final float price, final float tickSize) {
        if (tickSize < DELTA) {
            return price;
        }

        if (!isRoundingNeeded(price, tickSize)) {
            return price;
        }

        final BigDecimal p = new BigDecimal(price);
        final BigDecimal t = new BigDecimal(tickSize);

        final BigDecimal roundedPrice = p.divide(t, 0, HALF_UP).multiply(t);

        return roundedPrice.floatValue();
    }

    /**
     * Checks whether price needs rounding to the nearest tick size.
     *
     * @param price    price
     * @param tickSize tick size
     * @return true, if rounding is needed; false otherwise
     */
    public static final boolean isRoundingNeeded(final float price, final float tickSize) {
        final int mult = calculateTickMultiplier(tickSize);
        final int mod = (int) (tickSize * mult);
        final float reminder = (((price * mult) % mult) % mod);
        final boolean needsRounding = reminder > DELTA;
        return needsRounding;
    }

    public static final int calculateTickMultiplier(final float tickSize) {
        int result = 1;
        while (((tickSize * result) < 1) || (((tickSize * result) - (int) (tickSize * result)) > DELTA)) {
            result *= 10;
        }

        return result;
    }

}
Pritesh Mhatre
  • 3,847
  • 2
  • 23
  • 27
1
p= p - p % t + ((p % t < t / 2) ? 0.0 : t);

//where p= price and t= tick increment

Tushar Gupta - curioustushar
  • 58,085
  • 24
  • 103
  • 107