All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.finmath.singleswaprate.calibration.AbstractCubeCalibration Maven / Gradle / Ivy

Go to download

finmath lib is a Mathematical Finance Library in Java. It provides algorithms and methodologies related to mathematical finance.

The newest version!
package net.finmath.singleswaprate.calibration;

import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import net.finmath.marketdata.model.volatilities.SwaptionDataLattice;
import net.finmath.marketdata.model.volatilities.SwaptionDataLattice.QuotingConvention;
import net.finmath.marketdata.products.Swap;
import net.finmath.optimizer.LevenbergMarquardt;
import net.finmath.optimizer.SolverException;
import net.finmath.singleswaprate.annuitymapping.AnnuityMapping;
import net.finmath.singleswaprate.annuitymapping.AnnuityMapping.AnnuityMappingType;
import net.finmath.singleswaprate.annuitymapping.AnnuityMappingFactory;
import net.finmath.singleswaprate.data.DataTable.TableConvention;
import net.finmath.singleswaprate.model.VolatilityCubeModel;
import net.finmath.singleswaprate.model.volatilities.VolatilityCube;
import net.finmath.singleswaprate.products.CashSettledPayerSwaption;
import net.finmath.singleswaprate.products.CashSettledReceiverSwaption;
import net.finmath.time.Schedule;
import net.finmath.time.SchedulePrototype;

/**
 * Abstract class providing a default method of calibrating a parametric cube to market data, which can be implemented quickly for any cube by implementing the methods:
 * 
    *
  • buildCube
  • *
  • initializeParameters
  • *
  • applyParameterBounds
  • *
* * @author Christian Fries * @author Roland Bachl * */ public abstract class AbstractCubeCalibration { private final LocalDate referenceDate; private final SwaptionDataLattice cashPayerPremiums; private final SwaptionDataLattice cashReceiverPremiums; private final String discountCurveName; private final String forwardCurveName; private final VolatilityCubeModel model; private final AnnuityMappingType annuityMappingType; private int maxIterations = 250; private int numberOfThreads = Runtime.getRuntime().availableProcessors(); private boolean replicationUseAsOffset = true; private double replicationLowerBound = -0.15; private double replicationUpperBound = 0.15; private int replicationNumberOfEvaluationPoints = 500; //calculation private double[] initialParameters; private double[] calibratedParameters; private double[] marketTargets; private final ArrayList payerSwaptions = new ArrayList<>(); private final ArrayList receiverSwaptions = new ArrayList<>(); /** * Create the calibrator. * * @param referenceDate The reference date of the cube. * @param cashPayerPremiums The lattice containing market targets for cash settled payer swaptions. The lattice needs to be quoted in QuotingConvention.PRICE. * @param cashReceiverPremiums The lattice containing market targets for cash settled receiver swaptions. The lattice needs to be quoted in QuotingConvention.PRICE. * @param model The model providing context. * @param annuityMappingType The type of annuity mapping to be used for calibration. * * @throws IllegalArgumentException Triggers when data is not provided in QuotingConvention.Price. */ public AbstractCubeCalibration(final LocalDate referenceDate, final SwaptionDataLattice cashPayerPremiums, final SwaptionDataLattice cashReceiverPremiums, final VolatilityCubeModel model, final AnnuityMappingType annuityMappingType) { super(); if(cashPayerPremiums.getQuotingConvention() != QuotingConvention.PAYERPRICE || cashReceiverPremiums.getQuotingConvention() != QuotingConvention.RECEIVERPRICE) { throw new IllegalArgumentException("Swaption data not provided in QuotingConvention.PAYERPRICE and QuotingConvention.RECEIVERPRICE respectively."); } this.referenceDate = referenceDate; this.cashPayerPremiums = cashPayerPremiums; this.cashReceiverPremiums = cashReceiverPremiums; this.model = model; this.annuityMappingType = annuityMappingType; discountCurveName = cashPayerPremiums.getDiscountCurveName(); forwardCurveName = cashPayerPremiums.getForwardCurveName(); } // adjust these methods to adapt to different cubes /** * Build the cube from a set of parameters. These need to be an array of all parameters to be calibrated. * * @param name The name the cube will carry. * @param parameters The parameters of the cube as array. * @return The volatility cube. */ protected abstract VolatilityCube buildCube(String name, double[] parameters); /** * Prepare the parameters for the start of the calibration. */ protected abstract void initializeParameters(); /** * Apply bounds to parameters. Such as volatility larger zero. * * @param parameters The raw parameters of the cube as array. * @return The parameters with their respective bounds applied. */ protected abstract double[] applyParameterBounds(double[] parameters); //following methods apply to all cubes /** * Run the calibration. * * @param cubeName The name of the final cube. * @return The calibrated cube. * * @throws SolverException Thrown, when solvers fail to find suitable parameters. */ public VolatilityCube calibrate(final String cubeName) throws SolverException { // sort target data for calibration generateTargets(); //setup initial parameters initializeParameters(); runOptimization(); return buildCube(cubeName, calibratedParameters); } /** * Run the calibration. * * @throws SolverException Thrown, when solvers fail to find suitable parameters. */ private void runOptimization() throws SolverException { final LevenbergMarquardt optimizer = new LevenbergMarquardt(getInitialParameters(), marketTargets, maxIterations, numberOfThreads) { /** * */ private static final long serialVersionUID = -7604474677930060206L; @Override public void setValues(double[] parameters, final double[] values) { //apply bounds to the parameters parameters = applyParameterBounds(parameters); //get volatility cube and add to temporary model final String tempCubeName = "tempCube"; final VolatilityCube cube = buildCube(tempCubeName, parameters); final VolatilityCubeModel tempModel = getModel().addVolatilityCube(cube); //pre allocate space Schedule floatSchedule; Schedule fixSchedule; double forwardSwapRate; double strike; String mappingName; AnnuityMapping mapping; AnnuityMappingFactory factory; final Map container = new HashMap<>(); int index = 0; //calculate cash payer swaption values SchedulePrototype fixMetaSchedule = cashPayerPremiums.getFixMetaSchedule(); SchedulePrototype floatMetaSchedule = cashPayerPremiums.getFloatMetaSchedule(); for(final SwaptionInfo swaption : payerSwaptions) { fixSchedule = fixMetaSchedule.generateSchedule(getReferenceDate(), swaption.maturity, swaption.termination); floatSchedule = floatMetaSchedule.generateSchedule(getReferenceDate(), swaption.maturity, swaption.termination); forwardSwapRate = Swap.getForwardSwapRate(fixSchedule, floatSchedule, tempModel.getForwardCurve(getForwardCurveName()), tempModel); strike = forwardSwapRate + swaption.moneyness; final double replicationLowerBound = replicationUseAsOffset ? forwardSwapRate + AbstractCubeCalibration.this.replicationLowerBound : AbstractCubeCalibration.this.replicationLowerBound; final double replicationUpperBound = replicationUseAsOffset ? forwardSwapRate + AbstractCubeCalibration.this.replicationUpperBound : AbstractCubeCalibration.this.replicationUpperBound; // see if appropriate mapping already exists, otherwise generate mappingName = swaption.toString(); if(container.containsKey(mappingName)) { mapping = container.get(mappingName); } else { factory = new AnnuityMappingFactory(fixSchedule, floatSchedule, discountCurveName, getForwardCurveName(), tempCubeName, strike, replicationLowerBound, replicationUpperBound, replicationNumberOfEvaluationPoints); mapping = factory.build(annuityMappingType, tempModel); container.put(mappingName, mapping); } final CashSettledPayerSwaption css = new CashSettledPayerSwaption(fixSchedule, floatSchedule, strike, discountCurveName, getForwardCurveName(), tempCubeName, annuityMappingType, replicationLowerBound, replicationUpperBound, replicationNumberOfEvaluationPoints); values[index++] = css.getValue(floatSchedule.getFixing(0), mapping, tempModel); } //calculate cash receiver swaption values fixMetaSchedule = cashReceiverPremiums.getFixMetaSchedule(); floatMetaSchedule = cashReceiverPremiums.getFloatMetaSchedule(); for(final SwaptionInfo swaption : receiverSwaptions) { fixSchedule = fixMetaSchedule.generateSchedule(getReferenceDate(), swaption.maturity, swaption.termination); floatSchedule = floatMetaSchedule.generateSchedule(getReferenceDate(), swaption.maturity, swaption.termination); forwardSwapRate = Swap.getForwardSwapRate(fixSchedule, floatSchedule, tempModel.getForwardCurve(getForwardCurveName()), tempModel); strike = forwardSwapRate + swaption.moneyness; final double replicationLowerBound = replicationUseAsOffset ? forwardSwapRate + AbstractCubeCalibration.this.replicationLowerBound : AbstractCubeCalibration.this.replicationLowerBound; final double replicationUpperBound = replicationUseAsOffset ? forwardSwapRate + AbstractCubeCalibration.this.replicationUpperBound : AbstractCubeCalibration.this.replicationUpperBound; // see if appropriate mapping already exists, otherwise generate mappingName = swaption.toString(); if(container.containsKey(mappingName)) { mapping = container.get(mappingName); } else { factory = new AnnuityMappingFactory(fixSchedule, floatSchedule, discountCurveName, getForwardCurveName(), tempCubeName, strike, replicationLowerBound, replicationUpperBound, replicationNumberOfEvaluationPoints); mapping = factory.build(annuityMappingType, tempModel); container.put(mappingName, mapping); } final CashSettledReceiverSwaption css = new CashSettledReceiverSwaption(fixSchedule, floatSchedule, strike, discountCurveName, getForwardCurveName(), tempCubeName, annuityMappingType, replicationLowerBound, replicationUpperBound, replicationNumberOfEvaluationPoints); values[index++] = css.getValue(floatSchedule.getFixing(0), mapping, tempModel); } // output for testing // StringBuilder builder = new StringBuilder(); // builder.append("Parameters:\t"); // for(double parameter : parameters) // builder.append(parameter +"\t"); // builder.append("\nMeanSquaredError:\t" + getMeanSquaredError(values) + "\nValues:\t"); // for(double value : values) // builder.append(value +"\t"); // builder.append("\nTargets:\t"); // for(double target : marketTargets) // builder.append(target + "\t"); //// builder.append("\nSwaptions:\t"); //// for(SwaptionInfo swaption : payerSwaptions) //// builder.append("payer/"+swaption.toString().replaceAll(", ", "/")+"\t"); //// for(SwaptionInfo swaption : receiverSwaptions) //// builder.append("receiver/"+swaption.toString().replaceAll(", ", "/")+"\t"); // builder.append('\n'); // String string = builder.toString(); // // synchronized (System.out) { // System.out.println(string); // } } }; optimizer.run(); System.out.println("Optimizer finished after " +optimizer.getIterations() +" iterations with mean error " + optimizer.getRootMeanSquaredError()); calibratedParameters = applyParameterBounds(optimizer.getBestFitParameters()); } /** * Build the target array. * */ private void generateTargets() { //convert data tables to swaptions and target array //order: cashPayer(strike maturity termination) cashReceiver(strike maturity termination) //prep temp variables final ArrayList targetsPayer = new ArrayList<>(); final ArrayList targetsReceiver = new ArrayList<>(); //sort all data into array lists for(final int moneyness : cashPayerPremiums.getMoneyness()) { for(final int maturity : cashPayerPremiums.getMaturities(moneyness)) { for(final int termination : cashPayerPremiums.getTenors(moneyness, maturity)) { targetsPayer.add(cashPayerPremiums.getValue(maturity, termination, moneyness)); payerSwaptions.add( new SwaptionInfo(moneyness, maturity, termination)); } } } for(final int moneyness : cashReceiverPremiums.getMoneyness()) { for(final int maturity : cashReceiverPremiums.getMaturities(moneyness)) { for(final int termination : cashReceiverPremiums.getTenors(moneyness, maturity)) { targetsReceiver.add(cashReceiverPremiums.getValue(maturity, termination, moneyness)); receiverSwaptions.add( new SwaptionInfo(-moneyness, maturity, termination)); } } } this.marketTargets = Stream.concat(targetsPayer.stream(), targetsReceiver.stream()).mapToDouble(Double::doubleValue).toArray(); } /** * Set the parameters for calibration. * * @param maxIterations The maximum number of iterations done during calibration. * @param numberOfThreads The number of processor threads to be used. */ public void setCalibrationParameters( final int maxIterations, final int numberOfThreads) { this.maxIterations = maxIterations; this.numberOfThreads = numberOfThreads; } /** * @return The maximum number of iterations of the optimizer. */ public int getMaxIterations() { return maxIterations; } /** * @return The number of threads the optimizer is allowed to use. */ public int getNumberOfThreads() { return numberOfThreads; } /** * Set the parameters for the swaption replication. * * @param useAsOffset Are the values of lower and upperBound to be understood as offsets from the par swap rate? * @param lowerBound The lowest strike allowed. * @param upperBound The maximal strike allowed. * @param numberOfEvaluationPoints The number of points to be evaluated during the integration. */ public void setReplicationParameters(final boolean useAsOffset, final double lowerBound, final double upperBound, final int numberOfEvaluationPoints) { this.replicationUseAsOffset = useAsOffset; this.replicationLowerBound = lowerBound; this.replicationUpperBound = upperBound; this.replicationNumberOfEvaluationPoints = numberOfEvaluationPoints; } /** * @return True when the replication bounds are to be understood as offset from the par swap rate. */ public boolean isReplicationUseAsOffset() { return replicationUseAsOffset; } /** * @return The lowest strike allowed during swaption replication. */ public double getReplicationLowerBound() { return replicationLowerBound; } /** * @return The maximal strike allowed during swaption replication. */ public double getReplicationUpperBound() { return replicationUpperBound; } /** * @return The number of points to be evaluated during swaption replication. */ public int getReplicationNumberOfEvaluationPoints() { return replicationNumberOfEvaluationPoints; } /** * @return the referenceDate */ public LocalDate getReferenceDate() { return referenceDate; } /** * @return the model */ public VolatilityCubeModel getModel() { return model; } /** * @return the forwardCurveName */ public String getForwardCurveName() { return forwardCurveName; } /** * @return the initialParameters */ public double[] getInitialParameters() { return initialParameters; } /** * @param initialParameters the initialParameters to set */ public void setInitialParameters(final double[] initialParameters) { this.initialParameters = initialParameters; } /** * Compact identifier for the swaptions to be created during the optimization. * * @author Christian Fries * @author Roland Bachl * */ protected class SwaptionInfo { private final double moneyness; private final LocalDate maturity; private final LocalDate termination; /** * Create the swaption with convention inMONTHS. * * @param moneyness * @param maturity * @param termination */ SwaptionInfo(final int moneyness, final int maturity, final int termination) { this.moneyness = moneyness / 10000.0; this.maturity = getReferenceDate().plusMonths(maturity); this.termination = this.maturity.plusMonths(termination); } /** * Create the swaption. * * @param moneyness * @param maturity * @param termination * @param tableConvention * * @throws IOException Triggers when the table convention is not recognized. */ SwaptionInfo(final int moneyness, final int maturity, final int termination, final TableConvention tableConvention) throws IOException { this.moneyness = moneyness /10000.0; switch(tableConvention) { case MONTHS : this.maturity = getReferenceDate().plusMonths(maturity); this.termination = this.maturity.plusMonths(termination); break; case YEARS : this.maturity = getReferenceDate().plusYears(maturity); this.termination = this.maturity.plusYears(termination); break; case DAYS : this.maturity = getReferenceDate().plusDays(maturity); this.termination = this.maturity.plusDays(termination); break; case WEEKS : this.maturity = getReferenceDate().plusDays(maturity * 7); this.termination = this.maturity.plusDays(termination * 7); break; default : throw new IOException("TableConvention "+tableConvention+" not recognized."); } } @Override public String toString() { return moneyness +"/"+maturity+"/"+termination; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy