/** * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.option; import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg; import static org.testng.Assert.assertEquals; import java.time.ZonedDateTime; import org.testng.annotations.Test; import com.opengamma.strata.basics.date.DayCounts; import com.opengamma.strata.basics.value.ValueDerivatives; import com.opengamma.strata.collect.TestHelper; import com.opengamma.strata.product.option.BarrierType; import com.opengamma.strata.product.option.KnockType; import com.opengamma.strata.product.option.SimpleConstantContinuousBarrier; /** * Test {@link BlackOneTouchAssetPriceFormulaRepository}. */ @Test public class BlackOneTouchAssetPriceFormulaRepositoryTest { private static final ZonedDateTime REFERENCE_DATE = TestHelper.dateUtc(2011, 7, 1); private static final ZonedDateTime EXPIRY_DATE = TestHelper.dateUtc(2015, 1, 2); private static final double EXPIRY_TIME = DayCounts.ACT_ACT_ISDA.relativeYearFraction(REFERENCE_DATE.toLocalDate(), EXPIRY_DATE.toLocalDate()); private static final SimpleConstantContinuousBarrier BARRIER_DOWN_IN = SimpleConstantContinuousBarrier.of(BarrierType.DOWN, KnockType.KNOCK_IN, 90); private static final SimpleConstantContinuousBarrier BARRIER_DOWN_OUT = SimpleConstantContinuousBarrier.of(BarrierType.DOWN, KnockType.KNOCK_OUT, 90); private static final SimpleConstantContinuousBarrier BARRIER_UP_IN = SimpleConstantContinuousBarrier.of(BarrierType.UP, KnockType.KNOCK_IN, 110); private static final SimpleConstantContinuousBarrier BARRIER_UP_OUT = SimpleConstantContinuousBarrier.of(BarrierType.UP, KnockType.KNOCK_OUT, 110); private static final SimpleConstantContinuousBarrier[] BARRIERS = new SimpleConstantContinuousBarrier[] {BARRIER_UP_IN, BARRIER_UP_OUT, BARRIER_DOWN_IN, BARRIER_DOWN_OUT }; private static final double SPOT = 105; private static final double RATE_DOM = 0.05; // Domestic rate private static final double RATE_FOR = 0.02; // Foreign rate private static final double COST_OF_CARRY = RATE_DOM - RATE_FOR; // Domestic - Foreign rate private static final double VOLATILITY = 0.20; private static final double DF_FOR = Math.exp(-RATE_FOR * EXPIRY_TIME); // 'Base Ccy private static final double DF_DOM = Math.exp(-RATE_DOM * EXPIRY_TIME); // 'Quote Ccy private static final double FWD_FX = SPOT * DF_FOR / DF_DOM; private static final double TOL = 1.0e-14; private static final double EPS_FD = 1.0e-6; private static final BlackOneTouchAssetPriceFormulaRepository PRICER = new BlackOneTouchAssetPriceFormulaRepository(); /** * standard in-out parity holds if r=0. */ public void inOutParity() { double upIn = PRICER.price(SPOT, EXPIRY_TIME, RATE_DOM, RATE_DOM, VOLATILITY, BARRIER_UP_IN); double upOut = PRICER.price(SPOT, EXPIRY_TIME, RATE_DOM, RATE_DOM, VOLATILITY, BARRIER_UP_OUT); double downIn = PRICER.price(SPOT, EXPIRY_TIME, RATE_DOM, RATE_DOM, VOLATILITY, BARRIER_DOWN_IN); double downOut = PRICER.price(SPOT, EXPIRY_TIME, RATE_DOM, RATE_DOM, VOLATILITY, BARRIER_DOWN_OUT); assertRelative(upIn + upOut, SPOT, TOL); assertRelative(downIn + downOut, SPOT, TOL); } /** * Upper barrier level is very high. */ public void largeBarrierTest() { SimpleConstantContinuousBarrier in = SimpleConstantContinuousBarrier.of(BarrierType.UP, KnockType.KNOCK_IN, 1.0e4); SimpleConstantContinuousBarrier out = SimpleConstantContinuousBarrier .of(BarrierType.UP, KnockType.KNOCK_OUT, 1.0e4); double upIn = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, in); double upOut = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, out); assertRelative(upIn, 0d, TOL); assertRelative(upOut, FWD_FX * DF_DOM, TOL); } /** * Lower barrier level is very small. */ public void smallBarrierTest() { SimpleConstantContinuousBarrier in = SimpleConstantContinuousBarrier.of(BarrierType.DOWN, KnockType.KNOCK_IN, 0.1d); SimpleConstantContinuousBarrier out = SimpleConstantContinuousBarrier.of(BarrierType.DOWN, KnockType.KNOCK_OUT, 0.1d); double dwIn = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, in); double dwOut = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, out); assertRelative(dwIn, 0d, TOL); assertRelative(dwOut, FWD_FX * DF_DOM, TOL); } /** * Greeks against finite difference approximation. */ public void greekfdTest() { for (SimpleConstantContinuousBarrier barrier : BARRIERS) { ValueDerivatives computed = PRICER.priceAdjoint( SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double spotUp = PRICER.price( SPOT + EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double spotDw = PRICER.price( SPOT - EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double rateUp = PRICER.price( SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM + EPS_FD, VOLATILITY, barrier); double rateDw = PRICER.price( SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM - EPS_FD, VOLATILITY, barrier); double costUp = PRICER.price( SPOT, EXPIRY_TIME, COST_OF_CARRY + EPS_FD, RATE_DOM, VOLATILITY, barrier); double costDw = PRICER.price( SPOT, EXPIRY_TIME, COST_OF_CARRY - EPS_FD, RATE_DOM, VOLATILITY, barrier); double volUp = PRICER.price( SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY + EPS_FD, barrier); double volDw = PRICER.price( SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY - EPS_FD, barrier); double timeUp = PRICER.price( SPOT, EXPIRY_TIME + EPS_FD, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double timeDw = PRICER.price( SPOT, EXPIRY_TIME - EPS_FD, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); ValueDerivatives spotUp1 = PRICER.priceAdjoint( SPOT + EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); ValueDerivatives spotDw1 = PRICER.priceAdjoint( SPOT - EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); assertEquals(computed.getDerivative(0), 0.5 * (spotUp - spotDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(1), 0.5 * (rateUp - rateDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(2), 0.5 * (costUp - costDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(3), 0.5 * (volUp - volDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(4), 0.5 * (timeUp - timeDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(5), 0.5 * (spotUp1.getDerivative(0) - spotDw1.getDerivative(0)) / EPS_FD, EPS_FD); } } /** * smoothly connected to limiting cases. */ public void smallsigmaTTest() { for (SimpleConstantContinuousBarrier barrier : BARRIERS) { double volUp = 2.0e-3; double volDw = 1.0e-3; double time = 1.0e-2; double optUp = PRICER.price(SPOT, time, COST_OF_CARRY, RATE_DOM, volUp, barrier); double optDw = PRICER.price(SPOT, time, COST_OF_CARRY, RATE_DOM, volDw, barrier); assertRelative(optUp, optDw, 1.0e-3); ValueDerivatives optUpAdj = PRICER.priceAdjoint(SPOT, time, COST_OF_CARRY, RATE_DOM, volUp, barrier); ValueDerivatives optDwAdj = PRICER.priceAdjoint(SPOT, time, COST_OF_CARRY, RATE_DOM, volDw, barrier); assertRelative(optUpAdj.getValue(), optDwAdj.getValue(), 1.0e-3); for (int i = 0; i < 6; ++i) { assertRelative(optUpAdj.getDerivative(i), optDwAdj.getDerivative(i), TOL); } } } /** * Barrier event has occured already. */ public void illegalBarrierLevelTest() { assertThrowsIllegalArg(() -> PRICER.price(BARRIER_UP_IN.getBarrierLevel() + 0.1, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, BARRIER_UP_IN)); assertThrowsIllegalArg(() -> PRICER.price(BARRIER_DOWN_OUT.getBarrierLevel() - 0.1, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, BARRIER_DOWN_OUT)); assertThrowsIllegalArg(() -> PRICER.priceAdjoint(BARRIER_UP_IN.getBarrierLevel() + 0.1, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, BARRIER_UP_IN)); assertThrowsIllegalArg(() -> PRICER.priceAdjoint(BARRIER_DOWN_OUT.getBarrierLevel() - 0.1, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, BARRIER_DOWN_OUT)); } //------------------------------------------------------------------------- private void assertRelative(double val1, double val2, double tol) { assertEquals(val1, val2, Math.max(Math.abs(val2), 1d) * tol); } }