/*
* (c) Copyright Christian P. Fries, Germany. All rights reserved. Contact: email@christianfries.com.
*
* Created on 22.06.2014
*/
package net.finmath.marketdata.products;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import net.finmath.functions.AnalyticFormulas;
import net.finmath.marketdata.model.AnalyticModelInterface;
import net.finmath.marketdata.model.curves.DiscountCurveInterface;
import net.finmath.marketdata.model.curves.ForwardCurveInterface;
import net.finmath.marketdata.model.volatilities.CapletVolatilities;
import net.finmath.marketdata.model.volatilities.VolatilitySurfaceInterface;
import net.finmath.marketdata.model.volatilities.VolatilitySurfaceInterface.QuotingConvention;
import net.finmath.optimizer.GoldenSectionSearch;
import net.finmath.time.Period;
import net.finmath.time.Schedule;
import net.finmath.time.ScheduleInterface;
/**
* Implements the valuation of a cap via an analytic model,
* i.e. the specification of a forward curve, discount curve and volatility surface.
*
* A cap is a portfolio of Caplets with a common strike, i.e., the strike is the same for all Caplets.
*
* The class can value a caplet with a given strike or given moneyness. If moneyness is given,
* the class calculates the ATM forward. Note that this is done by omitting the first (fixed) period,
* see {@link #getATMForward(AnalyticModelInterface, boolean)}.
*
* Note: A fixing in arrears is not handled correctly since a convexity adjustment is currently not applied.
*
* @TODO: Support convexity adjustment if fixing is in arrears.
*
* @author Christian Fries
*/
public class Cap extends AbstractAnalyticProduct {
private final ScheduleInterface schedule;
private final String forwardCurveName;
private final double strike;
private final boolean isStrikeMoneyness;
private final String discountCurveName;
private final String volatiltiySufaceName;
private final VolatilitySurfaceInterface.QuotingConvention quotingConvention;
private transient double cachedATMForward = Double.NaN;
private transient SoftReference cacheStateModel;
private transient boolean cacheStateIsFirstPeriodIncluded;
/**
* Create a Caplet with a given schedule, strike on a given forward curve (by name)
* with a given discount curve and volatility surface (by name).
*
* The valuation is performed using analytic valuation formulas for the underlying caplets.
*
* @param schedule A given payment schedule, i.e., a collection of Periods with fixings, payments and period length.
* @param forwardCurveName The forward curve to be used for the forward of the index.
* @param strike The given strike (or moneyness).
* @param isStrikeMoneyness If true, then the strike argument is interpreted as moneyness, i.e. we calculate an ATM forward from the schedule.
* @param discountCurveName The discount curve to be used for discounting.
* @param volatilitySurfaceName The volatility surface to be used.
* @param quotingConvention The quoting convention of the value returned by the {@link #getValue}-method.
*/
public Cap(ScheduleInterface schedule, String forwardCurveName, double strike, boolean isStrikeMoneyness, String discountCurveName, String volatilitySurfaceName, QuotingConvention quotingConvention) {
super();
this.schedule = schedule;
this.forwardCurveName = forwardCurveName;
this.strike = strike;
this.isStrikeMoneyness = isStrikeMoneyness;
this.discountCurveName = discountCurveName;
this.volatiltiySufaceName = volatilitySurfaceName;
this.quotingConvention = quotingConvention;
}
/**
* Create a Caplet with a given schedule, strike on a given forward curve (by name)
* with a given discount curve and volatility surface (by name).
*
* The valuation is performed using analytic valuation formulas for the underlying caplets.
*
* @param schedule A given payment schedule, i.e., a collection of Periods with fixings, payments and period length.
* @param forwardCurveName The forward curve to be used for the forward of the index.
* @param strike The given strike (or moneyness).
* @param isStrikeMoneyness If true, then the strike argument is interpreted as moneyness, i.e. we calculate an ATM forward from the schedule.
* @param discountCurveName The discount curve to be used for discounting.
* @param volatilitySurfaceName The volatility surface to be used.
*/
public Cap(ScheduleInterface schedule, String forwardCurveName, double strike, boolean isStrikeMoneyness, String discountCurveName, String volatilitySurfaceName) {
this(schedule, forwardCurveName, strike, isStrikeMoneyness, discountCurveName, volatilitySurfaceName, QuotingConvention.PRICE);
}
@Override
public double getValue(double evaluationTime, AnalyticModelInterface model) {
if(quotingConvention == QuotingConvention.PRICE) return getValueAsPrice(evaluationTime, model);
else return getImpliedVolatility(evaluationTime, model, quotingConvention);
}
/**
* Returns the value of this product under the given model.
*
* @param evaluationTime Evaluation time.
* @param model The model.
* @return Value of this product und the given model.
*/
public double getValueAsPrice(double evaluationTime, AnalyticModelInterface model) {
ForwardCurveInterface forwardCurve = model.getForwardCurve(forwardCurveName);
DiscountCurveInterface discountCurve = model.getDiscountCurve(discountCurveName);
DiscountCurveInterface discountCurveForForward = null;
if(forwardCurve == null && forwardCurveName != null && forwardCurveName.length() > 0) {
// User might like to get forward from discount curve.
discountCurveForForward = model.getDiscountCurve(forwardCurveName);
if(discountCurveForForward == null) {
// User specified a name for the forward curve, but no curve was found.
throw new IllegalArgumentException("No curve of the name " + forwardCurveName + " was found in the model.");
}
}
double value = 0.0;
for(int periodIndex=0; periodIndex evaluationTime ? discountCurve.getDiscountFactor(model, paymentDate) : 0.0;
double payoffUnit = discountFactor * periodLength;
double effektiveStrike = strike;
if(isStrikeMoneyness) effektiveStrike += getATMForward(model, true);
VolatilitySurfaceInterface volatilitySurface = model.getVolatilitySurface(volatiltiySufaceName);
if(volatilitySurface == null) throw new IllegalArgumentException("Volatility surface not found in model: " + volatiltiySufaceName);
if(volatilitySurface.getQuotingConvention() == QuotingConvention.VOLATILITYLOGNORMAL) {
double volatility = volatilitySurface.getValue(model, fixingDate, effektiveStrike, VolatilitySurfaceInterface.QuotingConvention.VOLATILITYLOGNORMAL);
value += AnalyticFormulas.blackScholesGeneralizedOptionValue(forward, volatility, fixingDate, effektiveStrike, payoffUnit);
}
else {
// Default to normal volatility as quoting convention
double volatility = volatilitySurface.getValue(model, fixingDate, effektiveStrike, VolatilitySurfaceInterface.QuotingConvention.VOLATILITYNORMAL);
value += AnalyticFormulas.bachelierOptionValue(forward, volatility, fixingDate, effektiveStrike, payoffUnit);
}
}
return value / discountCurve.getDiscountFactor(model, evaluationTime);
}
/**
* Return the ATM forward for this cap.
* The ATM forward is the fixed rate K such that the value of the payoffs
* \( F(t_i) - K \)
* is zero, where \( F(t_i) \) is the ATM forward of the i-th caplet.
* Note however that the is a convention to determine the ATM forward of a cap
* from the payoffs excluding the first one. The reason here is that for non-forward starting
* cap, the first period is already fixed, i.e. it has no vega.
*
* @param model The model to retrieve the forward curve from (by name).
* @param isFirstPeriodIncluded If true, the forward will be determined by considering the periods after removal of the first periods (except, if the Cap consists only of 1 period).
* @return The ATM forward of this cap.
*/
public double getATMForward(AnalyticModelInterface model, boolean isFirstPeriodIncluded) {
if(!Double.isNaN(cachedATMForward) && cacheStateModel.get() == model && cacheStateIsFirstPeriodIncluded == isFirstPeriodIncluded) return cachedATMForward;
ScheduleInterface remainderSchedule = schedule;
if(!isFirstPeriodIncluded) {
ArrayList periods = new ArrayList();
periods.addAll(schedule.getPeriods());
if(periods.size() > 1) periods.remove(0);
remainderSchedule = new Schedule(schedule.getReferenceDate(), periods, schedule.getDaycountconvention());
}
SwapLeg floatLeg = new SwapLeg(remainderSchedule, forwardCurveName, 0.0, discountCurveName, false);
SwapLeg annuityLeg = new SwapLeg(remainderSchedule, null, 1.0, discountCurveName, false);
cachedATMForward = floatLeg.getValue(model) / annuityLeg.getValue(model);
cacheStateModel = new SoftReference(model);
cacheStateIsFirstPeriodIncluded = isFirstPeriodIncluded;
return cachedATMForward;
}
/**
* Returns the value of this cap in terms of an implied volatility (of a flat caplet surface).
*
* @param evaluationTime The evaluation time as double. Cash flows prior and including this time are not considered.
* @param model The model under which the product is valued.
* @param quotingConvention The quoting convention requested for the return value.
* @return The value of the product using the given model in terms of a implied volatility.
*/
public double getImpliedVolatility(double evaluationTime, AnalyticModelInterface model, VolatilitySurfaceInterface.QuotingConvention quotingConvention) {
double lowerBound = Double.MAX_VALUE;
double upperBound = -Double.MAX_VALUE;
for(int periodIndex=0; periodIndex maxAccuracy && !solver.isDone() && solver.getNumberOfIterations() < maxIterations) {
double volatility = solver.getNextPoint();
double[] maturities = { 1.0 };
double[] strikes = { 0.0 };
double[] volatilities = { volatility };
VolatilitySurfaceInterface flatSurface = new CapletVolatilities(model.getVolatilitySurface(volatiltiySufaceName).getName(), model.getVolatilitySurface(volatiltiySufaceName).getReferenceDate(), model.getForwardCurve(forwardCurveName), maturities, strikes, volatilities, quotingConvention, model.getDiscountCurve(discountCurveName));
AnalyticModelInterface flatModel = (AnalyticModelInterface)model.clone();
flatModel = flatModel.addVolatilitySurfaces(flatSurface);
double flatModelValue = this.getValueAsPrice(evaluationTime, flatModel);
double error = value-flatModelValue;
solver.setValue(error*error);
}
return solver.getBestPoint();
}
/**
* Returns the name of the forward curve references by this cap.
*
* @return the forward curve name.
*/
public String getForwardCurveName() {
return forwardCurveName;
}
/**
* Returns the strike of this caplet.
*
* @return the strike
*/
public double getStrike() {
return strike;
}
/**
* Returns the name of the discount curve referenced by this cap.
*
* @return the discount curve name
*/
public String getDiscountCurveName() {
return discountCurveName;
}
@Override
public String toString() {
return "Cap [schedule=" + schedule + ", forwardCurveName="
+ forwardCurveName + ", strike=" + strike
+ ", discountCurveName=" + discountCurveName
+ ", volatiltiySufaceName=" + volatiltiySufaceName + "]";
}
}
Ads help maintain this website.