/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.interestrate.definition; import java.util.Arrays; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.instrument.annuity.AnnuityCouponDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponDefinition; import com.opengamma.analytics.financial.interestrate.swaption.derivative.SwaptionPhysicalFixedIbor; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.util.time.TimeCalculator; import com.opengamma.financial.convention.daycount.DayCount; import com.opengamma.util.ArgumentChecker; /** * Parameters related to a multi-factor Libor Market Model with separable * displaced diffusion dynamic. The equations underlying the Libor Market * Model in the probability space with numeraire $P(.,t_{j+1})$ are * $$ * \begin{equation*} * dL_t^j = \alpha(t) (L+a_{j}) \gamma_{j} . dW_t^{j+1} * \end{equation*} * $$ * with $\alpha(t) = \exp(a t)$. The $\gamma_j$ are m-dimensional vectors. */ public class LiborMarketModelDisplacedDiffusionParameters { /** * The times separating the Ibor periods. */ private final double[] _iborTime; /** * The accrual factors for the different periods. */ private final double[] _accrualFactor; /** * The displacements for the different periods. */ private final double[] _displacement; /** * The volatilities. The dimensions of the volatility is number of periods X number of factors. */ private final double[][] _volatility; /** * The mean reversion used for the volatility time dependency. */ private final double _meanReversion; /** * The number of periods. */ private final int _nbPeriod; /** * The number of factors. */ private final int _nbFactor; /** * The time tolerance between the dates given by the model and the dates of the instrument. To avoid rounding problems. */ private static final double TIME_TOLERANCE = 1.0E-3; /** * Initialises the mean reversion and number of parameters to zero, and other parameters to empty arrays. */ public LiborMarketModelDisplacedDiffusionParameters() { _nbPeriod = 0; _iborTime = new double[1]; _accrualFactor = new double[0]; _displacement = new double[0]; _volatility = new double[0][0]; _meanReversion = 0.0; _nbFactor = 0; } /** * Constructor from the model details. * @param iborTime The times separating the Ibor periods. * @param accrualFactor The accrual factors for the different periods. * @param displacement The displacements for the different periods. * @param volatility The volatilities. The dimensions of the volatility is number of periods X number of factors. * @param meanReversion The mean reversion used for the volatility time dependency. */ public LiborMarketModelDisplacedDiffusionParameters(final double[] iborTime, final double[] accrualFactor, final double[] displacement, final double[][] volatility, final double meanReversion) { ArgumentChecker.notNull(iborTime, "LMM Libor times"); ArgumentChecker.notNull(accrualFactor, "LMM accrual factors"); ArgumentChecker.notNull(displacement, "LMM displacements"); ArgumentChecker.notNull(volatility, "LMM volatility"); _nbPeriod = accrualFactor.length; ArgumentChecker.isTrue(iborTime.length == _nbPeriod + 1, "LMM data: Dimension"); ArgumentChecker.isTrue(_nbPeriod == displacement.length, "LMM data: Dimension"); ArgumentChecker.isTrue(_nbPeriod == volatility.length, "LMM data: Dimension"); _iborTime = iborTime; _accrualFactor = accrualFactor; _displacement = displacement; _volatility = volatility; _meanReversion = meanReversion; _nbFactor = volatility[0].length; } /** * Create a new copy of the object with the same data. All the arrays are cloned. * @return The LMM parameters. */ public LiborMarketModelDisplacedDiffusionParameters copy() { final double[][] vol = new double[_volatility.length][]; for (int loopperiod = 0; loopperiod < _volatility.length; loopperiod++) { vol[loopperiod] = _volatility[loopperiod].clone(); } return new LiborMarketModelDisplacedDiffusionParameters(_iborTime.clone(), _accrualFactor.clone(), _displacement.clone(), vol, _meanReversion); } /** * Create model parameters adapted to a specific annuity. * @param modelDate The pricing date, not null * @param annuity The annuity to be used for the model construction, not null * @param dayCount The Ibor day count, not null * @param displacement The displacement (common to all Ibors). * @param meanReversion The mean reversion. * @param volatilityFunction The volatility function. For a given time to Ibor period start date it provides the volatilities (or weights) of the different factors, not null * @return A Libor Market Model parameter set. */ public static LiborMarketModelDisplacedDiffusionParameters from(final ZonedDateTime modelDate, final AnnuityCouponDefinition<? extends CouponDefinition> annuity, final DayCount dayCount, final double displacement, final double meanReversion, final Function1D<Double, Double[]> volatilityFunction) { ArgumentChecker.notNull(modelDate, "modelDate"); ArgumentChecker.notNull(annuity, "annuity"); ArgumentChecker.notNull(dayCount, "dayCount"); ArgumentChecker.notNull(volatilityFunction, "volatilityFunction"); final int nbPeriod = annuity.getNumberOfPayments(); final ZonedDateTime[] iborDate = new ZonedDateTime[nbPeriod + 1]; final double[] iborTime = new double[nbPeriod + 1]; final double[] accrualFactor = new double[nbPeriod]; final double[] d = new double[nbPeriod]; final Double[] tmp = volatilityFunction.evaluate(0.0); final double[][] vol = new double[nbPeriod][tmp.length]; iborDate[0] = annuity.getNthPayment(0).getAccrualStartDate(); iborTime[0] = TimeCalculator.getTimeBetween(modelDate, iborDate[0]); for (int loopcf = 0; loopcf < nbPeriod; loopcf++) { iborDate[loopcf + 1] = annuity.getNthPayment(loopcf).getPaymentDate(); iborTime[loopcf + 1] = TimeCalculator.getTimeBetween(modelDate, iborDate[loopcf + 1]); accrualFactor[loopcf] = dayCount.getDayCountFraction(iborDate[loopcf], iborDate[loopcf + 1], annuity.getCalendar()); d[loopcf] = displacement; //TODO: better conversion to double[] final Double[] tmp2 = volatilityFunction.evaluate(iborTime[loopcf]); for (int looptmp = 0; looptmp < tmp2.length; looptmp++) { vol[loopcf][looptmp] = tmp2[looptmp]; } } return new LiborMarketModelDisplacedDiffusionParameters(iborTime, accrualFactor, d, vol, meanReversion); } /** * Creates model parameters adapted to a specific physical delivery fixed-float swaption. * @param swaption The swaption, not null * @param displacement The displacement * @param meanReversion The mean reversion * @param volatilityFunction The volatility function. For a given time to Ibor period start date it provides the volatilities (or weights) of the different factors, not null * @return A Libor Market Model parameter set. */ public static LiborMarketModelDisplacedDiffusionParameters from(final SwaptionPhysicalFixedIbor swaption, final double displacement, final double meanReversion, final Function1D<Double, Double[]> volatilityFunction) { ArgumentChecker.notNull(swaption, "swaption"); ArgumentChecker.notNull(volatilityFunction, "volatilityFunction"); final int nbPeriod = swaption.getUnderlyingSwap().getSecondLeg().getNumberOfPayments(); final double[] iborTime = new double[nbPeriod + 1]; final double[] accrualFactor = new double[nbPeriod]; final double[] d = new double[nbPeriod]; final Double[] tmp = volatilityFunction.evaluate(0.0); final double[][] vol = new double[nbPeriod][tmp.length]; iborTime[0] = swaption.getSettlementTime(); for (int loopcf = 0; loopcf < nbPeriod; loopcf++) { iborTime[loopcf + 1] = swaption.getUnderlyingSwap().getSecondLeg().getNthPayment(loopcf).getPaymentTime(); accrualFactor[loopcf] = swaption.getUnderlyingSwap().getSecondLeg().getNthPayment(loopcf).getPaymentYearFraction(); d[loopcf] = displacement; //TODO: better conversion to double[] final Double[] tmp2 = volatilityFunction.evaluate(iborTime[loopcf]); for (int looptmp = 0; looptmp < tmp2.length; looptmp++) { vol[loopcf][looptmp] = tmp2[looptmp]; } } return new LiborMarketModelDisplacedDiffusionParameters(iborTime, accrualFactor, d, vol, meanReversion); } /** * Gets the _iborTime field. * @return the _iborTime */ public double[] getIborTime() { return _iborTime; } /** * Gets the _accrualFactor field. * @return the _accrualFactor */ public double[] getAccrualFactor() { return _accrualFactor; } /** * Gets the _displacement field. * @return the _displacement */ public double[] getDisplacement() { return _displacement; } /** * Gets the _volatility field. * @return the _volatility */ public double[][] getVolatility() { return _volatility; } /** * Gets the _meanReversion field. * @return the _meanReversion */ public double getMeanReversion() { return _meanReversion; } /** * Gets the _nbPeriod field. * @return the _nbPeriod */ public int getNbPeriod() { return _nbPeriod; } /** * Gets the _nbFactor field. * @return the _nbFactor */ public int getNbFactor() { return _nbFactor; } /** * Gets the time tolerance. * @return The time tolerance */ public double getTimeTolerance() { return TIME_TOLERANCE; } /** * Return the index in the Ibor time list of a given time. The match does not need to be exact (to allow rounding effects and 1 day discrepancy). * The allowed difference is set in the TIME_TOLERANCE variable. * @param time The time. * @return The index. */ public int getTimeIndex(final double time) { int index = Arrays.binarySearch(_iborTime, time); if (index < 0) { if (_iborTime[-index - 1] - time < TIME_TOLERANCE) { index = -index - 1; } else { if (time - _iborTime[-index - 2] < TIME_TOLERANCE) { index = -index - 2; } else { throw new IllegalArgumentException("Instrument time incompatible with LMM"); } } } return index; } /** * Change the model volatility in a block to a given volatility matrix. * @param volatility The changed volatility. * @param startIndex The start index for the block to change. */ public final void setVolatility(final double[][] volatility, final int startIndex) { ArgumentChecker.notNull(volatility, "LMM volatility"); ArgumentChecker.isTrue(volatility[0].length == _nbFactor, "LMM: incorrect number of factors"); for (int loopperiod = 0; loopperiod < volatility.length; loopperiod++) { System.arraycopy(volatility[loopperiod], 0, _volatility[startIndex + loopperiod], 0, volatility[loopperiod].length); } } /** * Change the model displacement in a block to a given displacement vector. * @param displacement The change displacement. * @param startIndex The start index for the block to change. */ public final void setDisplacement(final double[] displacement, final int startIndex) { ArgumentChecker.notNull(displacement, "LMM displacement"); System.arraycopy(displacement, 0, _displacement, startIndex, displacement.length); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(_accrualFactor); result = prime * result + Arrays.hashCode(_displacement); result = prime * result + Arrays.hashCode(_iborTime); long temp; temp = Double.doubleToLongBits(_meanReversion); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + _nbFactor; result = prime * result + _nbPeriod; result = prime * result + Arrays.hashCode(_volatility); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof LiborMarketModelDisplacedDiffusionParameters)) { return false; } final LiborMarketModelDisplacedDiffusionParameters other = (LiborMarketModelDisplacedDiffusionParameters) obj; if (_nbFactor != other._nbFactor) { return false; } if (Double.doubleToLongBits(_meanReversion) != Double.doubleToLongBits(other._meanReversion)) { return false; } if (_nbPeriod != other._nbPeriod) { return false; } if (!Arrays.equals(_accrualFactor, other._accrualFactor)) { return false; } if (!Arrays.equals(_displacement, other._displacement)) { return false; } if (!Arrays.equals(_iborTime, other._iborTime)) { return false; } if (!Arrays.deepEquals(_volatility, other._volatility)) { return false; } return true; } }