/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.fxopt;
import static com.opengamma.strata.basics.currency.Currency.EUR;
import static com.opengamma.strata.basics.currency.Currency.GBP;
import static com.opengamma.strata.basics.date.DayCounts.ACT_365F;
import static com.opengamma.strata.collect.TestHelper.coverBeanEquals;
import static com.opengamma.strata.collect.TestHelper.coverImmutableBean;
import static com.opengamma.strata.collect.TestHelper.date;
import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.LINEAR;
import static org.testng.Assert.assertEquals;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.testng.annotations.Test;
import com.opengamma.strata.basics.currency.CurrencyPair;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.ValueType;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.market.surface.DefaultSurfaceMetadata;
import com.opengamma.strata.market.surface.InterpolatedNodalSurface;
import com.opengamma.strata.market.surface.NodalSurface;
import com.opengamma.strata.market.surface.Surface;
import com.opengamma.strata.market.surface.SurfaceMetadata;
import com.opengamma.strata.market.surface.interpolator.GridSurfaceInterpolator;
import com.opengamma.strata.market.surface.interpolator.SurfaceInterpolator;
/**
* Test {@link BlackFxOptionSurfaceVolatilities}.
*/
@Test
public class BlackFxOptionSurfaceVolatilitiesTest {
private static final SurfaceInterpolator INTERPOLATOR_2D = GridSurfaceInterpolator.of(LINEAR, LINEAR);
private static final DoubleArray TIMES = DoubleArray.of(0.25, 0.25, 0.25, 0.50, 0.50, 0.50, 1.00, 1.00, 1.00);
private static final DoubleArray STRIKES = DoubleArray.of(0.7, 0.8, 0.9, 0.7, 0.8, 0.9, 0.7, 0.8, 0.9);
private static final DoubleArray VOL_ARRAY = DoubleArray.of(0.011, 0.012, 0.010, 0.012, 0.013, 0.011, 0.013, 0.014, 0.014);
private static final SurfaceMetadata METADATA = DefaultSurfaceMetadata.builder()
.surfaceName("Test")
.xValueType(ValueType.YEAR_FRACTION)
.yValueType(ValueType.STRIKE)
.zValueType(ValueType.BLACK_VOLATILITY)
.dayCount(ACT_365F)
.build();
private static final InterpolatedNodalSurface SURFACE =
InterpolatedNodalSurface.of(METADATA, TIMES, STRIKES, VOL_ARRAY, INTERPOLATOR_2D);
private static final LocalDate VAL_DATE = date(2015, 2, 17);
private static final LocalTime VAL_TIME = LocalTime.of(13, 45);
private static final ZoneId LONDON_ZONE = ZoneId.of("Europe/London");
private static final ZonedDateTime VAL_DATE_TIME = VAL_DATE.atTime(VAL_TIME).atZone(LONDON_ZONE);
private static final CurrencyPair CURRENCY_PAIR = CurrencyPair.of(EUR, GBP);
private static final BlackFxOptionSurfaceVolatilities VOLS =
BlackFxOptionSurfaceVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, SURFACE);
private static final LocalTime TIME = LocalTime.of(11, 45);
private static final ZonedDateTime[] TEST_EXPIRY = new ZonedDateTime[] {
date(2015, 2, 17).atTime(LocalTime.MIDNIGHT).atZone(LONDON_ZONE),
date(2015, 9, 17).atTime(TIME).atZone(LONDON_ZONE),
date(2016, 6, 17).atTime(TIME).atZone(LONDON_ZONE),
date(2018, 7, 17).atTime(TIME).atZone(LONDON_ZONE) };
private static final double[] FORWARD = new double[] {0.85, 0.82, 0.77, 0.76 };
private static final int NB_EXPIRY = TEST_EXPIRY.length;
private static final double[] TEST_STRIKE = new double[] {0.65, 0.73, 0.85, 0.92 };
private static final int NB_STRIKE = TEST_STRIKE.length;
private static final double TOLERANCE = 1.0E-12;
private static final double EPS = 1.0E-7;
//-------------------------------------------------------------------------
public void test_builder() {
BlackFxOptionSurfaceVolatilities test = BlackFxOptionSurfaceVolatilities.builder()
.currencyPair(CURRENCY_PAIR)
.surface(SURFACE)
.valuationDateTime(VAL_DATE_TIME)
.build();
assertEquals(test.getValuationDateTime(), VAL_DATE_TIME);
assertEquals(test.getCurrencyPair(), CURRENCY_PAIR);
assertEquals(test.getSurface(), SURFACE);
assertEquals(VOLS, test);
}
//-------------------------------------------------------------------------
public void test_volatility() {
for (int i = 0; i < NB_EXPIRY; i++) {
double expiryTime = VOLS.relativeTime(TEST_EXPIRY[i]);
for (int j = 0; j < NB_STRIKE; ++j) {
double volExpected = SURFACE.zValue(expiryTime, TEST_STRIKE[j]);
double volComputed = VOLS.volatility(CURRENCY_PAIR, TEST_EXPIRY[i], TEST_STRIKE[j], FORWARD[i]);
assertEquals(volComputed, volExpected, TOLERANCE);
}
}
}
public void test_volatility_inverse() {
for (int i = 0; i < NB_EXPIRY; i++) {
double expiryTime = VOLS.relativeTime(TEST_EXPIRY[i]);
for (int j = 0; j < NB_STRIKE; ++j) {
double volExpected = SURFACE.zValue(expiryTime, TEST_STRIKE[j]);
double volComputed = VOLS
.volatility(CURRENCY_PAIR.inverse(), TEST_EXPIRY[i], 1d / TEST_STRIKE[j], 1d / FORWARD[i]);
assertEquals(volComputed, volExpected, TOLERANCE);
}
}
}
//-------------------------------------------------------------------------
public void test_nodeSensitivity() {
for (int i = 0; i < NB_EXPIRY; i++) {
for (int j = 0; j < NB_STRIKE; ++j) {
double timeToExpiry = VOLS.relativeTime(TEST_EXPIRY[i]);
FxOptionSensitivity sensi = FxOptionSensitivity.of(
VOLS.getName(), CURRENCY_PAIR, timeToExpiry, TEST_STRIKE[j], FORWARD[i], GBP, 1d);
CurrencyParameterSensitivities computed = VOLS.parameterSensitivity(sensi);
for (int k = 0; k < SURFACE.getParameterCount(); k++) {
double value = computed.getSensitivities().get(0).getSensitivity().get(k);
double nodeExpiry = SURFACE.getXValues().get(k);
double nodeStrike = SURFACE.getYValues().get(k);
double expected = nodeSensitivity(
VOLS, CURRENCY_PAIR, TEST_EXPIRY[i], TEST_STRIKE[j], FORWARD[i], nodeExpiry, nodeStrike);
assertEquals(value, expected, EPS);
}
}
}
}
public void test_nodeSensitivity_inverse() {
for (int i = 0; i < NB_EXPIRY; i++) {
for (int j = 0; j < NB_STRIKE; ++j) {
double timeToExpiry = VOLS.relativeTime(TEST_EXPIRY[i]);
FxOptionSensitivity sensi = FxOptionSensitivity.of(
VOLS.getName(), CURRENCY_PAIR.inverse(), timeToExpiry, 1d / TEST_STRIKE[j], 1d / FORWARD[i], GBP, 1d);
CurrencyParameterSensitivities computed = VOLS.parameterSensitivity(sensi);
for (int k = 0; k < SURFACE.getParameterCount(); k++) {
double value = computed.getSensitivities().get(0).getSensitivity().get(k);
double nodeExpiry = SURFACE.getXValues().get(k);
double nodeStrike = SURFACE.getYValues().get(k);
double expected = nodeSensitivity(VOLS, CURRENCY_PAIR.inverse(),
TEST_EXPIRY[i], 1d / TEST_STRIKE[j], 1d / FORWARD[i], nodeExpiry, nodeStrike);
assertEquals(value, expected, EPS);
}
}
}
}
//-------------------------------------------------------------------------
public void coverage() {
BlackFxOptionSurfaceVolatilities test1 = BlackFxOptionSurfaceVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, SURFACE);
coverImmutableBean(test1);
BlackFxOptionSurfaceVolatilities test2 = BlackFxOptionSurfaceVolatilities.of(
CURRENCY_PAIR.inverse(),
ZonedDateTime.of(2015, 12, 21, 11, 15, 0, 0, ZoneId.of("Z")),
SURFACE);
coverBeanEquals(test1, test2);
}
//-------------------------------------------------------------------------
// bumping a node point at (nodeExpiry, nodeStrike)
private double nodeSensitivity(
BlackFxOptionSurfaceVolatilities provider,
CurrencyPair pair,
ZonedDateTime expiry,
double strike,
double forward,
double nodeExpiry,
double nodeStrike) {
NodalSurface surface = (NodalSurface) provider.getSurface();
DoubleArray xValues = surface.getXValues();
DoubleArray yValues = surface.getYValues();
DoubleArray zValues = surface.getZValues();
int nData = xValues.size();
int index = -1;
for (int i = 0; i < nData; ++i) {
if (Math.abs(xValues.get(i) - nodeExpiry) < TOLERANCE && Math.abs(yValues.get(i) - nodeStrike) < TOLERANCE) {
index = i;
}
}
Surface surfaceUp = surface.withZValues(zValues.with(index, zValues.get(index) + EPS));
Surface surfaceDw = surface.withZValues(zValues.with(index, zValues.get(index) - EPS));
BlackFxOptionSurfaceVolatilities provUp = BlackFxOptionSurfaceVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, surfaceUp);
BlackFxOptionSurfaceVolatilities provDw = BlackFxOptionSurfaceVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, surfaceDw);
double volUp = provUp.volatility(pair, expiry, strike, forward);
double volDw = provDw.volatility(pair, expiry, strike, forward);
return 0.5 * (volUp - volDw) / EPS;
}
}