/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.option.pricing.analytic.formula; import static org.testng.Assert.assertEquals; import org.testng.annotations.Test; import org.threeten.bp.Period; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.forex.definition.ForexDefinition; import com.opengamma.analytics.financial.forex.definition.ForexOptionDigitalDefinition; import com.opengamma.analytics.financial.forex.derivative.ForexOptionDigital; import com.opengamma.analytics.financial.forex.method.TestsDataSetsForex; import com.opengamma.analytics.financial.interestrate.YieldCurveBundle; import com.opengamma.analytics.financial.model.volatility.surface.SmileDeltaTermStructureParametersStrikeInterpolation; import com.opengamma.analytics.financial.schedule.ScheduleCalculator; import com.opengamma.analytics.math.statistics.distribution.NormalDistribution; import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution; import com.opengamma.analytics.util.time.TimeCalculator; import com.opengamma.financial.convention.businessday.BusinessDayConvention; import com.opengamma.financial.convention.businessday.BusinessDayConventions; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.calendar.MondayToFridayCalendar; import com.opengamma.util.money.Currency; import com.opengamma.util.test.TestGroup; import com.opengamma.util.time.DateUtils; import com.opengamma.util.tuple.Triple; /** * Test. */ @SuppressWarnings("deprecation") @Test(groups = TestGroup.UNIT) public class DigitalOptionFunctionTest { private static final double SPOT = 105.; private static final double[] STRIKES = new double[] {97., 105., 105.1, 114. }; private static final double TIME = 4.2; private static final double[] INTERESTS = new double[] {-0.01, 0.017, 0.05, 0.1 }; private static final double[] VOLS = new double[] {0.05, 0.1, 0.5 }; private static final double[] DIVIDENDS = new double[] {0.005, 0.024, 0.05 }; private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2011, 6, 13); private static final Calendar CALENDAR = new MondayToFridayCalendar("A"); private static final BusinessDayConvention BUSINESS_DAY = BusinessDayConventions.MODIFIED_FOLLOWING; private static final int SETTLEMENT_DAYS = 2; private static final Currency EUR = Currency.EUR; private static final Currency USD = Currency.USD; private static final YieldCurveBundle CURVES = TestsDataSetsForex.createCurvesForex(); private static final String[] CURVES_NAME = TestsDataSetsForex.curveNames(); private static final SmileDeltaTermStructureParametersStrikeInterpolation SMILE_TERM = TestsDataSetsForex.smile5points(REFERENCE_DATE); private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1); /** * Tests the present value against an explicit computation. The amount is paid in the domestic currency. */ @Test public void presentValueDomestic() { final double strike = 1.45; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime payDate = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(9), BUSINESS_DAY, CALENDAR); final ZonedDateTime expDate = ScheduleCalculator.getAdjustedDate(payDate, -SETTLEMENT_DAYS, CALENDAR); final double timeToExpiry = TimeCalculator.getTimeBetween(REFERENCE_DATE, expDate); final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, payDate, notional, strike); final ForexOptionDigitalDefinition forexOptionDefinition = new ForexOptionDigitalDefinition(forexUnderlyingDefinition, expDate, isCall, isLong); final ForexOptionDigital forexOption = forexOptionDefinition.toDerivative(REFERENCE_DATE); final double dfDomestic = CURVES.getCurve(CURVES_NAME[1]).getDiscountFactor(forexOption.getUnderlyingForex().getPaymentTime()); final double dfForeign = CURVES.getCurve(CURVES_NAME[0]).getDiscountFactor(forexOption.getUnderlyingForex().getPaymentTime()); final double rDomestic = CURVES.getCurve(CURVES_NAME[1]).getInterestRate(forexOption.getUnderlyingForex().getPaymentTime()); final double rForeign = CURVES.getCurve(CURVES_NAME[0]).getInterestRate(forexOption.getUnderlyingForex().getPaymentTime()); final double forward = SPOT * dfForeign / dfDomestic; final double volatility = SMILE_TERM.getVolatility(new Triple<>(timeToExpiry, forward, forward)); final double sigmaRootT = volatility * Math.sqrt(forexOption.getExpirationTime()); final double dM = Math.log(forward / strike) / sigmaRootT - 0.5 * sigmaRootT; final int omega = isCall ? 1 : -1; final double pvExpected = Math.abs(forexOption.getUnderlyingForex().getPaymentCurrency1().getAmount()) * dfDomestic * NORMAL.getCDF(omega * dM) * (isLong ? 1.0 : -1.0); final double price = Math.abs(forexOption.getUnderlyingForex().getPaymentCurrency1().getAmount()) * DigitalOptionFunction.price(SPOT, strike, forexOption.getUnderlyingForex().getPaymentTime(), volatility, rDomestic, rDomestic - rForeign, isCall); assertEquals(price, pvExpected, pvExpected * 1.e-14); } /** * */ @Test public void greeksTest() { final boolean[] tfSet = new boolean[] {true, false }; final double eps = 1.e-6; for (final boolean isCall : tfSet) { for (final double strike : STRIKES) { for (final double interest : INTERESTS) { for (final double vol : VOLS) { for (final double dividend : DIVIDENDS) { final double delta = DigitalOptionFunction.delta(SPOT, strike, TIME, vol, interest, interest - dividend, isCall); final double gamma = DigitalOptionFunction.gamma(SPOT, strike, TIME, vol, interest, interest - dividend, isCall); final double theta = DigitalOptionFunction.theta(SPOT, strike, TIME, vol, interest, interest - dividend, isCall); final double vega = DigitalOptionFunction.vega(SPOT, strike, TIME, vol, interest, interest - dividend, isCall); final double upSpot = DigitalOptionFunction.price(SPOT + eps, strike, TIME, vol, interest, interest - dividend, isCall); final double downSpot = DigitalOptionFunction.price(SPOT - eps, strike, TIME, vol, interest, interest - dividend, isCall); final double upSpotDelta = DigitalOptionFunction.delta(SPOT + eps, strike, TIME, vol, interest, interest - dividend, isCall); final double downSpotDelta = DigitalOptionFunction.delta(SPOT - eps, strike, TIME, vol, interest, interest - dividend, isCall); final double upTime = DigitalOptionFunction.price(SPOT, strike, TIME + eps, vol, interest, interest - dividend, isCall); final double downTime = DigitalOptionFunction.price(SPOT, strike, TIME - eps, vol, interest, interest - dividend, isCall); final double upVol = DigitalOptionFunction.price(SPOT, strike, TIME, vol + eps, interest, interest - dividend, isCall); final double downVol = DigitalOptionFunction.price(SPOT, strike, TIME, vol - eps, interest, interest - dividend, isCall); assertEquals(delta, 0.5 * (upSpot - downSpot) / eps, eps); assertEquals(gamma, 0.5 * (upSpotDelta - downSpotDelta) / eps, eps); assertEquals(theta, -0.5 * (upTime - downTime) / eps, eps); assertEquals(vega, 0.5 * (upVol - downVol) / eps, eps); final double forward = SPOT * Math.exp(interest - dividend * TIME); final double forwardTheta = DigitalOptionFunction.driftlessTheta(forward, strike, TIME, vol, isCall); final double sign = isCall ? 1. : -1.; final double dUp = Math.log(forward / strike) / vol / Math.sqrt(TIME + eps) - 0.5 * vol * Math.sqrt(TIME + eps); final double dDw = Math.log(forward / strike) / vol / Math.sqrt(TIME - eps) - 0.5 * vol * Math.sqrt(TIME - eps); final double fwUp = NORMAL.getCDF(sign * dUp); final double fwDw = NORMAL.getCDF(sign * dDw); assertEquals(forwardTheta, -0.5 * (fwUp - fwDw) / eps, eps); } } } } } } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeSpotPriceTest() { DigitalOptionFunction.price(-SPOT, STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeStrikePriceTest() { DigitalOptionFunction.price(SPOT, -STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeTimePriceTest() { DigitalOptionFunction.price(SPOT, STRIKES[0], -TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeVolPriceTest() { DigitalOptionFunction.price(SPOT, STRIKES[0], TIME, -VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeSpotdeltaTest() { DigitalOptionFunction.delta(-SPOT, STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeStrikedeltaTest() { DigitalOptionFunction.delta(SPOT, -STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeTimedeltaTest() { DigitalOptionFunction.delta(SPOT, STRIKES[0], -TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeVoldeltaTest() { DigitalOptionFunction.delta(SPOT, STRIKES[0], TIME, -VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeSpotgammaTest() { DigitalOptionFunction.gamma(-SPOT, STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeStrikegammaTest() { DigitalOptionFunction.gamma(SPOT, -STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeTimegammaTest() { DigitalOptionFunction.gamma(SPOT, STRIKES[0], -TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeVolgammaTest() { DigitalOptionFunction.gamma(SPOT, STRIKES[0], TIME, -VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeSpotthetaTest() { DigitalOptionFunction.theta(-SPOT, STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeStrikethetaTest() { DigitalOptionFunction.theta(SPOT, -STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeTimethetaTest() { DigitalOptionFunction.theta(SPOT, STRIKES[0], -TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeVolthetaTest() { DigitalOptionFunction.theta(SPOT, STRIKES[0], TIME, -VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeSpotvegaTest() { DigitalOptionFunction.vega(-SPOT, STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeStrikevegaTest() { DigitalOptionFunction.vega(SPOT, -STRIKES[0], TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeTimevegaTest() { DigitalOptionFunction.vega(SPOT, STRIKES[0], -TIME, VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeVolvegaTest() { DigitalOptionFunction.vega(SPOT, STRIKES[0], TIME, -VOLS[1], INTERESTS[1], INTERESTS[1] - DIVIDENDS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeSpotdriftlessThetaTest() { DigitalOptionFunction.driftlessTheta(-SPOT, STRIKES[0], TIME, VOLS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeStrikedriftlessThetaTest() { DigitalOptionFunction.driftlessTheta(SPOT, -STRIKES[0], TIME, VOLS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeTimedriftlessThetaTest() { DigitalOptionFunction.driftlessTheta(SPOT, STRIKES[0], -TIME, VOLS[1], true); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void negativeVoldriftlessThetaTest() { DigitalOptionFunction.driftlessTheta(SPOT, STRIKES[0], TIME, -VOLS[1], true); } }