final ZonedDateTime payDate = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(9), BUSINESS_DAY, CALENDAR);
final ZonedDateTime expDate = ScheduleCalculator.getAdjustedDate(payDate, -SETTLEMENT_DAYS, CALENDAR);
final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, payDate, notional, strike);
final ForexOptionDigitalDefinition forexOptionDefinitionCall = new ForexOptionDigitalDefinition(forexUnderlyingDefinition, expDate, isCall, isLong);
final ForexOptionDigital forexOptionCall = forexOptionDefinitionCall.toDerivative(REFERENCE_DATE, CURVES_NAME);
final Forex forexForward = forexUnderlyingDefinition.toDerivative(REFERENCE_DATE, CURVES_NAME);
final MultipleCurrencyInterestRateCurveSensitivity sensi = METHOD_BLACK_DIGITAL.presentValueCurveSensitivity(forexOptionCall, SMILE_BUNDLE);
final double dfDomestic = CURVES.getCurve(CURVES_NAME[1]).getDiscountFactor(forexForward.getPaymentTime());
final double dfForeign = CURVES.getCurve(CURVES_NAME[0]).getDiscountFactor(forexForward.getPaymentTime());
final double forward = SPOT * dfForeign / dfDomestic;
final double volatility = SMILE_TERM.getVolatility(new Triple<>(forexOptionCall.getExpirationTime(), forward, forward));
final double sigmaRootT = volatility * Math.sqrt(forexOptionCall.getExpirationTime());
final int omega = isCall ? 1 : -1;
// Finite difference
final YieldAndDiscountCurve curveDomestic = CURVES.getCurve(forexOptionCall.getUnderlyingForex().getPaymentCurrency2().getFundingCurveName());
final YieldAndDiscountCurve curveForeign = CURVES.getCurve(forexOptionCall.getUnderlyingForex().getPaymentCurrency1().getFundingCurveName());
double forwardBumped;
double dfForeignBumped;
double dfDomesticBumped;
double dMBumped;
final double deltaShift = 0.00001; // 0.1 bp
final double[] nodeTimes = new double[2];
nodeTimes[0] = 0.0;
nodeTimes[1] = forexOptionCall.getUnderlyingForex().getPaymentTime();
final double[] yields = new double[2];
YieldAndDiscountCurve curveNode;
YieldAndDiscountCurve curveBumpedPlus;
YieldAndDiscountCurve curveBumpedMinus;
final String bumpedCurveName = "Bumped";
//Foreign
yields[0] = curveForeign.getInterestRate(nodeTimes[0]);
yields[1] = curveForeign.getInterestRate(nodeTimes[1]);
curveNode = YieldCurve.from(InterpolatedDoublesCurve.fromSorted(nodeTimes, yields, new LinearInterpolator1D()));
curveBumpedPlus = curveNode.withSingleShift(nodeTimes[1], deltaShift);
curveBumpedMinus = curveNode.withSingleShift(nodeTimes[1], -deltaShift);
final YieldCurveBundle curvesForeign = new YieldCurveBundle();
curvesForeign.setCurve(bumpedCurveName, curveBumpedPlus);
curvesForeign.setCurve(CURVES_NAME[1], CURVES.getCurve(CURVES_NAME[1]));
dfForeignBumped = curveBumpedPlus.getDiscountFactor(forexForward.getPaymentTime());
forwardBumped = SPOT * dfForeignBumped / dfDomestic;
dMBumped = Math.log(forwardBumped / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double bumpedPvForeignPlus = Math.abs(forexOptionCall.getUnderlyingForex().getPaymentCurrency2().getAmount()) * dfDomestic * NORMAL.getCDF(omega * dMBumped) * (isLong ? 1.0 : -1.0);
dfForeignBumped = curveBumpedMinus.getDiscountFactor(forexForward.getPaymentTime());
forwardBumped = SPOT * dfForeignBumped / dfDomestic;
dMBumped = Math.log(forwardBumped / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double bumpedPvForeignMinus = Math.abs(forexOptionCall.getUnderlyingForex().getPaymentCurrency2().getAmount()) * dfDomestic * NORMAL.getCDF(omega * dMBumped) * (isLong ? 1.0 : -1.0);
final double resultForeign = (bumpedPvForeignPlus - bumpedPvForeignMinus) / (2 * deltaShift);
assertEquals("Forex Digital option: curve sensitivity", forexForward.getPaymentTime(), sensi.getSensitivity(USD).getSensitivities().get(CURVES_NAME[0]).get(0).first, TOLERANCE_TIME);
assertEquals("Forex Digital option: curve sensitivity", resultForeign, sensi.getSensitivity(USD).getSensitivities().get(CURVES_NAME[0]).get(0).second, TOLERANCE_DELTA);
//Domestic
yields[0] = curveDomestic.getInterestRate(nodeTimes[0]);
yields[1] = curveDomestic.getInterestRate(nodeTimes[1]);
curveNode = YieldCurve.from(InterpolatedDoublesCurve.fromSorted(nodeTimes, yields, new LinearInterpolator1D()));
curveBumpedPlus = curveNode.withSingleShift(nodeTimes[1], deltaShift);
curveBumpedMinus = curveNode.withSingleShift(nodeTimes[1], -deltaShift);
final YieldCurveBundle curvesDomestic = new YieldCurveBundle();
curvesDomestic.setCurve(CURVES_NAME[0], CURVES.getCurve(CURVES_NAME[0]));
curvesDomestic.setCurve(bumpedCurveName, curveBumpedPlus);
dfDomesticBumped = curveBumpedPlus.getDiscountFactor(forexForward.getPaymentTime());
forwardBumped = SPOT * dfForeign / dfDomesticBumped;
dMBumped = Math.log(forwardBumped / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double bumpedPvDomesticPlus = Math.abs(forexOptionCall.getUnderlyingForex().getPaymentCurrency2().getAmount()) * dfDomesticBumped * NORMAL.getCDF(omega * dMBumped) * (isLong ? 1.0 : -1.0);
curvesForeign.replaceCurve(bumpedCurveName, curveBumpedMinus);
dfDomesticBumped = curveBumpedMinus.getDiscountFactor(forexForward.getPaymentTime());
forwardBumped = SPOT * dfForeign / dfDomesticBumped;
dMBumped = Math.log(forwardBumped / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double bumpedPvDomesticMinus = Math.abs(forexOptionCall.getUnderlyingForex().getPaymentCurrency2().getAmount()) * dfDomesticBumped * NORMAL.getCDF(omega * dMBumped) * (isLong ? 1.0 : -1.0);
final double resultDomestic = (bumpedPvDomesticPlus - bumpedPvDomesticMinus) / (2 * deltaShift);
assertEquals("Forex Digital option: curve sensitivity", forexForward.getPaymentTime(), sensi.getSensitivity(USD).getSensitivities().get(CURVES_NAME[1]).get(0).first, TOLERANCE_TIME);
assertEquals("Forex Digital option: curve sensitivity", resultDomestic, sensi.getSensitivity(USD).getSensitivities().get(CURVES_NAME[1]).get(0).second, TOLERANCE_DELTA);
}