/*
* (c) Copyright Christian P. Fries, Germany. All rights reserved. Contact: email@christian-fries.de.
*
* Created on 09.02.2004
*/
package net.finmath.montecarlo.interestrate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.finmath.exception.CalculationException;
import net.finmath.marketdata.model.AnalyticModelInterface;
import net.finmath.marketdata.model.curves.DiscountCurveInterface;
import net.finmath.marketdata.model.curves.ForwardCurveInterface;
import net.finmath.montecarlo.interestrate.modelplugins.TermStructureCovarianceModelInterface;
import net.finmath.montecarlo.interestrate.modelplugins.TermStructureCovarianceModelParametric;
import net.finmath.montecarlo.interestrate.products.AbstractLIBORMonteCarloProduct;
import net.finmath.montecarlo.model.AbstractModel;
import net.finmath.montecarlo.process.AbstractProcessInterface;
import net.finmath.stochastic.RandomVariableInterface;
import net.finmath.time.TimeDiscretization;
import net.finmath.time.TimeDiscretizationInterface;
/**
* Implements a discretized Heath-Jarrow-Morton model / LIBOR market model with dynamic tenor refinement, see
* <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2884699">https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2884699</a>.
*
* <br><br>
* In its default case the class specifies a multi-factor LIBOR market model, that is
* \( L_{j} = \frac{1}{T_{j+1}-T_{j}} ( exp(Y_{j}) - 1 ) \), where
* \[
* dY_{j} = \mu_{j} dt + \lambda_{1,j} dW_{1} + \ldots + \lambda_{m,j} dW_{m}
* \]
* <br>
* The model uses an <code>AbstractLIBORCovarianceModel</code> for the specification of
* <i>(λ<sub>1,j</sub>,...,λ<sub>m,j</sub>)</i> as a covariance model.
* See {@link net.finmath.montecarlo.model.AbstractModelInterface} for details on the implemented interface
* <br><br>
* The model uses an <code>AbstractLIBORCovarianceModel</code> as a covariance model.
* If the covariance model is of type <code>AbstractLIBORCovarianceModelParametric</code>
* a calibration to swaptions can be performed.
* <br>
* Note that λ may still depend on <i>L</i> (through a local volatility model).
* <br>
* The simulation is performed under spot measure, that is, the numeraire
* is \( N(T_{i}) = \prod_{j=0}^{i-1} (1 + L(T_{j},T_{j+1};T_{j}) (T_{j+1}-T_{j})) \).
*
* The map <code>properties</code> allows to configure the model. The following keys may be used:
* <ul>
* <li>
* <code>liborCap</code>: An optional <code>Double</code> value applied as a cap to the LIBOR rates.
* May be used to limit the simulated valued to prevent values attaining POSITIVE_INFINITY and
* numerical problems. To disable the cap, set <code>liborCap</code> to <code>Double.POSITIVE_INFINITY</code>.
* </li>
* </ul>
* <br>
*
* The main task of this class is to calculate the risk-neutral drift and the
* corresponding numeraire given the covariance model.
*
* The calibration of the covariance structure is not part of this class.
*
* @author Christian Fries
* @version 1.2
* @see net.finmath.montecarlo.process.AbstractProcessInterface The interface for numerical schemes.
* @see net.finmath.montecarlo.model.AbstractModelInterface The interface for models provinding parameters to numerical schemes.
* @see <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2884699">https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2884699</a>
*/
public class LIBORMarketModelWithTenorRefinement extends AbstractModel implements TermStructureModelInterface {
public enum Driftapproximation { EULER, LINE_INTEGRAL, PREDICTOR_CORRECTOR }
private final TimeDiscretizationInterface[] liborPeriodDiscretizations;
private final Integer[] numberOfDiscretizationIntervalls;
private String forwardCurveName;
private AnalyticModelInterface curveModel;
private ForwardCurveInterface forwardRateCurve;
private DiscountCurveInterface discountCurve;
private TermStructureCovarianceModelInterface covarianceModel;
// Cache for the numeraires, needs to be invalidated if process changes
private final ConcurrentHashMap<Integer, RandomVariableInterface> numeraires;
private AbstractProcessInterface numerairesProcess = null;
public static class CalibrationItem {
public final AbstractLIBORMonteCarloProduct calibrationProduct;
public final double calibrationTargetValue;
public final double calibrationWeight;
public CalibrationItem(AbstractLIBORMonteCarloProduct calibrationProduct, double calibrationTargetValue, double calibrationWeight) {
super();
this.calibrationProduct = calibrationProduct;
this.calibrationTargetValue = calibrationTargetValue;
this.calibrationWeight = calibrationWeight;
}
@Override
public String toString() {
return "CalibrationItem [calibrationProduct=" + calibrationProduct
+ ", calibrationTargetValue=" + calibrationTargetValue
+ ", calibrationWeight=" + calibrationWeight + "]";
}
}
/**
* Creates a model for given covariance.
*
* Creates a discretized Heath-Jarrow-Morton model / LIBOR market model with dynamic tenor refinement, see
* <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2884699">https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2884699</a>.
* <br>
* If calibrationItems in non-empty and the covariance model is a parametric model,
* the covariance will be replaced by a calibrate version of the same model, i.e.,
* the LIBOR Market Model will be calibrated.
* <br>
* The map <code>properties</code> allows to configure the model. The following keys may be used:
* <ul>
* <li>
* <code>liborCap</code>: An optional <code>Double</code> value applied as a cap to the LIBOR rates.
* May be used to limit the simulated valued to prevent values attaining POSITIVE_INFINITY and
* numerical problems. To disable the cap, set <code>liborCap</code> to <code>Double.POSITIVE_INFINITY</code>.
* </li>
* <li>
* <code>calibrationParameters</code>: Possible values:
* <ul>
* <li>
* <code>Map<String,Object></code> a parameter map with the following key/value pairs:
* <ul>
* <li>
* <code>accuracy</code>: <code>Double</code> specifying the required solver accuracy.
* </li>
* <li>
* <code>maxIterations</code>: <code>Integer</code> specifying the maximum iterations for the solver.
* </li>
* </ul>
* </li>
* </ul>
* </li>
* </ul>
*
* @param liborPeriodDiscretizations A vector of tenor discretizations of the interest rate curve into forward rates (tenor structure), finest first.
* @param numberOfDiscretizationIntervalls A vector of number of periods to be taken from the liborPeriodDiscretizations.
* @param analyticModel The associated analytic model of this model (containing the associated market data objects like curve).
* @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 calibrationItems 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).
* @param properties Key value map specifying properties like <code>measure</code> and <code>stateSpace</code>.
* @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
*/
public LIBORMarketModelWithTenorRefinement(
TimeDiscretizationInterface[] liborPeriodDiscretizations,
Integer[] numberOfDiscretizationIntervalls,
AnalyticModelInterface analyticModel,
ForwardCurveInterface forwardRateCurve,
DiscountCurveInterface discountCurve,
TermStructureCovarianceModelInterface covarianceModel,
CalibrationItem[] calibrationItems,
Map<String, ?> properties
) throws CalculationException {
Map<String,Object> calibrationParameters = null;
if(properties != null && properties.containsKey("calibrationParameters")) calibrationParameters = (Map<String,Object>)properties.get("calibrationParameters");
this.liborPeriodDiscretizations = liborPeriodDiscretizations;
this.numberOfDiscretizationIntervalls = numberOfDiscretizationIntervalls;
this.curveModel = analyticModel;
this.forwardRateCurve = forwardRateCurve;
this.discountCurve = discountCurve;
this.covarianceModel = covarianceModel;
// Perform calibration, if data is given
if(calibrationItems != null && calibrationItems.length > 0) {
TermStructureCovarianceModelParametric covarianceModelParametric = null;
try {
covarianceModelParametric = (TermStructureCovarianceModelParametric)covarianceModel;
}
catch(Exception e) {
throw new ClassCastException("Calibration is currently restricted to parametric covariance models (TermStructureCovarianceModelParametricInterface).");
}
// @TODO Should be more elegant. Convert array for constructor
AbstractLIBORMonteCarloProduct[] calibrationProducts = new AbstractLIBORMonteCarloProduct[calibrationItems.length];
double[] calibrationTargetValues = new double[calibrationItems.length];
double[] calibrationWeights = new double[calibrationItems.length];
for(int i=0; i<calibrationTargetValues.length; i++) {
calibrationProducts[i] = calibrationItems[i].calibrationProduct;
calibrationTargetValues[i] = calibrationItems[i].calibrationTargetValue;
calibrationWeights[i] = calibrationItems[i].calibrationWeight;
}
this.covarianceModel = covarianceModelParametric.getCloneCalibrated(this, calibrationProducts, calibrationTargetValues, calibrationWeights, calibrationParameters);
}
numeraires = new ConcurrentHashMap<Integer, RandomVariableInterface>();
}
/**
* Return the numeraire at a given time.
*
* The numeraire is provided for interpolated points. If requested on points which are not
* part of the tenor discretization, the numeraire uses a linear interpolation of the reciprocal
* value. See ISBN 0470047224 for details.
*
* @param time Time time <i>t</i> for which the numeraire should be returned <i>N(t)</i>.
* @return The numeraire at the specified time as <code>RandomVariableInterface</code>
* @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
*/
@Override
public RandomVariableInterface getNumeraire(double time) throws CalculationException {
int timeIndex = liborPeriodDiscretizations[0].getTimeIndex(time);
TimeDiscretizationInterface liborPeriodDiscretization = liborPeriodDiscretizations[0];
if(timeIndex < 0) {
// Interpolation of Numeraire: log linear interpolation.
int upperIndex = -timeIndex-1;
int lowerIndex = upperIndex-1;
if(lowerIndex < 0) throw new IllegalArgumentException("Numeraire requested for time " + time + ". Unsupported");
double alpha = (time-liborPeriodDiscretization.getTime(lowerIndex)) / (liborPeriodDiscretization.getTime(upperIndex) - liborPeriodDiscretization.getTime(lowerIndex));
RandomVariableInterface numeraire = getNumeraire(liborPeriodDiscretization.getTime(upperIndex)).log().mult(alpha).add(getNumeraire(liborPeriodDiscretization.getTime(lowerIndex)).log().mult(1.0-alpha)).exp();
/*
* Adjust for discounting, i.e. funding or collateralization
*/
if(discountCurve != null) {
// This includes a control for zero bonds
double deterministicNumeraireAdjustment = numeraire.invert().getAverage() / discountCurve.getDiscountFactor(curveModel, time);
numeraire = numeraire.mult(deterministicNumeraireAdjustment);
}
return numeraire;
}
/*
* Calculate the numeraire, when time is part of liborPeriodDiscretization
*/
/*
* Check if numeraire cache is values (i.e. process did not change)
*/
if(getProcess() != numerairesProcess) {
numeraires.clear();
numerairesProcess = getProcess();
}
/*
* Check if numeraire is part of the cache
*/
RandomVariableInterface numeraire = numeraires.get(timeIndex);
if(numeraire == null) {
/*
* Calculate the numeraire for timeIndex
*/
if(timeIndex == 0) {
numeraire = getProcess().getStochasticDriver().getRandomVariableForConstant(1.0);
}
else {
// Initialize to previous numeraire
numeraire = getNumeraire(liborPeriodDiscretizations[0].getTime(timeIndex-1));
double periodStart = liborPeriodDiscretizations[0].getTime(timeIndex-1);
double periodEnd = liborPeriodDiscretizations[0].getTime(timeIndex);
RandomVariableInterface libor = getLIBOR(periodStart, periodStart, periodEnd);
numeraire = numeraire.accrue(libor, periodEnd-periodStart);
}
// Cache the numeraire
numeraires.put(timeIndex, numeraire);
}
/*
* Adjust for discounting, i.e. funding or collateralization
*/
if(discountCurve != null) {
// This includes a control for zero bonds
double deterministicNumeraireAdjustment = numeraire.invert().getAverage() / discountCurve.getDiscountFactor(curveModel, time);
numeraire = numeraire.mult(deterministicNumeraireAdjustment);
}
return numeraire;
}
@Override
public RandomVariableInterface[] getInitialState() {
RandomVariableInterface[] initialStateRandomVariable = new RandomVariableInterface[getNumberOfComponents()];
for(int componentIndex=0; componentIndex<getNumberOfComponents(); componentIndex++) {
initialStateRandomVariable[componentIndex] = getProcess().getStochasticDriver().getRandomVariableForConstant(0.0);
}
return initialStateRandomVariable;
}
/**
* Return the complete vector of the drift for the time index timeIndex, given that current state is realizationAtTimeIndex.
* The drift will be zero for rates being already fixed.
*
* The method currently provides the drift for either <code>Measure.SPOT</code> or <code>Measure.TERMINAL</code> - depending how the
* model object was constructed. For <code>Measure.TERMINAL</code> the j-th entry of the return value is the random variable
* \[
* \mu_{j}^{\mathbb{Q}^{P(T_{n})}}(t) \ = \ - \mathop{\sum_{l\geq j+1}}_{l\leq n-1} \frac{\delta_{l}}{1+\delta_{l} L_{l}(t)} (\lambda_{j}(t) \cdot \lambda_{l}(t))
* \]
* and for <code>Measure.SPOT</code> the j-th entry of the return value is the random variable
* \[
* \mu_{j}^{\mathbb{Q}^{N}}(t) \ = \ \sum_{m(t) < l\leq j} \frac{\delta_{l}}{1+\delta_{l} L_{l}(t)} (\lambda_{j}(t) \cdot \lambda_{l}(t))
* \]
* where \( \lambda_{j} \) is the vector for factor loadings for the j-th component of the stochastic process (that is, the diffusion part is
* \( \sum_{k=1}^m \lambda_{j,k} \mathrm{d}W_{k} \)).
*
* Note: The scalar product of the factor loadings determines the instantaneous covariance. If the model is written in log-coordinates (using exp as a state space transform), we find
* \(\lambda_{j} \cdot \lambda_{l} = \sum_{k=1}^m \lambda_{j,k} \lambda_{l,k} = \sigma_{j} \sigma_{l} \rho_{j,l} \).
* If the model is written without a state space transformation (in its orignial coordinates) then \(\lambda_{j} \cdot \lambda_{l} = \sum_{k=1}^m \lambda_{j,k} \lambda_{l,k} = L_{j} L_{l} \sigma_{j} \sigma_{l} \rho_{j,l} \).
*
*
* @see net.finmath.montecarlo.interestrate.LIBORMarketModelWithTenorRefinement#getNumeraire(double) The calculation of the drift is consistent with the calculation of the numeraire in <code>getNumeraire</code>.
* @see net.finmath.montecarlo.interestrate.LIBORMarketModelWithTenorRefinement#getFactorLoading(int, int, RandomVariableInterface[]) The factor loading \( \lambda_{j,k} \).
*
* @param timeIndex Time index <i>i</i> for which the drift should be returned <i>μ(t<sub>i</sub>)</i>.
* @param realizationAtTimeIndex Time current forward rate vector at time index <i>i</i> which should be used in the calculation.
* @return The drift vector μ(t<sub>i</sub>) as <code>RandomVariable[]</code>
*/
@Override
public RandomVariableInterface[] getDrift(int timeIndex, RandomVariableInterface[] realizationAtTimeIndex, RandomVariableInterface[] realizationPredictor) {
double time = getTime(timeIndex);
double timeStep = getTimeDiscretization().getTimeStep(timeIndex);
double timeNext = getTime(timeIndex+1);
RandomVariableInterface zero = getProcess().getStochasticDriver().getRandomVariableForConstant(0.0);
// Allocate drift vector and initialize to zero (will be used to sum up drift components)
RandomVariableInterface[] drift = new RandomVariableInterface[getNumberOfComponents()];
for(int componentIndex=0; componentIndex<getNumberOfComponents(); componentIndex++) {
drift[componentIndex] = null;
}
RandomVariableInterface[] variances = new RandomVariableInterface[getNumberOfComponents()];
for(int componentIndex=0; componentIndex<getNumberOfComponents(); componentIndex++) {
variances[componentIndex] = zero;
}
RandomVariableInterface[] covarianceFactorSums = new RandomVariableInterface[getNumberOfFactors()];
for(int factorIndex=0; factorIndex<getNumberOfFactors(); factorIndex++) {
covarianceFactorSums[factorIndex] = zero;
}
/*
* Standard HJM drift part of log-forward-bond
*/
TimeDiscretizationInterface liborPeriodDiscretization = getLiborPeriodDiscretization(timeNext);
// Calculate drift for the component componentIndex (starting at firstLiborIndex, others are zero)
for(int componentIndex=0; componentIndex<liborPeriodDiscretization.getNumberOfTimeSteps(); componentIndex++) {
drift[componentIndex] = zero;
double periodStart = liborPeriodDiscretization.getTime(componentIndex);
double periodLength = liborPeriodDiscretization.getTimeStep(componentIndex);
double periodEnd = periodStart + periodLength;
double tenorTime = covarianceModel.getScaledTenorTime(periodStart, periodEnd);
// @todo Document that factorLoading componentIndexing is on time discretization of t+1 for interval (t,t+1)
RandomVariableInterface[] factorLoading = getFactorLoading(timeIndex, componentIndex, realizationAtTimeIndex);
double weight = getWeightForTenorRefinement(periodStart,periodStart,periodStart,periodEnd);
for(int factorIndex=0; factorIndex<getNumberOfFactors(); factorIndex++) {
drift[componentIndex] = drift[componentIndex].addProduct(covarianceFactorSums[factorIndex].addProduct(factorLoading[factorIndex], weight),factorLoading[factorIndex]);
variances[componentIndex] = variances[componentIndex].addProduct(factorLoading[factorIndex], factorLoading[factorIndex]);
covarianceFactorSums[factorIndex] = covarianceFactorSums[factorIndex].addProduct(factorLoading[factorIndex],tenorTime);
}
}
/*
* Change of tenor discretization - impact on log-forward-bond
*/
TimeDiscretizationInterface liborPeriodDiscretizationPrevious = getLiborPeriodDiscretization(time);
for(int componentIndex=0; componentIndex<liborPeriodDiscretization.getNumberOfTimeSteps(); componentIndex++) {
double periodStart = liborPeriodDiscretization.getTime(componentIndex);
double periodLength = liborPeriodDiscretization.getTimeStep(componentIndex);
double periodEnd = periodStart + periodLength;
double periodStartPrevious = liborPeriodDiscretizationPrevious.getTime(componentIndex);
double periodLengthPrevious = liborPeriodDiscretizationPrevious.getTimeStep(componentIndex);
double periodEndPrevious = periodStartPrevious + periodLengthPrevious;
if(periodStartPrevious == periodStart && periodEndPrevious == periodEnd) continue;
RandomVariableInterface stateVariablePrevious = getStateVariable(timeIndex, periodStartPrevious, periodEndPrevious);
RandomVariableInterface stateVariable = getStateVariable(timeIndex, periodStart, periodEnd);
if(Double.isNaN(stateVariable.getAverage()) || Double.isNaN(stateVariablePrevious.getAverage())) {
throw new IllegalArgumentException();
}
// Shift in indexing and/or tenor refinement
drift[componentIndex] = drift[componentIndex].add(stateVariable.sub(stateVariablePrevious).div(timeStep));
}
/*
* Integrated variance - drift part
*/
for(int componentIndex=0; componentIndex<liborPeriodDiscretization.getNumberOfTimeSteps(); componentIndex++) {
drift[getNumberOfLibors()+componentIndex] = variances[componentIndex];
}
/*
* Change of tenor discretization - impact on integrated variance
*/
for(int componentIndex=0; componentIndex<liborPeriodDiscretization.getNumberOfTimeSteps(); componentIndex++) {
double periodStart = liborPeriodDiscretization.getTime(componentIndex);
double periodLength = liborPeriodDiscretization.getTimeStep(componentIndex);
double periodEnd = periodStart + periodLength;
double periodStartPrevious = liborPeriodDiscretizationPrevious.getTime(componentIndex);
double periodLengthPrevious = liborPeriodDiscretizationPrevious.getTimeStep(componentIndex);
double periodEndPrevious = periodStartPrevious + periodLengthPrevious;
if(periodStartPrevious == periodStart && periodEndPrevious == periodEnd) continue;
RandomVariableInterface stateVariablePrevious = getIntegratedVariance(timeIndex, periodStartPrevious, periodEndPrevious);
RandomVariableInterface stateVariable = getIntegratedVariance(timeIndex, periodStart, periodEnd);
if(Double.isNaN(stateVariable.getAverage()) || Double.isNaN(stateVariablePrevious.getAverage())) {
throw new IllegalArgumentException();
}
// Shift in indexing
drift[getNumberOfLibors()+componentIndex] = drift[getNumberOfLibors()+componentIndex].add(stateVariable.sub(stateVariablePrevious).div(timeStep));
}
return drift;
}
/**
* @param timeIndex
* @param periodStart
* @param periodEnd
* @return
*/
private RandomVariableInterface getIntegratedVariance(int timeIndex, double periodStart, double periodEnd) {
TimeDiscretizationInterface liborPeriodTiscretization = getLiborPeriodDiscretization(getTime(timeIndex));
int periodStartIndex = liborPeriodTiscretization.getTimeIndex(periodStart);
int perirodEndIndex = liborPeriodTiscretization.getTimeIndex(periodEnd);
if(periodStartIndex < 0) periodStartIndex = -periodStartIndex-1-1;
if(perirodEndIndex < 0) perirodEndIndex = -perirodEndIndex-1;
if(perirodEndIndex != periodStartIndex+1) {
throw new IllegalArgumentException();
}
RandomVariableInterface integratedVariance = null;
try {
integratedVariance = getProcess().getProcessValue(timeIndex, getNumberOfLibors()+periodStartIndex);
} catch (CalculationException e) {
}
return integratedVariance;
}
/**
* @param periodStartPrevious
* @param periodEndPrevious
* @param periodStart
* @param periodEnd
* @return
*/
private double getWeightForTenorRefinement(double periodStartPrevious, double periodEndPrevious, double periodStart, double periodEnd) {
TimeDiscretizationInterface numeriareDiscretization = liborPeriodDiscretizations[0];
int periodStartPreviousIndex = numeriareDiscretization.getTimeIndex(periodStartPrevious);
int periodEndPreviousIndex = numeriareDiscretization.getTimeIndex(periodEndPrevious);
int periodStartIndex = numeriareDiscretization.getTimeIndex(periodStart);
int periodEndIndex = numeriareDiscretization.getTimeIndex(periodEnd);
/// @TODO Need to improve LIBOR interpolation if required
if(periodStartIndex < 0) periodStartIndex = -periodStartIndex-1;
if(periodEndIndex < 0) periodEndIndex = -periodEndIndex-1-1;
double weight1 = 0.0;
for(int periodIndex = periodStartPreviousIndex; periodIndex<periodEndPreviousIndex; periodIndex++) {
double deltaT = covarianceModel.getScaledTenorTime(numeriareDiscretization.getTime(periodIndex), numeriareDiscretization.getTime(periodIndex+1));
double deltaTSum = covarianceModel.getScaledTenorTime(periodStartPrevious, numeriareDiscretization.getTime(periodIndex+1));
weight1 += deltaT * deltaTSum;
}
double weight2 = 0.0;
for(int periodIndex = periodStartIndex; periodIndex<periodEndIndex; periodIndex++) {
double deltaT = covarianceModel.getScaledTenorTime(numeriareDiscretization.getTime(periodIndex), numeriareDiscretization.getTime(periodIndex+1));
double deltaTSum = covarianceModel.getScaledTenorTime(periodStartPrevious, numeriareDiscretization.getTime(periodIndex+1));
weight2 += deltaT * deltaTSum;
}
if(weight1 > 0) return weight2 / covarianceModel.getScaledTenorTime(periodStart, periodEnd) - weight1 / covarianceModel.getScaledTenorTime(periodStartPrevious, periodEndPrevious);
else return weight2 / covarianceModel.getScaledTenorTime(periodStart, periodEnd);
}
@Override
public RandomVariableInterface[] getFactorLoading(int timeIndex, int componentIndex, RandomVariableInterface[] realizationAtTimeIndex)
{
RandomVariableInterface zero = getProcess().getStochasticDriver().getRandomVariableForConstant(0.0);
if(componentIndex < getNumberOfLibors()) {
TimeDiscretizationInterface liborPeriodDiscretization = getLiborPeriodDiscretization(getTime(timeIndex));
TimeDiscretizationInterface liborPeriodDiscretizationNext = getLiborPeriodDiscretization(getTime(timeIndex+1));
double periodStart = liborPeriodDiscretizationNext.getTime(componentIndex);
double periodEnd = liborPeriodDiscretizationNext.getTime(componentIndex+1);
RandomVariableInterface[] factorLoadingVector = covarianceModel.getFactorLoading(getTime(timeIndex), periodStart, periodEnd, liborPeriodDiscretization, realizationAtTimeIndex, this);
return factorLoadingVector;
}
else {
RandomVariableInterface[] zeros = new RandomVariableInterface[getProcess().getStochasticDriver().getNumberOfFactors()];
Arrays.fill(zeros, zero);
return zeros;
}
}
@Override
public RandomVariableInterface applyStateSpaceTransform(int componentIndex, RandomVariableInterface randomVariable) {
RandomVariableInterface value = randomVariable;
return value;
}
private TimeDiscretizationInterface getLiborPeriodDiscretization(double time) {
ArrayList<Double> tenorTimes = new ArrayList<Double>();
double firstTime = liborPeriodDiscretizations[0].getTime(liborPeriodDiscretizations[0].getTimeIndexNearestLessOrEqual(time));
double lastTime = firstTime;
tenorTimes.add(firstTime);
for(int discretizationLevelIndex = 0; discretizationLevelIndex<liborPeriodDiscretizations.length; discretizationLevelIndex++) {
int tentorIntervallStartIndex = liborPeriodDiscretizations[discretizationLevelIndex].getTimeIndexNearestLessOrEqual(lastTime)+1;
for(int tenorIntervall=0; tenorIntervall<numberOfDiscretizationIntervalls[discretizationLevelIndex]; tenorIntervall++) {
if(tentorIntervallStartIndex+tenorIntervall >= liborPeriodDiscretizations[discretizationLevelIndex].getNumberOfTimes()) break;
lastTime = liborPeriodDiscretizations[discretizationLevelIndex].getTime(tentorIntervallStartIndex+tenorIntervall);
// round to liborPeriodDiscretizations[0]
lastTime = liborPeriodDiscretizations[0].getTime(liborPeriodDiscretizations[0].getTimeIndexNearestLessOrEqual(lastTime));
tenorTimes.add(lastTime);
}
}
return new TimeDiscretization(tenorTimes);
}
public RandomVariableInterface getStateVariableForPeriod(TimeDiscretizationInterface liborPeriodDiscretization, RandomVariableInterface[] stateVariables, double periodStart, double periodEnd) {
int periodStartIndex = liborPeriodDiscretization.getTimeIndex(periodStart);
int periodEndIndex = liborPeriodDiscretization.getTimeIndex(periodEnd);
RandomVariableInterface stateVariableSum = this.getProcess().getStochasticDriver().getRandomVariableForConstant(0.0);
if(periodStartIndex < 0) {
periodStartIndex = -periodStartIndex-1;
if(periodStartIndex >= liborPeriodDiscretization.getNumberOfTimes()) {
throw new IllegalArgumentException();
}
RandomVariableInterface stateVariable = stateVariables[periodStartIndex-1];
double shortPeriodEnd = liborPeriodDiscretization.getTime(periodStartIndex);
double tenorRefinementWeight = getWeightForTenorRefinement(liborPeriodDiscretization.getTime(periodStartIndex-1), shortPeriodEnd, periodStart, shortPeriodEnd);
RandomVariableInterface integratedVariance = stateVariables[getNumberOfLibors()+periodStartIndex-1];
double tenor = covarianceModel.getScaledTenorTime(periodStart, shortPeriodEnd);
stateVariableSum = stateVariableSum.addProduct(stateVariable.addProduct(integratedVariance, tenorRefinementWeight), tenor);
}
if(periodEndIndex < 0) {
periodEndIndex = -periodEndIndex-1;
RandomVariableInterface stateVariable = stateVariables[periodEndIndex-1];
double shortPeriodStart = liborPeriodDiscretization.getTime(periodEndIndex-1);
double tenorRefinementWeight = getWeightForTenorRefinement(shortPeriodStart, liborPeriodDiscretization.getTime(periodEndIndex), shortPeriodStart, periodEnd);
RandomVariableInterface integratedVariance = stateVariables[getNumberOfLibors()+periodEndIndex-1];
double tenor = covarianceModel.getScaledTenorTime(shortPeriodStart, periodEnd);
stateVariableSum = stateVariableSum.addProduct(stateVariable.addProduct(integratedVariance, tenorRefinementWeight), tenor);
periodEndIndex--;
}
for(int periodIndex = periodStartIndex; periodIndex<periodEndIndex; periodIndex++) {
RandomVariableInterface stateVariable = stateVariables[periodIndex];
double tenor = covarianceModel.getScaledTenorTime(liborPeriodDiscretization.getTime(periodIndex), liborPeriodDiscretization.getTime(periodIndex+1));
stateVariableSum = stateVariableSum.addProduct(stateVariable, tenor);
}
double tenor = covarianceModel.getScaledTenorTime(periodStart, periodEnd);
stateVariableSum = stateVariableSum.div(tenor);
return stateVariableSum;
}
public RandomVariableInterface getLIBORForStateVariable(TimeDiscretizationInterface liborPeriodDiscretization, RandomVariableInterface[] stateVariables, double periodStart, double periodEnd) {
RandomVariableInterface stateVariable = getStateVariableForPeriod(liborPeriodDiscretization, stateVariables, periodStart, periodEnd);
stateVariable = stateVariable.mult(periodEnd-periodStart).add(Math.log(1+forwardRateCurve.getForward(null, periodStart)*(periodEnd-periodStart)));
RandomVariableInterface libor = stateVariable.exp().sub(1.0).div(periodEnd-periodStart);
return null;//libor;
}
public RandomVariableInterface getStateVariable(int timeIndex, double periodStart, double periodEnd)
{
// @TODO: Make getLiborPeriodDiscretization to use timeIndex
double time = this.getTimeDiscretization().getTime(timeIndex);
TimeDiscretizationInterface liborPeriodDiscretization = this.getLiborPeriodDiscretization(time);
// return getStateVariableForPeriod(liborPeriodDiscretization, stateVariables, periodStart, periodEnd);
int periodStartIndex = liborPeriodDiscretization.getTimeIndex(periodStart);
int periodEndIndex = liborPeriodDiscretization.getTimeIndex(periodEnd);
RandomVariableInterface stateVariableSum = null;
try {
stateVariableSum = this.getProcess().getStochasticDriver().getRandomVariableForConstant(0.0);
if(periodStartIndex < 0) {
periodStartIndex = -periodStartIndex-1;
if(periodStartIndex >= liborPeriodDiscretization.getNumberOfTimes()) {
throw new IllegalArgumentException();
}
RandomVariableInterface stateVariable = getProcessValue(timeIndex, periodStartIndex-1);
double shortPeriodEnd = liborPeriodDiscretization.getTime(periodStartIndex);
double tenorRefinementWeight = getWeightForTenorRefinement(liborPeriodDiscretization.getTime(periodStartIndex-1), shortPeriodEnd, periodStart, shortPeriodEnd);
RandomVariableInterface integratedVariance = getIntegratedVariance(timeIndex, liborPeriodDiscretization.getTime(periodStartIndex-1), liborPeriodDiscretization.getTime(periodStartIndex));
stateVariableSum = stateVariableSum.addProduct(stateVariable.addProduct(integratedVariance, tenorRefinementWeight), covarianceModel.getScaledTenorTime(periodStart, shortPeriodEnd));
}
if(periodEndIndex < 0) {
periodEndIndex = -periodEndIndex-1;
RandomVariableInterface stateVariable = getProcessValue(timeIndex, periodEndIndex-1);
double shortPeriodStart = liborPeriodDiscretization.getTime(periodEndIndex-1);
double tenorRefinementWeight = getWeightForTenorRefinement(shortPeriodStart, liborPeriodDiscretization.getTime(periodEndIndex), shortPeriodStart, periodEnd);
RandomVariableInterface integratedVariance = getIntegratedVariance(timeIndex, liborPeriodDiscretization.getTime(periodEndIndex-1), liborPeriodDiscretization.getTime(periodEndIndex));
stateVariableSum = stateVariableSum.addProduct(stateVariable.addProduct(integratedVariance, tenorRefinementWeight), covarianceModel.getScaledTenorTime(shortPeriodStart,periodEnd));
periodEndIndex--;
}
for(int periodIndex = periodStartIndex; periodIndex<periodEndIndex; periodIndex++) {
RandomVariableInterface stateVariable = getProcessValue(timeIndex, periodIndex);
stateVariableSum = stateVariableSum.addProduct(stateVariable, covarianceModel.getScaledTenorTime(liborPeriodDiscretization.getTime(periodIndex), liborPeriodDiscretization.getTime(periodIndex+1)));
}
stateVariableSum = stateVariableSum.div(covarianceModel.getScaledTenorTime(periodStart,periodEnd));
} catch (CalculationException e) {
}
return stateVariableSum;
}
@Override
public RandomVariableInterface getLIBOR(double time, double periodStart, double periodEnd) {
int timeIndex = getProcess().getTimeIndex(time);
// @TODO Improve interpolation in simulation time here, if required.
if(timeIndex < 0) timeIndex = -timeIndex-1-1;
return getLIBOR(timeIndex, periodStart, periodEnd);
}
public RandomVariableInterface getLIBOR(int timeIndex, double periodStart, double periodEnd)
{
RandomVariableInterface stateVariable = getStateVariable(timeIndex, periodStart, periodEnd);
double initialValue = Math.log(1+forwardRateCurve.getForward(curveModel, periodStart)*(forwardRateCurve.getPaymentOffset(periodStart))) / forwardRateCurve.getPaymentOffset(periodStart);
double tenorTime = covarianceModel.getScaledTenorTime(periodStart, periodEnd);
stateVariable = stateVariable.mult(tenorTime).add(initialValue*(periodEnd-periodStart));
RandomVariableInterface libor = stateVariable.exp().sub(1.0).div(periodEnd-periodStart);
return libor;
}
@Override
public int getNumberOfComponents() {
return 2 * this.getLiborPeriodDiscretization(0.0).getNumberOfTimeSteps();
}
public int getNumberOfLibors()
{
return this.getLiborPeriodDiscretization(0.0).getNumberOfTimeSteps();
}
@Override
public Object clone() {
throw new UnsupportedOperationException();
/*
try {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("measure", measure.name());
properties.put("stateSpace", stateSpace.name());
return new LIBORMarketModelWithTenorRefinement(getLiborPeriodDiscretization(), getForwardRateCurve(), getDiscountCurve(), covarianceModel, new CalibrationItem[0], properties);
} catch (CalculationException e) {
return null;
}
*/
}
@Override
public AnalyticModelInterface getAnalyticModel() {
return curveModel;
}
@Override
public DiscountCurveInterface getDiscountCurve() {
return discountCurve;
}
@Override
public ForwardCurveInterface getForwardRateCurve() {
return forwardRateCurve;
}
@Override
public TermStructureModelInterface getCloneWithModifiedData(Map<String, Object> dataModified) throws CalculationException {
CalibrationItem[] calibrationItems = null;
Map<String, ?> properties = null;
TermStructureCovarianceModelInterface covarianceModel = this.covarianceModel;
if(dataModified.containsKey("covarianceModel")) covarianceModel = (TermStructureCovarianceModelInterface)dataModified.get("covarianceModel");
return new LIBORMarketModelWithTenorRefinement(liborPeriodDiscretizations, numberOfDiscretizationIntervalls, curveModel, forwardRateCurve, discountCurve, covarianceModel, calibrationItems, properties);
}
/**
* Returns the term structure covariance model.
*
* @return the term structure covariance model.
*/
public TermStructureCovarianceModelInterface getCovarianceModel() {
return covarianceModel;
}
}