/**
* 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 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.curve.CurveMetadata;
import com.opengamma.strata.market.curve.DefaultCurveMetadata;
import com.opengamma.strata.market.curve.InterpolatedNodalCurve;
import com.opengamma.strata.market.curve.NodalCurve;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolator;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolators;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
/**
* Test {@link BlackFxOptionFlatVolatilities}.
*/
@Test
public class BlackFxOptionFlatVolatilitiesTest {
private static final CurveInterpolator INTERPOLATOR = CurveInterpolators.LINEAR;
private static final DoubleArray TIMES = DoubleArray.of(0.5, 1.0, 3.0);
private static final DoubleArray VOL_ARRAY = DoubleArray.of(0.05, 0.09, 0.16);
private static final CurveMetadata METADATA = DefaultCurveMetadata.builder()
.curveName("Test")
.xValueType(ValueType.YEAR_FRACTION)
.yValueType(ValueType.BLACK_VOLATILITY)
.dayCount(ACT_365F)
.build();
private static final InterpolatedNodalCurve CURVE = InterpolatedNodalCurve.of(METADATA, TIMES, VOL_ARRAY, INTERPOLATOR);
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 BlackFxOptionFlatVolatilities VOLS =
BlackFxOptionFlatVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, CURVE);
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.75, 0.68 };
private static final int NB_EXPIRY = TEST_EXPIRY.length;
private static final double[] TEST_STRIKE = new double[] {0.67, 0.81, 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() {
BlackFxOptionFlatVolatilities test = BlackFxOptionFlatVolatilities.builder()
.currencyPair(CURRENCY_PAIR)
.curve(CURVE)
.valuationDateTime(VAL_DATE_TIME)
.build();
assertEquals(test.getValuationDateTime(), VAL_DATE_TIME);
assertEquals(test.getCurrencyPair(), CURRENCY_PAIR);
assertEquals(test.getName(), FxOptionVolatilitiesName.of(CURVE.getName().getName()));
assertEquals(test.getCurve(), CURVE);
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 = CURVE.yValue(expiryTime);
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 = CURVE.yValue(expiryTime);
double volComputed = VOLS
.volatility(CURRENCY_PAIR.inverse(), TEST_EXPIRY[i], 1d / TEST_STRIKE[j], 1d / FORWARD[i]);
assertEquals(volComputed, volExpected, TOLERANCE);
}
}
}
//-------------------------------------------------------------------------
public void test_parameterSensitivity() {
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 < TIMES.size(); k++) {
double value = computed.getSensitivities().get(0).getSensitivity().get(k);
double nodeExpiry = TIMES.get(k);
double expected = nodeSensitivity(
VOLS, CURRENCY_PAIR, TEST_EXPIRY[i], TEST_STRIKE[j], FORWARD[i], nodeExpiry);
assertEquals(value, expected, EPS);
}
}
}
}
public void test_parameterSensitivity_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 < TIMES.size(); k++) {
double value = computed.getSensitivities().get(0).getSensitivity().get(k);
double nodeExpiry = TIMES.get(k);
double expected = nodeSensitivity(
VOLS, CURRENCY_PAIR.inverse(), TEST_EXPIRY[i], 1d / TEST_STRIKE[j], 1d / FORWARD[i], nodeExpiry);
assertEquals(value, expected, EPS);
}
}
}
}
//-------------------------------------------------------------------------
public void coverage() {
BlackFxOptionFlatVolatilities test1 = BlackFxOptionFlatVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, CURVE);
coverImmutableBean(test1);
BlackFxOptionFlatVolatilities test2 = BlackFxOptionFlatVolatilities.of(
CURRENCY_PAIR.inverse(), ZonedDateTime.of(2015, 12, 21, 11, 15, 0, 0, ZoneId.of("Z")), CURVE);
coverBeanEquals(test1, test2);
}
//-------------------------------------------------------------------------
// bumping a node point at nodeExpiry
private double nodeSensitivity(
BlackFxOptionFlatVolatilities provider,
CurrencyPair pair,
ZonedDateTime expiry,
double strike,
double forward,
double nodeExpiry) {
NodalCurve curve = (NodalCurve) provider.getCurve();
DoubleArray xValues = curve.getXValues();
DoubleArray yValues = curve.getYValues();
int nData = xValues.size();
int index = -1;
for (int i = 0; i < nData; ++i) {
if (Math.abs(xValues.get(i) - nodeExpiry) < TOLERANCE) {
index = i;
}
}
NodalCurve curveUp = curve.withYValues(yValues.with(index, yValues.get(index) + EPS));
NodalCurve curveDw = curve.withYValues(yValues.with(index, yValues.get(index) - EPS));
BlackFxOptionFlatVolatilities provUp = BlackFxOptionFlatVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, curveUp);
BlackFxOptionFlatVolatilities provDw = BlackFxOptionFlatVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, curveDw);
double volUp = provUp.volatility(pair, expiry, strike, forward);
double volDw = provDw.volatility(pair, expiry, strike, forward);
return 0.5 * (volUp - volDw) / EPS;
}
}