/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.credit.options;
import org.apache.log4j.Logger;
import com.opengamma.analytics.financial.credit.index.CDSIndexCalculator;
import com.opengamma.analytics.financial.credit.index.IntrinsicIndexDataBundle;
import com.opengamma.analytics.financial.credit.index.PortfolioSwapAdjustment;
import com.opengamma.analytics.financial.credit.isdastandardmodel.AnnuityForSpreadFunction;
import com.opengamma.analytics.financial.credit.isdastandardmodel.AnnuityForSpreadISDAFunction;
import com.opengamma.analytics.financial.credit.isdastandardmodel.CDSAnalytic;
import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantYieldCurve;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.util.ArgumentChecker;
/**
*
*/
public class BlackIndexOptionPricer {
private static final Logger LOGGER = Logger.getLogger(BlackIndexOptionPricer.class.getName());
private final AnnuityForSpreadFunction _annuityFunc;
private final double _expiry;
private final double _coupon;
private final double _df;
private final double _minExercisePrice;
private final double _maxExercisePrice;
private final double _daFwdSpread;
private final double _fAnnuity;
/**
* Price options on CDS indices using an approximation where the forward annuity is `frozen' to today's value - this allows the use for the Black formula.
* @param fwdCDS Forward CDS - this represents the index (a CDSAnayltic which holds the cash flow details) at <b>the option expiry</b> - i.e. the 'trade date' of
* the CDS should be the option expiry and <b>not</b> today (where we are valuing the option)
* @param timeToExpiry time to expiry of the option
* @param yieldCurve The current yield curve
* @param indexCoupon The index coupon
* @param defaultAdjustedFwdSpread The default-adjusted forward spread (if not given extraneously, use {@link CDSIndexCalculator#defaultAdjustedForwardSpread} to calculate)
* @param pvFwdAnnuity The present value of the forward annuity of the underlying index (if not given extraneously, use {@link CDSIndexCalculator#indexAnnuity} to calculate this)
*/
public BlackIndexOptionPricer(final CDSAnalytic fwdCDS, final double timeToExpiry, final ISDACompliantYieldCurve yieldCurve, final double indexCoupon, final double defaultAdjustedFwdSpread,
final double pvFwdAnnuity) {
this(fwdCDS, timeToExpiry, yieldCurve, indexCoupon, new double[] {defaultAdjustedFwdSpread, pvFwdAnnuity });
}
/**
* Price options on CDS indices using an approximation where the forward annuity is `frozen' to today's value - this allows the use for the Black formula.
* @param fwdCDS Forward CDS - this represents the index (a CDSAnayltic which holds the cash flow details) at <b>the option expiry</b> - i.e. the 'trade date' of
* the CDS should be the option expiry and <b>not</b> today (where we are valuing the option)
* @param timeToExpiry time to expiry of the option
* @param yieldCurve The current yield curve
* @param indexCoupon The index coupon
* @param intrinsicData credit curves, weights and recovery rates of the intrinsic names. Usually these would have first been adjusted to match index prices (using {@link PortfolioSwapAdjustment#adjustCurves})
*/
public BlackIndexOptionPricer(final CDSAnalytic fwdCDS, final double timeToExpiry, final ISDACompliantYieldCurve yieldCurve, final double indexCoupon, final IntrinsicIndexDataBundle intrinsicData) {
this(fwdCDS, timeToExpiry, yieldCurve, indexCoupon, getFwdSpreadAndAnnuity(fwdCDS, timeToExpiry, yieldCurve, intrinsicData));
}
private BlackIndexOptionPricer(final CDSAnalytic fwdCDS, final double timeToExpiry, final ISDACompliantYieldCurve yieldCurve, final double indexCoupon, final double[] fwdSpreadAndAnnity) {
ArgumentChecker.notNull(fwdCDS, "fwdCDS");
ArgumentChecker.isTrue(timeToExpiry > 0.0, "timeToExpiry must be positive. Value given {}", timeToExpiry);
ArgumentChecker.isTrue(fwdCDS.getEffectiveProtectionStart() == 0.0, "fwdCDS should be a Forward CDS");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.isTrue(indexCoupon > 0.0, "indexCoupon must be positive");
ArgumentChecker.isTrue(fwdSpreadAndAnnity.length == 2, "too many parameters passed in");
final double defaultAdjustedFwdSpread = fwdSpreadAndAnnity[0];
final double pvFwdAnnuity = fwdSpreadAndAnnity[1];
ArgumentChecker.isTrue(defaultAdjustedFwdSpread > 0.0, "defaultAdjustedFwdSpread must be positive");
if (indexCoupon > 1) {
LOGGER.warn("Index Coupon should be given as a fraction; a value of " + indexCoupon + " is " + indexCoupon * 1e4 + "basis points.");
}
if (defaultAdjustedFwdSpread > 10) {
LOGGER.warn("defaultAdjustedFwdSpread should be given as a fraction; a value of " + defaultAdjustedFwdSpread + " is " + defaultAdjustedFwdSpread * 1e4 + "basis points.");
}
ArgumentChecker.isTrue(pvFwdAnnuity > 0, "pvFwdAnnuity must be positive");
ArgumentChecker.isTrue(pvFwdAnnuity < fwdCDS.getProtectionEnd() * 1.1, "Value of annuity of {} is greater than length (in years) of forward CDS. Annuity should be given for unit notional",
pvFwdAnnuity);
_annuityFunc = new AnnuityForSpreadISDAFunction(fwdCDS, yieldCurve.withOffset(timeToExpiry));
_expiry = timeToExpiry;
_coupon = indexCoupon;
_df = yieldCurve.getDiscountFactor(timeToExpiry + fwdCDS.getCashSettleTime());
_daFwdSpread = defaultAdjustedFwdSpread;
_fAnnuity = pvFwdAnnuity;
_minExercisePrice = -indexCoupon * _annuityFunc.evaluate(0.);
_maxExercisePrice = fwdCDS.getLGD();
}
private static double[] getFwdSpreadAndAnnuity(final CDSAnalytic fwdCDS, final double timeToExpiry, final ISDACompliantYieldCurve yieldCurve,
final IntrinsicIndexDataBundle intrinsicData) {
final CDSIndexCalculator indexCal = new CDSIndexCalculator();
final double[] res = new double[2];
final CDSAnalytic fwdStartCDS = fwdCDS.withOffset(timeToExpiry);
res[0] = indexCal.defaultAdjustedForwardSpread(fwdStartCDS, timeToExpiry, yieldCurve, intrinsicData);
res[1] = indexCal.indexAnnuity(fwdStartCDS, yieldCurve, intrinsicData);
return res;
}
/**
* Calculate the option premium (price per unit of notional)
* @see CDSIndexCalculator
* @param strike The option strike. This can be either given as the exercise price directly (ExerciseAmount) or as a spread (SpreadBasedStrike)
* @param vol The volatility of the default-adjusted forward spread
* @param isPayer true for payer and false for receiver option
* @return The option premium
*/
public double getOptionPremium(final IndexOptionStrike strike, final double vol, final boolean isPayer) {
ArgumentChecker.notNull(strike, "strike");
if (strike instanceof SpreadBasedStrike) {
return getOptionPriceForSpreadQuotedIndex(strike.amount(), vol, isPayer);
} else if (strike instanceof ExerciseAmount) {
return getOptionPriceForPriceQuotedIndex(strike.amount(), vol, isPayer);
} else {
throw new IllegalArgumentException("unknow strike type " + strike.getClass());
}
}
/**
* Price an option of a CDS index that is spread based (i.e. the strike is given as a spread).
* @param strike The strike as a spread
* @param vol The volatility of the default-adjusted forward spread
* @param isPayer true for payer, false for receiver
* @return The option price
*/
public double getOptionPriceForSpreadQuotedIndex(final double strike, final double vol, final boolean isPayer) {
ArgumentChecker.isTrue(strike >= 0.0, "strike cannot be negative");
final double gK = (strike - _coupon) * _annuityFunc.evaluate(strike); //the excise price
return getOptionPriceForPriceQuotedIndex(gK, vol, isPayer);
}
/**
* Price an option of a CDS index that is priced based (i.e. the exercise price is given directly).
* @param gK Exercise price
* @param vol The volatility of the default-adjusted forward spread
* @param isPayer true for payer, false for receiver
* @return The option price
*/
public double getOptionPriceForPriceQuotedIndex(final double gK, final double vol, final boolean isPayer) {
ArgumentChecker.isTrue(gK >= _minExercisePrice && gK < _maxExercisePrice, "The exercise price must be in the range {} to {} - value of {} is outside this", _minExercisePrice, _maxExercisePrice,
gK);
final double kMod = _coupon + gK * _df / _fAnnuity;
final double modBlackPrice = _fAnnuity * BlackFormulaRepository.price(_daFwdSpread, kMod, _expiry, vol, isPayer);
return modBlackPrice;
}
/**
* Get the implied volatility given a known option premium (price for unit notional)
* @param strike strike The option strike. This can be either given as the exercise price directly (ExerciseAmount) or as a spread (SpreadBasedStrike)
* @param optionPremium The option premium
* @param isPayer true for payer, false for receiver
* @return The implied volatility
*/
public double getImpliedVolatility(final IndexOptionStrike strike, final double optionPremium, final boolean isPayer) {
ArgumentChecker.notNull(strike, "strike");
if (strike instanceof SpreadBasedStrike) {
return getImpliedVolForSpreadStrike(strike.amount(), optionPremium, isPayer);
} else if (strike instanceof ExerciseAmount) {
return getImpliedVolForExercisePrice(strike.amount(), optionPremium, isPayer);
} else {
throw new IllegalArgumentException("unknow strike type " + strike.getClass());
}
}
/**
* Get the implied volatility given a known option premium (price for unit notional)
* @param strike The strike as a spread
* @param optionPremium The option premium
* @param isPayer true for payer, false for receiver
* @return The implied volatility
*/
public double getImpliedVolForSpreadStrike(final double strike, final double optionPremium, final boolean isPayer) {
final double gK = (strike - _coupon) * _annuityFunc.evaluate(strike); //the excise price
return getImpliedVolForExercisePrice(gK, optionPremium, isPayer);
}
/**
* Get the implied volatility given a known option premium (price for unit notional)
* @param gK Exercise price
* @param optionPremium The option premium
* @param isPayer true for payer, false for receiver
* @return The implied volatility
*/
public double getImpliedVolForExercisePrice(final double gK, final double optionPremium, final boolean isPayer) {
final double kMod = _coupon + gK * _df / _fAnnuity;
final double norPrice = optionPremium / _fAnnuity;
final double iv = BlackFormulaRepository.impliedVolatility(norPrice, _daFwdSpread, kMod, _expiry, isPayer);
return iv;
}
}