/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.swaption;
import static com.opengamma.strata.basics.date.DayCounts.ACT_365F;
import static com.opengamma.strata.product.swap.type.FixedIborSwapConventions.USD_FIXED_6M_LIBOR_3M;
import static org.testng.Assert.assertEquals;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.BitSet;
import org.testng.annotations.Test;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.tuple.Pair;
import com.opengamma.strata.market.ValueType;
import com.opengamma.strata.math.impl.statistics.leastsquare.LeastSquareResultsWithTransform;
import com.opengamma.strata.pricer.impl.option.BlackFormulaRepository;
import com.opengamma.strata.pricer.impl.option.NormalFormulaRepository;
import com.opengamma.strata.pricer.impl.volatility.smile.SabrFormulaData;
import com.opengamma.strata.pricer.model.SabrVolatilityFormula;
import com.opengamma.strata.product.common.PutCall;
/**
* Tests {@link SabrSwaptionCalibrator} with single smile.
*/
@Test
public class SabrSwaptionCalibratorSmileTest {
private static final ReferenceData REF_DATA = ReferenceData.standard();
private static final LocalDate CALIBRATION_DATE = LocalDate.of(2015, 8, 7);
private static final ZonedDateTime CALIBRATION_TIME = CALIBRATION_DATE.atTime(11, 0).atZone(ZoneId.of("America/New_York"));
private static final SabrVolatilityFormula SABR_FORMULA = SabrVolatilityFormula.hagan();
private static final SabrSwaptionCalibrator SABR_CALIBRATION = SabrSwaptionCalibrator.DEFAULT;
private static final Period EXPIRY_PERIOD = Period.ofYears(5);
private static final BusinessDayAdjustment BDA = USD_FIXED_6M_LIBOR_3M.getFloatingLeg().getStartDateBusinessDayAdjustment();
private static final LocalDate SWAPTION_EXERCISE_DATE =
USD_FIXED_6M_LIBOR_3M.getFixedLeg().getStartDateBusinessDayAdjustment()
.adjust(CALIBRATION_DATE.plus(EXPIRY_PERIOD), REF_DATA);
private static final double TIME_EXPIRY = ACT_365F.relativeYearFraction(CALIBRATION_DATE, SWAPTION_EXERCISE_DATE);
private static final double FORWARD = 0.02075;
private static final DoubleArray MONEYNESS_5 =
DoubleArray.of(-0.0100, -0.0050, 0.0000, 0.0050, 0.0100);
private static final DoubleArray MONEYNESS_3 = // Used for (almost) exact calibration
DoubleArray.of(-0.0100, 0.0000, 0.0100);
private static final DoubleArray VOLATILITY_BLACK_5 =
DoubleArray.of(0.34, 0.32, 0.30, 0.29, 0.29);
private static final DoubleArray VOLATILITY_BLACK_3 = // Used for (almost) exact calibration
DoubleArray.of(0.335, 0.30, 0.29);
private static final DoubleArray VOLATILITY_NORMAL_5 =
DoubleArray.of(0.0110, 0.0098, 0.0092, 0.0103, 0.0120);
private static final DoubleArray VOLATILITY_NORMAL_3 = // Used for (almost) exact calibration
DoubleArray.of(0.0110, 0.0092, 0.0120);
private static final double TOLERANCE_PRICE_CALIBRATION_LS = 1.0E-4; // Calibration Least Square; result not exact
private static final double TOLERANCE_PRICE_CALIBRATION_EX = 1.0E-6; // With 3 points, calibration should be almost exact
public void calibrate_smile_normal_beta_fixed_5() {
double beta = 0.50;
double shift = 0.0100; // 100 bps
DoubleArray startParameters = DoubleArray.of(0.05, beta, 0.0, 0.1);
BitSet fixed = new BitSet();
fixed.set(1); // Beta fixed
checkCalibrationNormal(MONEYNESS_5, VOLATILITY_NORMAL_5, startParameters, fixed, shift, TOLERANCE_PRICE_CALIBRATION_LS);
}
public void calibrate_smile_normal_beta_fixed_3() {
double beta = 0.50;
double shift = 0.0100; // 100 bps
DoubleArray startParameters = DoubleArray.of(0.05, beta, 0.0, 0.1);
BitSet fixed = new BitSet();
fixed.set(1); // Beta fixed
checkCalibrationNormal(MONEYNESS_3, VOLATILITY_NORMAL_3, startParameters, fixed, shift, TOLERANCE_PRICE_CALIBRATION_EX);
}
public void calibrate_smile_normal_rho_fixed_5() {
double rho = 0.25;
double shift = 0.0100; // 100 bps
DoubleArray startParameters = DoubleArray.of(0.05, 0.50, rho, 0.1);
BitSet fixed = new BitSet();
fixed.set(2); // Rho fixed
checkCalibrationNormal(MONEYNESS_5, VOLATILITY_NORMAL_5, startParameters, fixed, shift, 20 * TOLERANCE_PRICE_CALIBRATION_LS);
}
public void calibrate_smile_black_no_shift_beta_fixed_5() {
double beta = 0.50;
DoubleArray startParameters = DoubleArray.of(0.05, beta, 0.0, 0.1);
BitSet fixed = new BitSet();
fixed.set(1); // Beta fixed
checkCalibrationBlack(MONEYNESS_5, VOLATILITY_BLACK_5, startParameters, fixed, 0.0, TOLERANCE_PRICE_CALIBRATION_LS);
}
public void calibrate_smile_black_no_shift_beta_fixed_3() {
double beta = 0.50;
DoubleArray startParameters = DoubleArray.of(0.05, beta, 0.0, 0.1);
BitSet fixed = new BitSet();
fixed.set(1); // Beta fixed
checkCalibrationBlack(MONEYNESS_3, VOLATILITY_BLACK_3, startParameters, fixed, 0.0, TOLERANCE_PRICE_CALIBRATION_EX);
}
public void calibrate_smile_price_beta_fixed_5() {
double beta = 0.50;
double shift = 0.0100; // 100 bps
DoubleArray startParameters = DoubleArray.of(0.05, beta, 0.0, 0.1);
BitSet fixed = new BitSet();
fixed.set(1); // Beta fixed
checkCalibrationPrice(MONEYNESS_5, VOLATILITY_BLACK_5, startParameters, fixed, shift, TOLERANCE_PRICE_CALIBRATION_LS);
}
public void calibrate_smile_price_beta_fixed_3() {
double beta = 0.50;
double shift = 0.0100; // 100 bps
DoubleArray startParameters = DoubleArray.of(0.05, beta, 0.0, 0.1);
BitSet fixed = new BitSet();
fixed.set(1); // Beta fixed
checkCalibrationPrice(MONEYNESS_3, VOLATILITY_BLACK_3, startParameters, fixed, shift, TOLERANCE_PRICE_CALIBRATION_EX);
}
public void calibrate_smile_price_rho_fixed_5() {
double rho = 0.25;
double shift = 0.0100; // 100 bps
DoubleArray startParameters = DoubleArray.of(0.05, 0.50, rho, 0.1);
BitSet fixed = new BitSet();
fixed.set(2); // Rho fixed
checkCalibrationPrice(MONEYNESS_5, VOLATILITY_BLACK_5, startParameters, fixed, shift, TOLERANCE_PRICE_CALIBRATION_LS);
}
private void checkCalibrationNormal(
DoubleArray moneyness,
DoubleArray normalVol,
DoubleArray startParameters,
BitSet fixed,
double shift,
double tolerance) {
Pair<LeastSquareResultsWithTransform, DoubleArray> rComputed = SABR_CALIBRATION
.calibrateLsShiftedFromNormalVolatilities(BDA, CALIBRATION_TIME, ACT_365F, EXPIRY_PERIOD, FORWARD,
moneyness, ValueType.SIMPLE_MONEYNESS, normalVol, startParameters, fixed, shift);
SabrFormulaData sabrComputed = SabrFormulaData.of(rComputed.getFirst().getModelParameters().toArrayUnsafe());
for (int i = 0; i < moneyness.size(); i++) {
double ivComputed = SABR_FORMULA.volatility(
FORWARD + shift,
FORWARD + moneyness.get(i) + shift,
TIME_EXPIRY,
sabrComputed.getAlpha(),
sabrComputed.getBeta(),
sabrComputed.getRho(),
sabrComputed.getNu());
double priceComputed = BlackFormulaRepository.price(FORWARD + shift, FORWARD + moneyness.get(i) + shift,
TIME_EXPIRY, ivComputed, true);
double priceNormal = NormalFormulaRepository.price(FORWARD, FORWARD + moneyness.get(i),
TIME_EXPIRY, normalVol.get(i), PutCall.CALL);
assertEquals(priceComputed, priceNormal, tolerance);
}
}
private void checkCalibrationBlack(
DoubleArray moneyness,
DoubleArray blackVol,
DoubleArray startParameters,
BitSet fixed,
double shift,
double tolerance) {
Pair<LeastSquareResultsWithTransform, DoubleArray> rComputed = SABR_CALIBRATION
.calibrateLsShiftedFromBlackVolatilities(BDA, CALIBRATION_TIME, ACT_365F, EXPIRY_PERIOD, FORWARD,
moneyness, ValueType.SIMPLE_MONEYNESS, blackVol, 0.0, startParameters, fixed, shift);
SabrFormulaData sabrComputed = SabrFormulaData.of(rComputed.getFirst().getModelParameters().toArrayUnsafe());
for (int i = 0; i < moneyness.size(); i++) {
double ivComputed = SABR_FORMULA.volatility(
FORWARD + shift,
FORWARD + moneyness.get(i) + shift,
TIME_EXPIRY,
sabrComputed.getAlpha(),
sabrComputed.getBeta(),
sabrComputed.getRho(),
sabrComputed.getNu());
double priceComputed = BlackFormulaRepository.price(FORWARD + shift, FORWARD + moneyness.get(i) + shift,
TIME_EXPIRY, ivComputed, true);
double priceBlack = BlackFormulaRepository.price(FORWARD, FORWARD + moneyness.get(i),
TIME_EXPIRY, blackVol.get(i), true);
assertEquals(priceComputed, priceBlack, tolerance);
// System.out.println("Black: " + priceComputed + " / " + priceBlack);
}
}
private void checkCalibrationPrice(
DoubleArray moneyness,
DoubleArray blackVol,
DoubleArray startParameters,
BitSet fixed,
double shift,
double tolerance) {
double[] prices = new double[moneyness.size()];
for (int i = 0; i < moneyness.size(); i++) {
prices[i] = BlackFormulaRepository
.price(FORWARD, FORWARD + moneyness.get(i), TIME_EXPIRY, blackVol.get(i), true);
// Prices generated from Black implied volatilities
}
Pair<LeastSquareResultsWithTransform, DoubleArray> rComputed = SABR_CALIBRATION
.calibrateLsShiftedFromPrices(BDA, CALIBRATION_TIME, ACT_365F, EXPIRY_PERIOD, FORWARD,
moneyness, ValueType.SIMPLE_MONEYNESS, DoubleArray.ofUnsafe(prices), startParameters, fixed, shift);
SabrFormulaData sabrComputed = SabrFormulaData.of(rComputed.getFirst().getModelParameters().toArrayUnsafe());
for (int i = 0; i < moneyness.size(); i++) {
double ivComputed = SABR_FORMULA.volatility(
FORWARD + shift,
FORWARD + moneyness.get(i) + shift,
TIME_EXPIRY,
sabrComputed.getAlpha(),
sabrComputed.getBeta(),
sabrComputed.getRho(),
sabrComputed.getNu());
double priceComputed = BlackFormulaRepository.price(FORWARD + shift, FORWARD + moneyness.get(i) + shift,
TIME_EXPIRY, ivComputed, true);
assertEquals(priceComputed, prices[i], tolerance);
}
}
}