package net.objectlab.kit.fxcalc; import java.math.BigDecimal; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.objectlab.kit.util.BigDecimalUtil; /** * Calculator responsible for generating a new cross FX Rate based on two relevant (sharing a cross currency) FX Rates; e.g. CHF/JPY based on USD/CHF and USD/JPY. * This calculator is able to handle all 'standard' cases where the market convention is different, e.g. GBP/CAD via USD/CAD and EUR/USD or inverted rates * if it is what you might have e.g. GBP/CAD via CAD/USD and EUR/USD. * @author Benoit Xhenseval */ public final class CrossRateCalculator { private static final Logger LOG = LoggerFactory.getLogger(CrossRateCalculator.class); private CrossRateCalculator() { } /** * Calculate the cross rate, use this only if required. * @param targetPair the currency pair we want Bid/Ask for * @param fx1 one rate involving either targetPair.ccy1 or targetPair.ccy2 * @param fx2 one rate involving either targetPair.ccy1 or targetPair.ccy2 * @param precision required in case we need to divide rates * @param ranking link to algorithm to determine if the targetPair will be market convention or not * @return a new instance of FxRate * @throws IllegalArgumentException if the 2 fx1 and fx2 do not share a common cross currency or either currencies in the targetPair */ public static FxRate calculateCross(final CurrencyPair targetPair, final FxRate fx1, final FxRate fx2, final int precision, final int precisionForInverseFxRate, final MajorCurrencyRanking ranking, final int bidRounding, final int askRounding, CurrencyProvider currencyProvider) { final Optional<String> crossCcy = fx1.getCurrencyPair().findCommonCcy(fx2.getCurrencyPair()); final String xCcy = crossCcy.orElseThrow( () -> new IllegalArgumentException("The 2 FXRates do not share a ccy " + fx1.getCurrencyPair() + " " + fx2.getCurrencyPair())); if (targetPair.containsCcy(crossCcy.get())) { throw new IllegalArgumentException("The target currency pair " + targetPair + " contains the common ccy " + crossCcy.get()); } final String fx1Ccy1 = fx1.getCurrencyPair().getCcy1(); final String fx2Ccy1 = fx2.getCurrencyPair().getCcy1(); final String fx1Ccy2 = fx1.getCurrencyPair().getCcy2(); final String fx2Ccy2 = fx2.getCurrencyPair().getCcy2(); final boolean shouldDivide = fx1Ccy1.equals(xCcy) && fx2Ccy1.equals(xCcy) || fx1Ccy2.equals(xCcy) && fx2Ccy2.equals(xCcy); // what if it i // both ccy2? BigDecimal bid; BigDecimal ask; if (shouldDivide) { final FxRate numeratorFx = targetPair.getCcy1().equals(fx2Ccy2) || targetPair.getCcy1().equals(fx1Ccy1) ? fx1 : fx2; final FxRate denominatorFx = numeratorFx == fx1 ? fx2 : fx1; LOG.debug("CALC {} / {}", numeratorFx, denominatorFx); bid = BigDecimalUtil.divide(precision, numeratorFx.getBid(), denominatorFx.getAsk(), bidRounding); ask = BigDecimalUtil.divide(precision, numeratorFx.getAsk(), denominatorFx.getBid(), askRounding); } else { final boolean inverse = targetPair.getCcy1().equals(fx2Ccy2) || targetPair.getCcy1().equals(fx1Ccy2); LOG.debug("CALC {} x {}", fx1, fx2); if (inverse) { ask = BigDecimalUtil.setScale( BigDecimalUtil.inverse(BigDecimalUtil.multiply(fx1.getBid(), fx2.getBid()), precisionForInverseFxRate, bidRounding), precision); bid = BigDecimalUtil.setScale( BigDecimalUtil.inverse(BigDecimalUtil.multiply(fx1.getAsk(), fx2.getAsk()), precisionForInverseFxRate, askRounding), precision); } else { bid = BigDecimalUtil.setScale(BigDecimalUtil.multiply(fx1.getBid(), fx2.getBid()), precision); ask = BigDecimalUtil.setScale(BigDecimalUtil.multiply(fx1.getAsk(), fx2.getAsk()), precision); } } final FxRateImpl crossRate = new FxRateImpl(targetPair, xCcy, ranking.isMarketConvention(targetPair), bid, ask, currencyProvider); LOG.debug("X RATE {}", crossRate); LOG.debug(crossRate.getDescription()); return crossRate; } }