net.finmath.montecarlo.interestrate.models.LIBORMarketModelStandard Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of finmath-lib Show documentation
Show all versions of finmath-lib Show documentation
finmath lib is a Mathematical Finance Library in Java.
It provides algorithms and methodologies related to mathematical finance.
/* * (c) Copyright Christian P. Fries, Germany. Contact: [email protected]. * * Created on 09.02.2004 */ package net.finmath.montecarlo.interestrate.models; import java.util.ArrayList; import java.util.Map; import net.finmath.exception.CalculationException; import net.finmath.functions.AnalyticFormulas; import net.finmath.marketdata.model.AnalyticModel; import net.finmath.marketdata.model.curves.DiscountCurve; import net.finmath.marketdata.model.curves.DiscountCurveFromForwardCurve; import net.finmath.marketdata.model.curves.ForwardCurve; import net.finmath.marketdata.model.volatilities.SwaptionMarketData; import net.finmath.marketdata.products.Swap; import net.finmath.marketdata.products.SwapAnnuity; import net.finmath.montecarlo.RandomVariableFactory; import net.finmath.montecarlo.RandomVariableFromArrayFactory; import net.finmath.montecarlo.RandomVariableFromDoubleArray; import net.finmath.montecarlo.interestrate.CalibrationProduct; import net.finmath.montecarlo.interestrate.LIBORMarketModel; import net.finmath.montecarlo.interestrate.models.covariance.AbstractLIBORCovarianceModelParametric; import net.finmath.montecarlo.interestrate.models.covariance.LIBORCovarianceModel; import net.finmath.montecarlo.interestrate.products.AbstractTermStructureMonteCarloProduct; import net.finmath.montecarlo.interestrate.products.SwaptionAnalyticApproximation; import net.finmath.montecarlo.interestrate.products.SwaptionSimple; import net.finmath.montecarlo.model.AbstractProcessModel; import net.finmath.montecarlo.process.MonteCarloProcess; import net.finmath.stochastic.RandomVariable; import net.finmath.time.RegularSchedule; import net.finmath.time.Schedule; import net.finmath.time.TimeDiscretization; import net.finmath.time.TimeDiscretizationFromArray; /** * Implements a basic LIBOR market model with some drift approximation methods. *
. * * @param process The discretization process generating this model. The process provides call backs for TimeDiscretization and allows calls to getProcessValue for timeIndices less or equal the given one. * @param timeIndex Time index i for which the drift should be returned μ(ti). * @param realizationAtTimeIndex Time current forward rate vector at time index i which should be used in the calculation. * @return The drift vector μ(ti) as
* The class implements different measure(drift) / numeraire pairs (terminal and spot). *
* The class specifies a LIBOR market model in its log-normal formulation, that is * Lj = exp(Yj) where *
* dYj = μj dt + λ1,j dW1 + ... + λm,j dWm *
* see {@link net.finmath.montecarlo.model.ProcessModel} for details on the implemented interface. *
* The model uses anAbstractLIBORCovarianceModel
for the specification of (λ1,j,...,λm,j) as a covariance model, * which may have the ability to calibrate to swaptions. * * @see net.finmath.montecarlo.interestrate.models.covariance.AbstractLIBORCovarianceModel * * @author Christian Fries * @version 1.1 */ public class LIBORMarketModelStandard extends AbstractProcessModel implements LIBORMarketModel { private static final boolean isUseAnalyticApproximation; static { // Default value is true; isUseAnalyticApproximation = Boolean.parseBoolean(System.getProperty("net.finmath.montecarlo.interestrate.LIBORMarketModelStandard.isUseAnalyticApproximation","true")); } public enum Driftapproximation { EULER, LINE_INTEGRAL, PREDICTOR_CORRECTOR } public enum Measure { SPOT, TERMINAL } private final TimeDiscretization liborPeriodDiscretization; private String forwardCurveName; private AnalyticModel curveModel; private final ForwardCurve forwardRateCurve; private DiscountCurve discountCurve; private final RandomVariableFactory randomVariableFactory = new RandomVariableFromArrayFactory(); private LIBORCovarianceModel covarianceModel; private SwaptionMarketData swaptionMarketData; private Driftapproximation driftApproximationMethod = Driftapproximation.EULER; private Measure measure = Measure.SPOT; // This is a cache of the integrated covariance. private double[][][] integratedLIBORCovariance; /** * Creates a LIBOR Market Model for given covariance. * * @param liborPeriodDiscretization The discretization of the interest rate curve into forward rates (tenor structure). * @param forwardRateCurve The initial values for the forward rates. * @param covarianceModel The covariance model to use. */ public LIBORMarketModelStandard( final TimeDiscretization liborPeriodDiscretization, final ForwardCurve forwardRateCurve, final LIBORCovarianceModel covarianceModel ) { this.liborPeriodDiscretization = liborPeriodDiscretization; this.forwardRateCurve = forwardRateCurve; this.covarianceModel = covarianceModel; } /** * Creates a LIBOR Market Model for given covariance. * * @param liborPeriodDiscretization The discretization of the interest rate curve into forward rates (tenor structure). * @param forwardRateCurve The initial values for the forward rates. * @param discountCurve The discount curve to use. This will create an LMM model with a deterministic zero-spread discounting adjustment. * @param covarianceModel The covariance model to use. */ public LIBORMarketModelStandard( final TimeDiscretization liborPeriodDiscretization, final ForwardCurve forwardRateCurve, final DiscountCurve discountCurve, final LIBORCovarianceModel covarianceModel ) { this.liborPeriodDiscretization = liborPeriodDiscretization; this.forwardRateCurve = forwardRateCurve; this.discountCurve = discountCurve; this.covarianceModel = covarianceModel; } /** * Creates a LIBOR Market Model using a given covariance model and calibrating this model * to given swaption volatility data. * * @param liborPeriodDiscretization The discretization of the interest rate curve into forward rates (tenor structure). * @param forwardRateCurve The initial values for the forward rates. * @param covarianceModel The covariance model to use. * @param swaptionMarketData The set of swaption values to calibrate to. * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via thecause()
method. */ public LIBORMarketModelStandard( final TimeDiscretization liborPeriodDiscretization, final ForwardCurve forwardRateCurve, final LIBORCovarianceModel covarianceModel, final SwaptionMarketData swaptionMarketData ) throws CalculationException { this(liborPeriodDiscretization, forwardRateCurve, null, covarianceModel, getCalibrationItems(liborPeriodDiscretization, forwardRateCurve, swaptionMarketData)); } /** * Creates a LIBOR Market Model for given covariance. * * @param liborPeriodDiscretization The discretization of the interest rate curve into forward rates (tenor structure). * @param forwardRateCurve The initial values for the forward rates. * @param discountCurve The discount curve to use. This will create an LMM model with a deterministic zero-spread discounting adjustment. * @param covarianceModel The covariance model to use. * @param swaptionMarketData The set of swaption values to calibrate to. * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via thecause()
method. */ public LIBORMarketModelStandard( final TimeDiscretization liborPeriodDiscretization, final ForwardCurve forwardRateCurve, final DiscountCurve discountCurve, final LIBORCovarianceModel covarianceModel, final SwaptionMarketData swaptionMarketData ) throws CalculationException { this(liborPeriodDiscretization, forwardRateCurve, discountCurve, covarianceModel, getCalibrationItems(liborPeriodDiscretization, forwardRateCurve, swaptionMarketData)); } /** * Creates a LIBOR Market Model for given covariance. * * @param liborPeriodDiscretization The discretization of the interest rate curve into forward rates (tenor structure). * @param forwardRateCurve The initial values for the forward rates. * @param discountCurve The discount curve to use. This will create an LMM model with a deterministic zero-spread discounting adjustment. * @param covarianceModel The covariance model to use. * @param calibrationProducts The vector of calibration items (a union of a product, target value and weight) for the objective function sum weight(i) * (modelValue(i)-targetValue(i). * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via thecause()
method. */ public LIBORMarketModelStandard( final TimeDiscretization liborPeriodDiscretization, final ForwardCurve forwardRateCurve, final DiscountCurve discountCurve, final LIBORCovarianceModel covarianceModel, final CalibrationProduct[] calibrationProducts ) throws CalculationException { this.liborPeriodDiscretization = liborPeriodDiscretization; final double[] times = new double[liborPeriodDiscretization.getNumberOfTimeSteps()]; for(int i=0; icalibrationProducts = new ArrayList<>(); for(int exerciseIndex=0; exerciseIndex<=optionMaturities.getNumberOfTimeSteps(); exerciseIndex++) { for(int tenorIndex=0; tenorIndex<=tenor.getNumberOfTimeSteps()-exerciseIndex; tenorIndex++) { // Create a swaption final double exerciseDate = optionMaturities.getTime(exerciseIndex); final double swapLength = tenor.getTime(tenorIndex); if(liborPeriodDiscretization.getTimeIndex(exerciseDate) < 0) { continue; } if(liborPeriodDiscretization.getTimeIndex(exerciseDate+swapLength) <= liborPeriodDiscretization.getTimeIndex(exerciseDate)) { continue; } final int numberOfPeriods = (int)(swapLength / swapPeriodLength); final double[] fixingDates = new double[numberOfPeriods]; final double[] paymentDates = new double[numberOfPeriods]; final double[] swapTenorTimes = new double[numberOfPeriods+1]; for(int periodStartIndex=0; periodStartIndex t for which the numeraire should be returned N(t). * @return The numeraire at the specified time as RandomVariableFromDoubleArray
* @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via thecause()
method. */ @Override public RandomVariable getNumeraire(final MonteCarloProcess process, double time) throws CalculationException { final int timeIndex = getLiborPeriodIndex(time); if(timeIndex < 0) { // Interpolation of Numeraire: linear interpolation of the reciprocal. final int lowerIndex = -timeIndex -1; final int upperIndex = -timeIndex; final double alpha = (time-getLiborPeriod(lowerIndex)) / (getLiborPeriod(upperIndex) - getLiborPeriod(lowerIndex)); return getNumeraire(process, getLiborPeriod(upperIndex)).invert().mult(alpha).add(getNumeraire(process, getLiborPeriod(lowerIndex)).invert().mult(1.0-alpha)).invert(); } // Calculate the numeraire, when time is part of liborPeriodDiscretization // Get the start of the product int firstLiborIndex = getLiborPeriodIndex(time); if(firstLiborIndex < 0) { throw new CalculationException("Simulation time discretization not part of forward rate tenor discretization."); } // Get the end of the product int lastLiborIndex = liborPeriodDiscretization.getNumberOfTimeSteps()-1; if(measure == Measure.SPOT) { // Spot measure firstLiborIndex = 0; lastLiborIndex = getLiborPeriodIndex(time)-1; } /* * Calculation of the numeraire */ // Initialize to 1.0 RandomVariable numeraire = new RandomVariableFromDoubleArray(time, 1.0); // The product for(int liborIndex = firstLiborIndex; liborIndex<=lastLiborIndex; liborIndex++) { final RandomVariable libor = getLIBOR(process, process.getTimeIndex(Math.min(time,liborPeriodDiscretization.getTime(liborIndex))), liborIndex); final double periodLength = liborPeriodDiscretization.getTimeStep(liborIndex); if(measure == Measure.SPOT) { numeraire = numeraire.accrue(libor, periodLength); } else { numeraire = numeraire.discount(libor, periodLength); } } /* * Adjust for discounting */ if(discountCurve != null) { final DiscountCurve discountcountCurveFromForwardPerformance = new DiscountCurveFromForwardCurve(forwardRateCurve); final double deterministicNumeraireAdjustment = discountcountCurveFromForwardPerformance.getDiscountFactor(time) / discountCurve.getDiscountFactor(time); numeraire = numeraire.mult(deterministicNumeraireAdjustment); } return numeraire; } @Override public RandomVariable[] getInitialState(MonteCarloProcess process) { final double[] liborInitialStates = new double[liborPeriodDiscretization.getNumberOfTimeSteps()]; for(int timeIndex=0; timeIndexgetNumeraire RandomVariableFromDoubleArray[]
*/ @Override public RandomVariable[] getDrift(final MonteCarloProcess process, final int timeIndex, final RandomVariable[] realizationAtTimeIndex, final RandomVariable[] realizationPredictor) { final double time = process.getTime(timeIndex); int firstLiborIndex = this.getLiborPeriodIndex(time)+1; if(firstLiborIndex<0) { firstLiborIndex = -firstLiborIndex-1 + 1; } // Allocate drift vector and initialize to zero (will be used to sum up drift components) final RandomVariable[] drift = new RandomVariable[getNumberOfComponents()]; final RandomVariable[][] covarianceFactorSums = new RandomVariable[getNumberOfComponents()][getNumberOfFactors()]; for(int componentIndex=firstLiborIndex; componentIndexfirstLiborIndex) { covarianceFactorSums[componentIndex][factorIndex] = covarianceFactorSums[componentIndex][factorIndex].add(covarianceFactorSums[componentIndex-1][factorIndex]); } } for(int factorIndex=0; factorIndex = liborPeriodDiscretization.getNumberOfTimes()) { throw new ArrayIndexOutOfBoundsException("Index for LIBOR period discretization out of bounds."); } return liborPeriodDiscretization.getTime(timeIndex); } /* (non-Javadoc) * @see net.finmath.montecarlo.interestrate.LIBORMarketModel#getLiborPeriodIndex(double) */ @Override public int getLiborPeriodIndex(final double time) { return liborPeriodDiscretization.getTimeIndex(time); } /* (non-Javadoc) * @see net.finmath.montecarlo.interestrate.LIBORMarketModel#getLiborPeriodDiscretization() */ @Override public TimeDiscretization getLiborPeriodDiscretization() { return liborPeriodDiscretization; } /** * Alternative implementation for the drift. For experimental purposes. * * @param timeIndex * @param componentIndex * @param realizationAtTimeIndex * @param realizationPredictor * @return */ private RandomVariable getDrift(final MonteCarloProcess process, final int timeIndex, final int componentIndex, final RandomVariable[] realizationAtTimeIndex, final RandomVariable[] realizationPredictor) { // Check if this LIBOR is already fixed if(process.getTime(timeIndex) >= this.getLiborPeriod(componentIndex)) { return null; } /* * We implemented several different methods to calculate the drift */ if(driftApproximationMethod == Driftapproximation.PREDICTOR_CORRECTOR && realizationPredictor != null) { RandomVariable drift = getDriftEuler(process, timeIndex, componentIndex, realizationAtTimeIndex); final RandomVariable driftEulerWithPredictor = getDriftEuler(process, timeIndex, componentIndex, realizationPredictor); drift = drift.add(driftEulerWithPredictor).div(2.0); return drift; } else if(driftApproximationMethod == Driftapproximation.LINE_INTEGRAL && realizationPredictor != null) { return getDriftLineIntegral(process, timeIndex, componentIndex, realizationAtTimeIndex, realizationPredictor); } else { return getDriftEuler(process, timeIndex, componentIndex, realizationAtTimeIndex); } } protected RandomVariable getDriftEuler(final MonteCarloProcess process, final int timeIndex, final int componentIndex, final RandomVariable[] liborVectorStart) { // The following is the drift of the LIBOR component final double time = process.getTime(timeIndex); // Initialize to 0.0 RandomVariable drift = new RandomVariableFromDoubleArray(time, 0.0); // Get the start and end of the summation (start is the LIBOR after the current LIBOR component, end is the last LIBOR) int firstLiborIndex, lastLiborIndex; switch(measure) { case SPOT: // Spot measure firstLiborIndex = this.getLiborPeriodIndex(time)+1; if(firstLiborIndex<0) { firstLiborIndex = -firstLiborIndex-1 + 1; } lastLiborIndex = componentIndex; break; case TERMINAL: default: firstLiborIndex = componentIndex+1; lastLiborIndex = liborPeriodDiscretization.getNumberOfTimeSteps()-1; break; } // The sum for(int liborIndex = firstLiborIndex; liborIndex<=lastLiborIndex; liborIndex++) { final double periodLength = liborPeriodDiscretization.getTimeStep(liborIndex); RandomVariable covariance = covarianceModel.getCovariance(timeIndex, componentIndex, liborIndex, null); final RandomVariable libor = liborVectorStart[liborIndex]; covariance = covariance.mult(periodLength).mult(libor).discount(libor, periodLength); drift = drift.add(covariance); } if(measure == Measure.TERMINAL) { drift = drift.mult(-1.0); } // Drift adjustment for log-coordinate final RandomVariable variance = covarianceModel.getCovariance(timeIndex, componentIndex, componentIndex, null); drift = drift.addProduct(variance, -0.5); return drift; } private RandomVariable getDriftLineIntegral(final MonteCarloProcess process, final int timeIndex, final int componentIndex, final RandomVariable[] liborVectorStart, final RandomVariable[] liborVectorEnd) { // The following is the dirft of the LIBOR component final double time = process.getTime(timeIndex); // Check if this LIBOR is already fixed if(process.getTime(timeIndex) >= this.getLiborPeriod(componentIndex)) { return null; } // Initialize to 0.0 RandomVariable drift = new RandomVariableFromDoubleArray(time, 0.0); // Get the start and end of the summation (start is the LIBOR after the current LIBOR component, end is the last LIBOR) int firstLiborIndex, lastLiborIndex; switch(measure) { case SPOT: // Spot measure firstLiborIndex = this.getLiborPeriodIndex(time)+1; if(firstLiborIndex<0) { firstLiborIndex = -firstLiborIndex-1 + 1; } lastLiborIndex = componentIndex; break; case TERMINAL: default: firstLiborIndex = componentIndex+1; lastLiborIndex = liborPeriodDiscretization.getNumberOfTimeSteps()-1; break; } // The sum for(int liborIndex = firstLiborIndex; liborIndex<=lastLiborIndex; liborIndex++) { final double periodLength = liborPeriodDiscretization.getTimeStep(liborIndex); final RandomVariable covariance = covarianceModel.getCovariance(timeIndex, componentIndex, liborIndex, null); /* * We calculate * driftTerm = covariance * log( (1 + periodLength * liborVectorEnd[liborIndex]) / (1 + periodLength * liborVectorStart[liborIndex]) ) * / log(liborVectorEnd[liborIndex] / liborVectorStart[liborIndex]) */ RandomVariable driftTerm = new RandomVariableFromDoubleArray(1.0); driftTerm = driftTerm.accrue(liborVectorEnd[liborIndex], periodLength); driftTerm = driftTerm.discount(liborVectorStart[liborIndex], periodLength); driftTerm = driftTerm.log(); driftTerm = driftTerm.mult(covariance); driftTerm = driftTerm.div(liborVectorEnd[liborIndex].div(liborVectorStart[liborIndex]).log()); drift = drift.sub(driftTerm); } return drift; } /** * @return Returns the measure. */ public Measure getMeasure() { return measure; } /* (non-Javadoc) * @see net.finmath.montecarlo.interestrate.LIBORMarketModel#getIntegratedLIBORCovariance() */ @Override public double[][][] getIntegratedLIBORCovariance(TimeDiscretization simulationTimeDiscretization) { if(integratedLIBORCovariance != null) { return integratedLIBORCovariance; } final TimeDiscretization liborPeriodDiscretization = getLiborPeriodDiscretization(); integratedLIBORCovariance = new double[simulationTimeDiscretization.getNumberOfTimeSteps()][liborPeriodDiscretization.getNumberOfTimeSteps()][liborPeriodDiscretization.getNumberOfTimeSteps()]; for(int componentIndex1 = 0; componentIndex1 < liborPeriodDiscretization.getNumberOfTimeSteps(); componentIndex1++) { // Sum the libor cross terms (use symmetry) for(int componentIndex2 = componentIndex1; componentIndex2 < liborPeriodDiscretization.getNumberOfTimeSteps(); componentIndex2++) { double integratedLIBORCovarianceValue = 0.0; for(int timeIndex = 0; timeIndex < simulationTimeDiscretization.getNumberOfTimeSteps(); timeIndex++) { final double dt = simulationTimeDiscretization.getTimeStep(timeIndex); final RandomVariable[] factorLoadingOfComponent1 = getCovarianceModel().getFactorLoading(simulationTimeDiscretization.getTime(timeIndex), liborPeriodDiscretization.getTime(componentIndex1), null); final RandomVariable[] factorLoadingOfComponent2 = getCovarianceModel().getFactorLoading(simulationTimeDiscretization.getTime(timeIndex), liborPeriodDiscretization.getTime(componentIndex2), null); for(int factorIndex = 0; factorIndex < getNumberOfFactors(); factorIndex++) { integratedLIBORCovarianceValue += factorLoadingOfComponent1[factorIndex].get(0) * factorLoadingOfComponent2[factorIndex].get(0) * dt; } integratedLIBORCovariance[timeIndex][componentIndex1][componentIndex2] = integratedLIBORCovarianceValue; } } } return integratedLIBORCovariance; } @Override public Object clone() { return new LIBORMarketModelStandard(liborPeriodDiscretization, forwardRateCurve, covarianceModel); } /** * @param driftApproximationMethod The driftApproximationMethod to set. */ public void setDriftApproximationMethod(final Driftapproximation driftApproximationMethod) { this.driftApproximationMethod = driftApproximationMethod; } /** * @param measure The measure to set. */ public void setMeasure(final Measure measure) { this.measure = measure; } @Override public AnalyticModel getAnalyticModel() { return curveModel; } @Override public DiscountCurve getDiscountCurve() { if(discountCurve == null) { final DiscountCurve discountCurveFromForwardCurve = new DiscountCurveFromForwardCurve(getForwardRateCurve()); return discountCurveFromForwardCurve; } return discountCurve; } @Override public ForwardCurve getForwardRateCurve() { return forwardRateCurve; } /** * Return the swaption market data used for calibration (if any, may be null). * * @return The swaption market data used for calibration (if any, may be null). */ public SwaptionMarketData getSwaptionMarketData() { return swaptionMarketData; } /* (non-Javadoc) * @see net.finmath.montecarlo.interestrate.LIBORMarketModel#getCovarianceModel() */ @Override public LIBORCovarianceModel getCovarianceModel() { return covarianceModel; } /** * @param covarianceModel A covariance model * @return A new LIBORMarketModelStandard
using the specified covariance model. */ @Override public LIBORMarketModelStandard getCloneWithModifiedCovarianceModel(final LIBORCovarianceModel covarianceModel) { final LIBORMarketModelStandard model = (LIBORMarketModelStandard)this.clone(); model.covarianceModel = covarianceModel; return model; } @Override public LIBORMarketModelStandard getCloneWithModifiedData(final MapdataModified) throws CalculationException { TimeDiscretization liborPeriodDiscretization = this.liborPeriodDiscretization; final AnalyticModel analyticModel = curveModel; ForwardCurve forwardRateCurve = this.forwardRateCurve; LIBORCovarianceModel covarianceModel = this.covarianceModel; SwaptionMarketData swaptionMarketData = null; if(dataModified.containsKey("liborPeriodDiscretization")) { liborPeriodDiscretization = (TimeDiscretization)dataModified.get("liborPeriodDiscretization"); } if(dataModified.containsKey("forwardRateCurve")) { forwardRateCurve = (ForwardCurve)dataModified.get("forwardRateCurve"); } if(dataModified.containsKey("forwardRateShift")) { throw new RuntimeException("Forward rate shift clone currently disabled."); } if(dataModified.containsKey("covarianceModel")) { covarianceModel = (LIBORCovarianceModel)dataModified.get("covarianceModel"); } if(dataModified.containsKey("swaptionMarketData")) { swaptionMarketData = (SwaptionMarketData)dataModified.get("swaptionMarketData"); } if(swaptionMarketData == null) { return new LIBORMarketModelStandard(liborPeriodDiscretization, forwardRateCurve, covarianceModel); } else { return new LIBORMarketModelStandard(liborPeriodDiscretization, forwardRateCurve, covarianceModel, swaptionMarketData); } } @Override public Map getModelParameters() { // TODO Add implementation throw new UnsupportedOperationException(); } }