/*
* (c) Copyright Christian P. Fries, Germany. All rights reserved. Contact: email@christian-fries.de.
*
* Created on 16.01.2015
*/
package net.finmath.tests.montecarlo.interestrate;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.joda.time.DateTimeConstants;
import org.joda.time.LocalDate;
import org.junit.Assert;
import org.junit.Test;
import net.finmath.exception.CalculationException;
import net.finmath.marketdata.calibration.ParameterObjectInterface;
import net.finmath.marketdata.calibration.Solver;
import net.finmath.marketdata.model.AnalyticModel;
import net.finmath.marketdata.model.AnalyticModelInterface;
import net.finmath.marketdata.model.curves.Curve.ExtrapolationMethod;
import net.finmath.marketdata.model.curves.Curve.InterpolationEntity;
import net.finmath.marketdata.model.curves.Curve.InterpolationMethod;
import net.finmath.marketdata.model.curves.CurveInterface;
import net.finmath.marketdata.model.curves.DiscountCurve;
import net.finmath.marketdata.model.curves.DiscountCurveFromForwardCurve;
import net.finmath.marketdata.model.curves.DiscountCurveInterface;
import net.finmath.marketdata.model.curves.ForwardCurve;
import net.finmath.marketdata.model.curves.ForwardCurveFromDiscountCurve;
import net.finmath.marketdata.model.curves.ForwardCurveInterface;
import net.finmath.marketdata.products.AnalyticProductInterface;
import net.finmath.marketdata.products.Swap;
import net.finmath.montecarlo.BrownianMotionInterface;
import net.finmath.montecarlo.BrownianMotionView;
import net.finmath.montecarlo.interestrate.LIBORMarketModel;
import net.finmath.montecarlo.interestrate.LIBORMarketModel.CalibrationItem;
import net.finmath.montecarlo.interestrate.LIBORModelInterface;
import net.finmath.montecarlo.interestrate.LIBORModelMonteCarloSimulation;
import net.finmath.montecarlo.interestrate.LIBORModelMonteCarloSimulationInterface;
import net.finmath.montecarlo.interestrate.modelplugins.AbstractLIBORCovarianceModelParametric;
import net.finmath.montecarlo.interestrate.modelplugins.BlendedLocalVolatilityModel;
import net.finmath.montecarlo.interestrate.modelplugins.DisplacedLocalVolatilityModel;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORCorrelationModel;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORCorrelationModelExponentialDecay;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORCovarianceModelExponentialForm5Param;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORCovarianceModelFromVolatilityAndCorrelation;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORCovarianceModelStochasticVolatility;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORVolatilityModel;
import net.finmath.montecarlo.interestrate.modelplugins.LIBORVolatilityModelPiecewiseConstant;
import net.finmath.montecarlo.interestrate.products.AbstractLIBORMonteCarloProduct;
import net.finmath.montecarlo.interestrate.products.SwaptionSimple;
import net.finmath.montecarlo.process.ProcessEulerScheme;
import net.finmath.optimizer.OptimizerFactoryInterface;
import net.finmath.optimizer.OptimizerFactoryLevenbergMarquardt;
import net.finmath.optimizer.SolverException;
import net.finmath.time.ScheduleGenerator;
import net.finmath.time.ScheduleInterface;
import net.finmath.time.TimeDiscretization;
import net.finmath.time.TimeDiscretizationInterface;
import net.finmath.time.businessdaycalendar.BusinessdayCalendarExcludingTARGETHolidays;
import net.finmath.time.daycount.DayCountConvention_ACT_365;
/**
* This class tests the LIBOR market model and products.
*
* @author Christian Fries
*/
public class LIBORMarketModelCalibrationTest {
private static DecimalFormat formatterValue = new DecimalFormat(" ##0.000%;-##0.000%", new DecimalFormatSymbols(Locale.ENGLISH));
private static DecimalFormat formatterParam = new DecimalFormat(" #0.000;-#0.000", new DecimalFormatSymbols(Locale.ENGLISH));
private static DecimalFormat formatterDeviation = new DecimalFormat(" 0.00000E00;-0.00000E00", new DecimalFormatSymbols(Locale.ENGLISH));
private CalibrationItem createCalibrationItem(double weight, double exerciseDate, double swapPeriodLength, int numberOfPeriods, double moneyness, double targetVolatility, String targetVolatilityType, ForwardCurveInterface forwardCurve, DiscountCurveInterface discountCurve) throws CalculationException {
double[] fixingDates = new double[numberOfPeriods];
double[] paymentDates = new double[numberOfPeriods];
double[] swapTenor = new double[numberOfPeriods + 1];
for (int periodStartIndex = 0; periodStartIndex < numberOfPeriods; periodStartIndex++) {
fixingDates[periodStartIndex] = exerciseDate + periodStartIndex * swapPeriodLength;
paymentDates[periodStartIndex] = exerciseDate + (periodStartIndex + 1) * swapPeriodLength;
swapTenor[periodStartIndex] = exerciseDate + periodStartIndex * swapPeriodLength;
}
swapTenor[numberOfPeriods] = exerciseDate + numberOfPeriods * swapPeriodLength;
// Swaptions swap rate
double swaprate = moneyness + getParSwaprate(forwardCurve, discountCurve, swapTenor);
// Set swap rates for each period
double[] swaprates = new double[numberOfPeriods];
Arrays.fill(swaprates, swaprate);
/*
* We use Monte-Carlo calibration on implied volatility.
* Alternatively you may change here to Monte-Carlo valuation on price or
* use an analytic approximation formula, etc.
*/
SwaptionSimple swaptionMonteCarlo = new SwaptionSimple(swaprate, swapTenor, SwaptionSimple.ValueUnit.valueOf(targetVolatilityType));
// double targetValuePrice = AnalyticFormulas.blackModelSwaptionValue(swaprate, targetVolatility, fixingDates[0], swaprate, getSwapAnnuity(discountCurve, swapTenor));
return new CalibrationItem(swaptionMonteCarlo, targetVolatility, weight);
}
@Test
public void testSwaptionSmileCalibration() throws CalculationException {
final int numberOfPaths = 5000;
final int numberOfFactors = 5;
/*
* Calibration test
*/
System.out.println("Calibration to Swaption Smile Products.");
/*
* Calibration of curves
*/
System.out.println("Calibration of rate curves:");
double[] fixingTimes = new double[] {
0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 16.0, 16.5, 17.0, 17.5, 18.0, 18.5, 19.0, 19.5, 20.0, 20.5, 21.0, 21.5, 22.0, 22.5, 23.0, 23.5, 24.0, 24.5, 25.0, 25.5, 26.0, 26.5, 27.0, 27.5, 28.0, 28.5, 29.0, 29.5, 30.0, 30.5, 31.0, 31.5, 32.0, 32.5, 33.0, 33.5, 34.0, 34.5, 35.0, 35.5, 36.0, 36.5, 37.0, 37.5, 38.0, 38.5, 39.0, 39.5, 40.0, 40.5, 41.0, 41.5, 42.0, 42.5, 43.0, 43.5, 44.0, 44.5, 45.0, 45.5, 46.0, 46.5, 47.0, 47.5, 48.0, 48.5, 49.0, 49.5, 50.0
};
double[] forwardRates = new double[] {
0.61/100.0, 0.61/100.0, 0.67/100.0, 0.73/100.0, 0.80/100.0, 0.92/100.0, 1.11/100.0, 1.36/100.0, 1.60/100.0, 1.82/100.0, 2.02/100.0, 2.17/100.0, 2.27/100.0, 2.36/100.0, 2.46/100.0, 2.52/100.0, 2.54/100.0, 2.57/100.0, 2.68/100.0, 2.82/100.0, 2.92/100.0, 2.98/100.0, 3.00/100.0, 2.99/100.0, 2.95/100.0, 2.89/100.0, 2.82/100.0, 2.74/100.0, 2.66/100.0, 2.59/100.0, 2.52/100.0, 2.47/100.0, 2.42/100.0, 2.38/100.0, 2.35/100.0, 2.33/100.0, 2.31/100.0, 2.30/100.0, 2.29/100.0, 2.28/100.0, 2.27/100.0, 2.27/100.0, 2.26/100.0, 2.26/100.0, 2.26/100.0, 2.26/100.0, 2.26/100.0, 2.26/100.0, 2.27/100.0, 2.28/100.0, 2.28/100.0, 2.30/100.0, 2.31/100.0, 2.32/100.0, 2.34/100.0, 2.35/100.0, 2.37/100.0, 2.39/100.0, 2.42/100.0, 2.44/100.0, 2.47/100.0, 2.50/100.0, 2.52/100.0, 2.56/100.0, 2.59/100.0, 2.62/100.0, 2.65/100.0, 2.68/100.0, 2.72/100.0, 2.75/100.0, 2.78/100.0, 2.81/100.0, 2.83/100.0, 2.86/100.0, 2.88/100.0, 2.91/100.0, 2.93/100.0, 2.94/100.0, 2.96/100.0, 2.97/100.0, 2.97/100.0, 2.97/100.0, 2.97/100.0, 2.97/100.0, 2.96/100.0, 2.95/100.0, 2.94/100.0, 2.93/100.0, 2.91/100.0, 2.89/100.0, 2.87/100.0, 2.85/100.0, 2.83/100.0, 2.80/100.0, 2.78/100.0, 2.75/100.0, 2.72/100.0, 2.69/100.0, 2.67/100.0, 2.64/100.0, 2.64/100.0
};
double liborPeriodLength = 0.5;
// Create the forward curve (initial value of the LIBOR market model)
ForwardCurve forwardCurve = ForwardCurve.createForwardCurveFromForwards(
"forwardCurve" /* name of the curve */,
fixingTimes /* fixings of the forward */,
forwardRates /* forwards */,
liborPeriodLength /* tenor / period length */
);
DiscountCurveInterface discountCurve = new DiscountCurveFromForwardCurve(forwardCurve, liborPeriodLength);
/*
* Create a set of calibration products.
*/
ArrayList<CalibrationItem> calibrationItems = new ArrayList<CalibrationItem>();
double swapPeriodLength = 0.5;
int numberOfPeriods = 20;
double[] smileMoneynesses = { -0.02, -0.01, -0.005, -0.0025, 0.0, 0.0025, 0.0050, 0.01, 0.02 };
double[] smileVolatilities = { 0.559, 0.377, 0.335, 0.320, 0.308, 0.298, 0.290, 0.280, 0.270 };
for(int i=0; i<smileMoneynesses.length; i++ ) {
double exerciseDate = 5.0;
double moneyness = smileMoneynesses[i];
double targetVolatility = smileVolatilities[i];
calibrationItems.add(createCalibrationItem(1.0 /* weight */, exerciseDate, swapPeriodLength, numberOfPeriods, moneyness, targetVolatility, "VOLATILITYLOGNORMAL", forwardCurve, discountCurve));
}
double[] atmOptionMaturities = { 2.00, 3.00, 4.00, 5.00, 7.00, 10.00, 15.00, 20.00, 25.00, 30.00 };
double[] atmOptionVolatilities = { 0.385, 0.351, 0.325, 0.308, 0.288, 0.279, 0.290, 0.272, 0.235, 0.192 };
for(int i=0; i<atmOptionMaturities.length; i++ ) {
double exerciseDate = atmOptionMaturities[i];
double moneyness = 0.0;
double targetVolatility = atmOptionVolatilities[i];
calibrationItems.add(createCalibrationItem(1.0 /* weight */, exerciseDate, swapPeriodLength, numberOfPeriods, moneyness, targetVolatility, "VOLATILITYLOGNORMAL", forwardCurve, discountCurve));
}
/*
* Create a LIBOR Market Model
*/
/*
* Create the libor tenor structure and the initial values
*/
double liborRateTimeHorzion = 20.0;
TimeDiscretization liborPeriodDiscretization = new TimeDiscretization(0.0, (int) (liborRateTimeHorzion / liborPeriodLength), liborPeriodLength);
/*
* Create a simulation time discretization
*/
double lastTime = 20.0;
double dt = 0.5;
TimeDiscretization timeDiscretization = new TimeDiscretization(0.0, (int) (lastTime / dt), dt);
/*
* Create Brownian motions
*/
BrownianMotionInterface brownianMotion = new net.finmath.montecarlo.BrownianMotion(timeDiscretization, numberOfFactors + 1, numberOfPaths, 31415 /* seed */);
BrownianMotionInterface brownianMotionView1 = new BrownianMotionView(brownianMotion, new Integer[] { 0, 1, 2, 3, 4 });
BrownianMotionInterface brownianMotionView2 = new BrownianMotionView(brownianMotion, new Integer[] { 0, 5 });
// Create a covariance model
AbstractLIBORCovarianceModelParametric covarianceModelParametric = new LIBORCovarianceModelExponentialForm5Param(timeDiscretization, liborPeriodDiscretization, numberOfFactors, new double[] { 0.20, 0.05, 0.10, 0.05, 0.10} );
// Create blended local volatility model with fixed parameter 0.0 (that is "lognormal").
AbstractLIBORCovarianceModelParametric covarianceModelBlended = new BlendedLocalVolatilityModel(covarianceModelParametric, 0.0, false);
// Create stochastic scaling (pass brownianMotionView2 to it)
AbstractLIBORCovarianceModelParametric covarianceModelStochasticParametric = new LIBORCovarianceModelStochasticVolatility(covarianceModelBlended, brownianMotionView2, 0.01, -0.30, true);
// Set model properties
Map<String, Object> properties = new HashMap<String, Object>();
// Choose the simulation measure
properties.put("measure", LIBORMarketModel.Measure.SPOT.name());
// Choose normal state space for the Euler scheme (the covariance model above carries a linear local volatility model, such that the resulting model is log-normal).
properties.put("stateSpace", LIBORMarketModel.StateSpace.NORMAL.name());
// Set calibration properties (should use our brownianMotion for calibration - needed to have to right correlation).
Map<String, Object> calibrationParameters = new HashMap<String, Object>();
// The brownianMotion to be used - if a full Monte-Carlo valuation is necessary.
calibrationParameters.put("brownianMotion", brownianMotionView1);
// The step size vector used to calculate first derivatives via finite differences
calibrationParameters.put("parameterStep", new Double(1E-5));
/*
* The optimizer to use and some of its parameters
*/
// The accuracy of the slower. The solver steps if the value does not improve more thatn the given parameter.
Double accuracy = new Double(1E-8);
int maxIterations = 100;
int numberOfThreads = 2; // two concurrent models
OptimizerFactoryInterface optimizerFactory = new OptimizerFactoryLevenbergMarquardt(maxIterations, accuracy, numberOfThreads);
calibrationParameters.put("optimizerFactory", optimizerFactory);
// Pass the calibrationParameters to the model.
properties.put("calibrationParameters", calibrationParameters);
LIBORMarketModel liborMarketModelCalibrated = new LIBORMarketModel(
liborPeriodDiscretization,
forwardCurve, discountCurve, covarianceModelStochasticParametric, calibrationItems.toArray(new CalibrationItem[0]), properties);
/*
* Test our calibration
*/
System.out.println("\nCalibrated parameters are:");
double[] param = ((AbstractLIBORCovarianceModelParametric) liborMarketModelCalibrated.getCovarianceModel()).getParameter();
// ((AbstractLIBORCovarianceModelParametric) liborMarketModelCalibrated.getCovarianceModel()).setParameter(param);
for (double p : param) System.out.println(formatterParam.format(p));
ProcessEulerScheme process = new ProcessEulerScheme(brownianMotionView1);
net.finmath.montecarlo.interestrate.LIBORModelMonteCarloSimulation simulationCalibrated = new net.finmath.montecarlo.interestrate.LIBORModelMonteCarloSimulation(
liborMarketModelCalibrated, process);
System.out.println("\nValuation on calibrated model:");
double deviationSum = 0.0;
double deviationSquaredSum = 0.0;
for (int i = 0; i < calibrationItems.size(); i++) {
AbstractLIBORMonteCarloProduct calibrationProduct = calibrationItems.get(i).calibrationProduct;
try {
double valueModel = calibrationProduct.getValue(simulationCalibrated);
double valueTarget = calibrationItems.get(i).calibrationTargetValue;
double error = valueModel-valueTarget;
deviationSum += error;
deviationSquaredSum += error*error;
System.out.println("Model: " + formatterValue.format(valueModel) + "\t Target: " + formatterValue.format(valueTarget) + "\t Deviation: " + formatterDeviation.format(valueModel-valueTarget) + "\t" + calibrationProduct.toString());
}
catch(Exception e) {
//
}
}
double averageDeviation = deviationSum/calibrationItems.size();
System.out.println("Mean Deviation:" + formatterValue.format(averageDeviation));
System.out.println("RMS Error.....:" + formatterValue.format(Math.sqrt(deviationSquaredSum/calibrationItems.size())));
System.out.println("__________________________________________________________________________________________\n");
Assert.assertTrue(Math.abs(averageDeviation) < 1E-2);
}
/**
* Brute force Monte-Carlo calibration of swaptions.
*
* @throws CalculationException
* @throws SolverException
*/
@Test
public void testATMSwaptionCalibration() throws CalculationException, SolverException {
final int numberOfPaths = 1000;
final int numberOfFactors = 1;
long millisCurvesStart = System.currentTimeMillis();
/*
* Calibration test
*/
System.out.println("Calibration to Swaptions.\n");
/*
* Calibration of rate curves
*/
System.out.println("Calibration of rate curves:");
final AnalyticModelInterface curveModel = getCalibratedCurve();
// Create the forward curve (initial value of the LIBOR market model)
final ForwardCurveInterface forwardCurve = curveModel.getForwardCurve("ForwardCurveFromDiscountCurve(discountCurve-EUR,6M)");
final DiscountCurveInterface discountCurve = curveModel.getDiscountCurve("discountCurve-EUR");
// curveModel.addCurve(discountCurve.getName(), discountCurve);
long millisCurvesEnd = System.currentTimeMillis();
System.out.println("");
/*
* Calibration of model volatilities
*/
System.out.println("Brute force Monte-Carlo calibration of model volatilities:");
/*
* Create a set of calibration products.
*/
ArrayList<String> calibrationItemNames = new ArrayList<String>();
final ArrayList<CalibrationItem> calibrationItems = new ArrayList<CalibrationItem>();
double swapPeriodLength = 0.5;
String[] atmExpiries = { "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "1M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "3M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "6M", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "1Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "2Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "3Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "4Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "7Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "10Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "15Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "20Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "25Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y", "30Y" };
String[] atmTenors = { "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y" };
double[] atmNormalVolatilities = { 0.00151, 0.00169, 0.0021, 0.00248, 0.00291, 0.00329, 0.00365, 0.004, 0.00437, 0.00466, 0.00527, 0.00571, 0.00604, 0.00625, 0.0016, 0.00174, 0.00217, 0.00264, 0.00314, 0.00355, 0.00398, 0.00433, 0.00469, 0.00493, 0.00569, 0.00607, 0.00627, 0.00645, 0.00182, 0.00204, 0.00238, 0.00286, 0.00339, 0.00384, 0.00424, 0.00456, 0.00488, 0.0052, 0.0059, 0.00623, 0.0064, 0.00654, 0.00205, 0.00235, 0.00272, 0.0032, 0.00368, 0.00406, 0.00447, 0.00484, 0.00515, 0.00544, 0.00602, 0.00629, 0.0064, 0.00646, 0.00279, 0.00319, 0.0036, 0.00396, 0.00436, 0.00469, 0.00503, 0.0053, 0.00557, 0.00582, 0.00616, 0.00628, 0.00638, 0.00641, 0.00379, 0.00406, 0.00439, 0.00472, 0.00504, 0.00532, 0.0056, 0.00582, 0.00602, 0.00617, 0.0063, 0.00636, 0.00638, 0.00639, 0.00471, 0.00489, 0.00511, 0.00539, 0.00563, 0.00583, 0.006, 0.00618, 0.0063, 0.00644, 0.00641, 0.00638, 0.00635, 0.00634, 0.00544, 0.00557, 0.00572, 0.00591, 0.00604, 0.00617, 0.0063, 0.00641, 0.00651, 0.00661, 0.00645, 0.00634, 0.00627, 0.00624, 0.00625, 0.00632, 0.00638, 0.00644, 0.0065, 0.00655, 0.00661, 0.00667, 0.00672, 0.00673, 0.00634, 0.00614, 0.00599, 0.00593, 0.00664, 0.00671, 0.00675, 0.00676, 0.00676, 0.00675, 0.00676, 0.00674, 0.00672, 0.00669, 0.00616, 0.00586, 0.00569, 0.00558, 0.00647, 0.00651, 0.00651, 0.00651, 0.00652, 0.00649, 0.00645, 0.0064, 0.00637, 0.00631, 0.00576, 0.00534, 0.00512, 0.00495, 0.00615, 0.0062, 0.00618, 0.00613, 0.0061, 0.00607, 0.00602, 0.00596, 0.00591, 0.00586, 0.00536, 0.00491, 0.00469, 0.0045, 0.00578, 0.00583, 0.00579, 0.00574, 0.00567, 0.00562, 0.00556, 0.00549, 0.00545, 0.00538, 0.00493, 0.00453, 0.00435, 0.0042, 0.00542, 0.00547, 0.00539, 0.00532, 0.00522, 0.00516, 0.0051, 0.00504, 0.005, 0.00495, 0.00454, 0.00418, 0.00404, 0.00394 };
LocalDate referenceDate = new LocalDate(2016, DateTimeConstants.SEPTEMBER, 30);
BusinessdayCalendarExcludingTARGETHolidays cal = new BusinessdayCalendarExcludingTARGETHolidays();
DayCountConvention_ACT_365 modelDC = new DayCountConvention_ACT_365();
for(int i=0; i<atmNormalVolatilities.length; i++ ) {
LocalDate exerciseDate = cal.createDateFromDateAndOffsetCode(referenceDate, atmExpiries[i]);
LocalDate tenorEndDate = cal.createDateFromDateAndOffsetCode(exerciseDate, atmTenors[i]);
double exercise = modelDC.getDaycountFraction(referenceDate, exerciseDate);
double tenor = modelDC.getDaycountFraction(exerciseDate, tenorEndDate);
// We consider an idealized tenor grid (alternative: adapt the model grid)
exercise = Math.round(exercise/0.25)*0.25;
tenor = Math.round(tenor/0.25)*0.25;
if(exercise < 1.0) continue;
int numberOfPeriods = (int)Math.round(tenor / swapPeriodLength);
double moneyness = 0.0;
double targetVolatility = atmNormalVolatilities[i];
String targetVolatilityType = "VOLATILITYNORMAL";
double weight = 1.0;
calibrationItems.add(createCalibrationItem(weight, exercise, swapPeriodLength, numberOfPeriods, moneyness, targetVolatility, targetVolatilityType, forwardCurve, discountCurve));
calibrationItemNames.add(atmExpiries[i]+"\t"+atmTenors[i]);
}
/*
* Create a simulation time discretization
*/
// If simulation time is below libor time, exceptions will be hard to track.
double lastTime = 40.0;
double dt = 0.25;
TimeDiscretization timeDiscretization = new TimeDiscretization(0.0, (int) (lastTime / dt), dt);
final TimeDiscretizationInterface liborPeriodDiscretization = timeDiscretization;
/*
* Create Brownian motions
*/
final BrownianMotionInterface brownianMotion = new net.finmath.montecarlo.BrownianMotion(timeDiscretization, numberOfFactors, numberOfPaths, 31415 /* seed */);
//final BrownianMotionInterface brownianMotion = new net.finmath.montecarlo.BrownianMotionCudaWithHostRandomVariable(timeDiscretization, numberOfFactors, numberOfPaths, 31415 /* seed */);
//final BrownianMotionInterface brownianMotion = new net.finmath.montecarlo.BrownianMotionCudaWithRandomVariableCuda(timeDiscretization, numberOfFactors, numberOfPaths, 31415 /* seed */);
LIBORVolatilityModel volatilityModel = new LIBORVolatilityModelPiecewiseConstant(timeDiscretization, liborPeriodDiscretization, new TimeDiscretization(0.00, 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, 40.0), new TimeDiscretization(0.00, 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, 40.0), 0.50 / 100);
LIBORCorrelationModel correlationModel = new LIBORCorrelationModelExponentialDecay(timeDiscretization, liborPeriodDiscretization, numberOfFactors, 0.05, false);
// Create a covariance model
//AbstractLIBORCovarianceModelParametric covarianceModelParametric = new LIBORCovarianceModelExponentialForm5Param(timeDiscretization, liborPeriodDiscretization, numberOfFactors, new double[] { 0.20/100.0, 0.05/100.0, 0.10, 0.05/100.0, 0.10} );
AbstractLIBORCovarianceModelParametric covarianceModelParametric = new LIBORCovarianceModelFromVolatilityAndCorrelation(timeDiscretization, liborPeriodDiscretization, volatilityModel, correlationModel);
// Create blended local volatility model with fixed parameter (0=lognormal, > 1 = almost a normal model).
AbstractLIBORCovarianceModelParametric covarianceModelDisplaced = new DisplacedLocalVolatilityModel(covarianceModelParametric, 1.0/0.25, false /* isCalibrateable */);
// Set model properties
Map<String, Object> properties = new HashMap<String, Object>();
// Choose the simulation measure
properties.put("measure", LIBORMarketModel.Measure.SPOT.name());
// Choose normal state space for the Euler scheme (the covariance model above carries a linear local volatility model, such that the resulting model is log-normal).
properties.put("stateSpace", LIBORMarketModel.StateSpace.NORMAL.name());
// Set calibration properties (should use our brownianMotion for calibration - needed to have to right correlation).
Double accuracy = new Double(5E-4); // Lower accuracy to reduce runtime of the unit test
int maxIterations = 400;
int numberOfThreads = 4;
OptimizerFactoryInterface optimizerFactory = new OptimizerFactoryLevenbergMarquardt(maxIterations, accuracy, numberOfThreads);
double[] parameterStandardDeviation = new double[covarianceModelParametric.getParameter().length];
double[] parameterLowerBound = new double[covarianceModelParametric.getParameter().length];
double[] parameterUpperBound = new double[covarianceModelParametric.getParameter().length];
Arrays.fill(parameterStandardDeviation, 0.20/100.0);
Arrays.fill(parameterLowerBound, 0.0);
Arrays.fill(parameterUpperBound, Double.POSITIVE_INFINITY);
// optimizerFactory = new OptimizerFactoryCMAES(accuracy, maxIterations, parameterLowerBound, parameterUpperBound, parameterStandardDeviation);
// Set calibration properties (should use our brownianMotion for calibration - needed to have to right correlation).
Map<String, Object> calibrationParameters = new HashMap<String, Object>();
calibrationParameters.put("accuracy", accuracy);
calibrationParameters.put("brownianMotion", brownianMotion);
calibrationParameters.put("optimizerFactory", optimizerFactory);
calibrationParameters.put("parameterStep", new Double(1E-4));
properties.put("calibrationParameters", calibrationParameters);
long millisCalibrationStart = System.currentTimeMillis();
/*
* Create corresponding LIBOR Market Model
*/
LIBORMarketModel.CalibrationItem[] calibrationItemsLMM = new LIBORMarketModel.CalibrationItem[calibrationItemNames.size()];
for(int i=0; i<calibrationItemNames.size(); i++) calibrationItemsLMM[i] = new LIBORMarketModel.CalibrationItem(calibrationItems.get(i).calibrationProduct,calibrationItems.get(i).calibrationTargetValue,calibrationItems.get(i).calibrationWeight);
LIBORModelInterface liborMarketModelCalibrated = new LIBORMarketModel(
liborPeriodDiscretization,
curveModel,
forwardCurve, new DiscountCurveFromForwardCurve(forwardCurve),
covarianceModelDisplaced,
calibrationItemsLMM,
properties);
long millisCalibrationEnd = System.currentTimeMillis();
System.out.println("\nCalibrated parameters are:");
double[] param = ((AbstractLIBORCovarianceModelParametric)((LIBORMarketModel) liborMarketModelCalibrated).getCovarianceModel()).getParameter();
for (double p : param) System.out.println(p);
ProcessEulerScheme process = new ProcessEulerScheme(brownianMotion);
LIBORModelMonteCarloSimulationInterface simulationCalibrated = new LIBORModelMonteCarloSimulation(liborMarketModelCalibrated, process);
System.out.println("\nValuation on calibrated model:");
double deviationSum = 0.0;
double deviationSquaredSum = 0.0;
for (int i = 0; i < calibrationItems.size(); i++) {
AbstractLIBORMonteCarloProduct calibrationProduct = calibrationItems.get(i).calibrationProduct;
try {
double valueModel = calibrationProduct.getValue(simulationCalibrated);
double valueTarget = calibrationItems.get(i).calibrationTargetValue;
double error = valueModel-valueTarget;
deviationSum += error;
deviationSquaredSum += error*error;
System.out.println(calibrationItemNames.get(i) + "\t" + "Model: " + formatterValue.format(valueModel) + "\t Target: " + formatterValue.format(valueTarget) + "\t Deviation: " + formatterDeviation.format(valueModel-valueTarget));// + "\t" + calibrationProduct.toString());
}
catch(Exception e) {
}
}
System.out.println("Calibration of curves........." + (millisCurvesEnd-millisCurvesStart)/1000.0);
System.out.println("Calibration of volatilities..." + (millisCalibrationEnd-millisCalibrationStart)/1000.0);
double averageDeviation = deviationSum/calibrationItems.size();
System.out.println("Mean Deviation:" + formatterValue.format(averageDeviation));
System.out.println("RMS Error.....:" + formatterValue.format(Math.sqrt(deviationSquaredSum/calibrationItems.size())));
System.out.println("__________________________________________________________________________________________\n");
Assert.assertTrue(Math.abs(averageDeviation) < 1E-2);
}
public AnalyticModelInterface getCalibratedCurve() throws SolverException {
final String[] maturity = { "6M", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "11Y", "12Y", "15Y", "20Y", "25Y", "30Y", "35Y", "40Y", "45Y", "50Y" };
final String[] frequency = { "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual" };
final String[] frequencyFloat = { "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual" };
final String[] daycountConventions = { "ACT/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360" };
final String[] daycountConventionsFloat = { "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360" };
final double[] rates = { -0.00216 ,-0.00208 ,-0.00222 ,-0.00216 ,-0.0019 ,-0.0014 ,-0.00072 ,0.00011 ,0.00103 ,0.00196 ,0.00285 ,0.00367 ,0.0044 ,0.00604 ,0.00733 ,0.00767 ,0.00773 ,0.00765 ,0.00752 ,0.007138 ,0.007 };
HashMap<String, Object> parameters = new HashMap<String, Object>();
parameters.put("referenceDate", new LocalDate(2016, DateTimeConstants.SEPTEMBER, 30));
parameters.put("currency", "EUR");
parameters.put("forwardCurveTenor", "6M");
parameters.put("maturities", maturity);
parameters.put("fixLegFrequencies", frequency);
parameters.put("floatLegFrequencies", frequencyFloat);
parameters.put("fixLegDaycountConventions", daycountConventions);
parameters.put("floatLegDaycountConventions", daycountConventionsFloat);
parameters.put("rates", rates);
return getCalibratedCurve(null, parameters);
}
private static AnalyticModelInterface getCalibratedCurve(AnalyticModelInterface model2, Map<String, Object> parameters) throws SolverException {
final LocalDate referenceDate = (LocalDate) parameters.get("referenceDate");
final String currency = (String) parameters.get("currency");
final String forwardCurveTenor = (String) parameters.get("forwardCurveTenor");
final String[] maturities = (String[]) parameters.get("maturities");
final String[] frequency = (String[]) parameters.get("fixLegFrequencies");
final String[] frequencyFloat = (String[]) parameters.get("floatLegFrequencies");
final String[] daycountConventions = (String[]) parameters.get("fixLegDaycountConventions");
final String[] daycountConventionsFloat = (String[]) parameters.get("floatLegDaycountConventions");
final double[] rates = (double[]) parameters.get("rates");
Assert.assertEquals(maturities.length, frequency.length);
Assert.assertEquals(maturities.length, daycountConventions.length);
Assert.assertEquals(maturities.length, rates.length);
Assert.assertEquals(frequency.length, frequencyFloat.length);
Assert.assertEquals(daycountConventions.length, daycountConventionsFloat.length);
int spotOffsetDays = 2;
String forwardStartPeriod = "0D";
String curveNameDiscount = "discountCurve-" + currency;
/*
* We create a forward curve by referencing the same discount curve, since
* this is a single curve setup.
*
* Note that using an independent NSS forward curve with its own NSS parameters
* would result in a problem where both, the forward curve and the discount curve
* have free parameters.
*/
ForwardCurveInterface forwardCurve = new ForwardCurveFromDiscountCurve(curveNameDiscount, referenceDate, forwardCurveTenor);
// Create a collection of objective functions (calibration products)
Vector<AnalyticProductInterface> calibrationProducts = new Vector<AnalyticProductInterface>();
double[] curveMaturities = new double[rates.length+1];
double[] curveValue = new double[rates.length+1];
boolean[] curveIsParameter = new boolean[rates.length+1];
curveMaturities[0] = 0.0;
curveValue[0] = 1.0;
curveIsParameter[0] = false;
for(int i=0; i<rates.length; i++) {
ScheduleInterface schedulePay = ScheduleGenerator.createScheduleFromConventions(referenceDate, spotOffsetDays, forwardStartPeriod, maturities[i], frequency[i], daycountConventions[i], "first", "following", new BusinessdayCalendarExcludingTARGETHolidays(), -2, 0);
ScheduleInterface scheduleRec = ScheduleGenerator.createScheduleFromConventions(referenceDate, spotOffsetDays, forwardStartPeriod, maturities[i], frequencyFloat[i], daycountConventionsFloat[i], "first", "following", new BusinessdayCalendarExcludingTARGETHolidays(), -2, 0);
curveMaturities[i+1] = Math.max(schedulePay.getPayment(schedulePay.getNumberOfPeriods()-1),scheduleRec.getPayment(scheduleRec.getNumberOfPeriods()-1));
curveValue[i+1] = 1.0;
curveIsParameter[i+1] = true;
calibrationProducts.add(new Swap(schedulePay, null, rates[i], curveNameDiscount, scheduleRec, forwardCurve.getName(), 0.0, curveNameDiscount));
}
InterpolationMethod interpolationMethod = InterpolationMethod.LINEAR;
// Create a discount curve
DiscountCurve discountCurve = DiscountCurve.createDiscountCurveFromDiscountFactors(
curveNameDiscount /* name */,
curveMaturities /* maturities */,
curveValue /* discount factors */,
curveIsParameter,
interpolationMethod ,
ExtrapolationMethod.CONSTANT,
InterpolationEntity.LOG_OF_VALUE
);
/*
* Model consists of the two curves, but only one of them provides free parameters.
*/
AnalyticModelInterface model = new AnalyticModel(new CurveInterface[] { discountCurve, forwardCurve });
/*
* Create a collection of curves to calibrate
*/
Set<ParameterObjectInterface> curvesToCalibrate = new HashSet<ParameterObjectInterface>();
curvesToCalibrate.add(discountCurve);
/*
* Calibrate the curve
*/
Solver solver = new Solver(model, calibrationProducts);
AnalyticModelInterface calibratedModel = solver.getCalibratedModel(curvesToCalibrate);
System.out.println("Solver reported acccurary....: " + solver.getAccuracy());
Assert.assertEquals("Calibration accurarcy", 0.0, solver.getAccuracy(), 1E-3);
// Get best parameters
double[] parametersBest = calibratedModel.getDiscountCurve(discountCurve.getName()).getParameter();
// Test calibration
model = calibratedModel;
double squaredErrorSum = 0.0;
for(AnalyticProductInterface c : calibrationProducts) {
double value = c.getValue(0.0, model);
double valueTaget = 0.0;
double error = value - valueTaget;
squaredErrorSum += error*error;
}
double rms = Math.sqrt(squaredErrorSum/calibrationProducts.size());
System.out.println("Independent checked acccurary: " + rms);
System.out.println("Calibrated discount curve: ");
for(int i=0; i<curveMaturities.length; i++) {
double maturity = curveMaturities[i];
System.out.println(maturity + "\t" + calibratedModel.getDiscountCurve(discountCurve.getName()).getDiscountFactor(maturity));
}
return model;
}
private static double getParSwaprate(ForwardCurveInterface forwardCurve, DiscountCurveInterface discountCurve, double[] swapTenor) throws CalculationException {
return net.finmath.marketdata.products.Swap.getForwardSwapRate(new TimeDiscretization(swapTenor), new TimeDiscretization(swapTenor), forwardCurve, discountCurve);
}
}