com.opengamma.strata.pricer.bond.DiscountingCapitalIndexedBondProductPricer Maven / Gradle / Ivy
/*
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.bond;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.MONTHS;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.function.Function;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeries;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.math.impl.rootfinding.BracketRoot;
import com.opengamma.strata.math.impl.rootfinding.BrentSingleRootFinder;
import com.opengamma.strata.math.impl.rootfinding.RealSingleRootFinder;
import com.opengamma.strata.pricer.CompoundedRateType;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.Security;
import com.opengamma.strata.product.bond.CapitalIndexedBondPaymentPeriod;
import com.opengamma.strata.product.bond.CapitalIndexedBondYieldConvention;
import com.opengamma.strata.product.bond.ResolvedCapitalIndexedBond;
import com.opengamma.strata.product.rate.InflationEndInterpolatedRateComputation;
import com.opengamma.strata.product.rate.InflationEndMonthRateComputation;
import com.opengamma.strata.product.rate.RateComputation;
/**
* Pricer for capital indexed bond products.
*
* This function provides the ability to price a {@link ResolvedCapitalIndexedBond}.
*
*
Price
* Strata uses decimal prices for bonds in the trade model, pricers and market data.
* For example, a price of 99.32% is represented in Strata by 0.9932.
*/
public class DiscountingCapitalIndexedBondProductPricer {
/**
* Default implementation.
*/
public static final DiscountingCapitalIndexedBondProductPricer DEFAULT =
new DiscountingCapitalIndexedBondProductPricer(DiscountingCapitalIndexedBondPaymentPeriodPricer.DEFAULT);
/**
* The root finder.
*/
private static final RealSingleRootFinder ROOT_FINDER = new BrentSingleRootFinder();
/**
* Brackets a root.
*/
private static final BracketRoot ROOT_BRACKETER = new BracketRoot();
/**
* Small parameter used in finite difference approximation.
*/
private static final double FD_EPS = 1.0e-5;
/**
* Pricer for {@link CapitalIndexedBondPaymentPeriod}.
*/
private final DiscountingCapitalIndexedBondPaymentPeriodPricer periodPricer;
/**
* Creates an instance.
*
* @param periodPricer the pricer for {@link CapitalIndexedBondPaymentPeriod}.
*/
public DiscountingCapitalIndexedBondProductPricer(DiscountingCapitalIndexedBondPaymentPeriodPricer periodPricer) {
this.periodPricer = ArgChecker.notNull(periodPricer, "periodPricer");
}
//-------------------------------------------------------------------------
/**
* Obtains the period pricer.
*
* @return the period pricer
*/
public DiscountingCapitalIndexedBondPaymentPeriodPricer getPeriodPricer() {
return periodPricer;
}
//-------------------------------------------------------------------------
/**
* Calculates the present value of the bond.
*
* The present value of the product is the value on the valuation date.
* The result is expressed using the payment currency of the bond.
*
* Coupon payments of the product are considered based on the valuation date.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @return the present value of the product
*/
public CurrencyAmount presentValue(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider) {
validate(ratesProvider, discountingProvider);
return presentValue(bond, ratesProvider, discountingProvider, ratesProvider.getValuationDate());
}
// calculate the present value
CurrencyAmount presentValue(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate referenceDate) {
IssuerCurveDiscountFactors issuerDf = issuerCurveDf(bond, discountingProvider);
double pvNominal = periodPricer.presentValue(bond.getNominalPayment(), ratesProvider, issuerDf);
double pvCoupon = 0d;
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if ((bond.hasExCouponPeriod() && period.getDetachmentDate().isAfter(referenceDate)) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(referenceDate))) {
pvCoupon += periodPricer.presentValue(period, ratesProvider, issuerDf);
}
}
return CurrencyAmount.of(bond.getCurrency(), pvCoupon + pvNominal);
}
/**
* Calculates the present value of the bond product with z-spread.
*
* The present value of the product is the value on the valuation date.
* The result is expressed using the payment currency of the bond.
*
* The z-spread is a parallel shift applied to continuously compounded rates or
* periodic compounded rates of the issuer discounting curve.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param zSpread the z-spread
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the present value of the bond product
*/
public CurrencyAmount presentValueWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
validate(ratesProvider, discountingProvider);
return presentValueWithZSpread(bond, ratesProvider, discountingProvider,
ratesProvider.getValuationDate(), zSpread, compoundedRateType, periodsPerYear);
}
// calculate the present value
CurrencyAmount presentValueWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate referenceDate,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
IssuerCurveDiscountFactors issuerDf = issuerCurveDf(bond, discountingProvider);
double pvNominal = periodPricer.presentValueWithZSpread(
bond.getNominalPayment(), ratesProvider, issuerDf, zSpread, compoundedRateType, periodsPerYear);
double pvCoupon = 0d;
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if ((bond.hasExCouponPeriod() && period.getDetachmentDate().isAfter(referenceDate)) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(referenceDate))) {
pvCoupon += periodPricer.presentValueWithZSpread(
period, ratesProvider, issuerDf, zSpread, compoundedRateType, periodsPerYear);
}
}
return CurrencyAmount.of(bond.getCurrency(), pvCoupon + pvNominal);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity of the bond product.
*
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @return the present value curve sensitivity of the product
*/
public PointSensitivityBuilder presentValueSensitivity(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider) {
validate(ratesProvider, discountingProvider);
return presentValueSensitivity(
bond, ratesProvider, discountingProvider, ratesProvider.getValuationDate());
}
/**
* Calculates the present value sensitivity of the bond product for the specified reference date.
*
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param referenceDate the reference date
* @return the present value curve sensitivity of the product
*/
public PointSensitivityBuilder presentValueSensitivity(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate referenceDate) {
IssuerCurveDiscountFactors issuerDf = issuerCurveDf(bond, discountingProvider);
PointSensitivityBuilder pointNominal =
periodPricer.presentValueSensitivity(bond.getNominalPayment(), ratesProvider, issuerDf);
PointSensitivityBuilder pointCoupon = PointSensitivityBuilder.none();
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if ((bond.hasExCouponPeriod() && period.getDetachmentDate().isAfter(referenceDate)) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(referenceDate))) {
pointCoupon = pointCoupon.combinedWith(
periodPricer.presentValueSensitivity(period, ratesProvider, issuerDf));
}
}
return pointNominal.combinedWith(pointCoupon);
}
/**
* Calculates the present value sensitivity of the bond product with z-spread.
*
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* The z-spread is a parallel shift applied to continuously compounded rates or
* periodic compounded rates of the issuer discounting curve.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param zSpread the z-spread
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the present value curve sensitivity of the product
*/
public PointSensitivityBuilder presentValueSensitivityWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
validate(ratesProvider, discountingProvider);
return presentValueSensitivityWithZSpread(bond, ratesProvider, discountingProvider,
ratesProvider.getValuationDate(), zSpread, compoundedRateType, periodsPerYear);
}
// calculate the present value sensitivity
PointSensitivityBuilder presentValueSensitivityWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate referenceDate,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
IssuerCurveDiscountFactors issuerDf = issuerCurveDf(bond, discountingProvider);
PointSensitivityBuilder pointNominal = periodPricer.presentValueSensitivityWithZSpread(
bond.getNominalPayment(), ratesProvider, issuerDf, zSpread, compoundedRateType, periodsPerYear);
PointSensitivityBuilder pointCoupon = PointSensitivityBuilder.none();
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if ((bond.hasExCouponPeriod() && period.getDetachmentDate().isAfter(referenceDate)) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(referenceDate))) {
pointCoupon = pointCoupon.combinedWith(periodPricer.presentValueSensitivityWithZSpread(
period, ratesProvider, issuerDf, zSpread, compoundedRateType, periodsPerYear));
}
}
return pointNominal.combinedWith(pointCoupon);
}
//-------------------------------------------------------------------------
/**
* Calculates the currency exposure of the bond product.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param referenceDate the reference date
* @return the currency exposure of the product
*/
public MultiCurrencyAmount currencyExposure(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate referenceDate) {
return MultiCurrencyAmount.of(presentValue(bond, ratesProvider, discountingProvider, referenceDate));
}
/**
* Calculates the currency exposure of the bond product with z-spread.
*
* The z-spread is a parallel shift applied to continuously compounded rates or
* periodic compounded rates of the issuer discounting curve.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param referenceDate the reference date
* @param zSpread the z-spread
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the currency exposure of the product
*/
public MultiCurrencyAmount currencyExposureWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate referenceDate,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
return MultiCurrencyAmount.of(presentValueWithZSpread(bond, ratesProvider, discountingProvider,
referenceDate, zSpread, compoundedRateType, periodsPerYear));
}
/**
* Calculates the current cash of the bond product.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @return the current cash of the product
*/
public CurrencyAmount currentCash(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate) {
LocalDate valuationDate = ratesProvider.getValuationDate();
Currency currency = bond.getCurrency();
CurrencyAmount currentCash = CurrencyAmount.zero(currency);
if (settlementDate.isBefore(valuationDate)) {
double cashCoupon = bond.hasExCouponPeriod() ?
0d :
currentCashPayment(bond, ratesProvider, valuationDate);
CapitalIndexedBondPaymentPeriod nominal = bond.getNominalPayment();
double cashNominal = nominal.getPaymentDate().isEqual(valuationDate) ?
periodPricer.forecastValue(nominal, ratesProvider) :
0d;
currentCash = currentCash.plus(CurrencyAmount.of(currency, cashCoupon + cashNominal));
}
return currentCash;
}
private double currentCashPayment(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate valuationDate) {
double cash = 0d;
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getPaymentDate().isEqual(valuationDate)) {
cash += periodPricer.forecastValue(period, ratesProvider);
}
}
return cash;
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty price of the bond security.
*
* The bond is represented as {@link Security} where standard ID of the bond is stored.
*
* Strata uses decimal prices for bonds. For example, a price of 99.32% is represented in Strata by 0.9932.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @return the dirty price of the bond security
*/
public double dirtyNominalPriceFromCurves(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
return dirtyNominalPriceFromCurves(bond, ratesProvider, discountingProvider, settlementDate);
}
/**
* Calculates the dirty price of the bond security for the specified settlement date.
*
* The bond is represented as {@link Security} where standard ID of the bond is stored.
*
* Strata uses decimal prices for bonds. For example, a price of 99.32% is represented in Strata by 0.9932.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param settlementDate the settlement date
* @return the dirty price of the bond security
*/
public double dirtyNominalPriceFromCurves(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate settlementDate) {
CurrencyAmount pv = presentValue(bond, ratesProvider, discountingProvider, settlementDate);
RepoCurveDiscountFactors repoDf = repoCurveDf(bond, discountingProvider);
double df = repoDf.discountFactor(settlementDate);
double notional = bond.getNotional();
return pv.getAmount() / (df * notional);
}
/**
* Calculates the dirty price of the bond security with z-spread.
*
* The bond is represented as {@link Security} where standard ID of the bond is stored.
*
* The z-spread is a parallel shift applied to continuously compounded rates or periodic
* compounded rates of the discounting curve.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @param zSpread the z-spread
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the dirty price of the bond security
*/
public double dirtyNominalPriceFromCurvesWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
return dirtyNominalPriceFromCurvesWithZSpread(
bond, ratesProvider, discountingProvider, settlementDate, zSpread, compoundedRateType, periodsPerYear);
}
// calculate the dirty price
double dirtyNominalPriceFromCurvesWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate settlementDate,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
CurrencyAmount pv = presentValueWithZSpread(
bond, ratesProvider, discountingProvider, settlementDate, zSpread, compoundedRateType, periodsPerYear);
RepoCurveDiscountFactors repoDf = repoCurveDf(bond, discountingProvider);
double df = repoDf.discountFactor(settlementDate);
double notional = bond.getNotional();
return pv.getAmount() / (df * notional);
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty price sensitivity of the bond security.
*
* The dirty price sensitivity of the security is the sensitivity of the dirty price value to
* the underlying curves.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @return the dirty price curve sensitivity of the security
*/
public PointSensitivityBuilder dirtyNominalPriceSensitivity(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
return dirtyNominalPriceSensitivity(bond, ratesProvider, discountingProvider, settlementDate);
}
/**
* Calculates the dirty price sensitivity of the bond security for the specified settlement date.
*
* The dirty price sensitivity of the security is the sensitivity of the dirty price value to
* the underlying curves.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param settlementDate the settlement date
* @return the dirty price curve sensitivity of the security
*/
public PointSensitivityBuilder dirtyNominalPriceSensitivity(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate settlementDate) {
double notional = bond.getNotional();
CurrencyAmount pv = presentValue(bond, ratesProvider, discountingProvider, settlementDate);
RepoCurveDiscountFactors repoDf = repoCurveDf(bond, discountingProvider);
double df = repoDf.discountFactor(settlementDate);
PointSensitivityBuilder pvSensi = presentValueSensitivity(
bond, ratesProvider, discountingProvider, settlementDate).multipliedBy(1d / (df * notional));
RepoCurveZeroRateSensitivity dfSensi =
repoDf.zeroRatePointSensitivity(settlementDate).multipliedBy(-pv.getAmount() / (df * df * notional));
return pvSensi.combinedWith(dfSensi);
}
/**
* Calculates the dirty price sensitivity of the bond security with z-spread.
*
* The dirty price sensitivity of the security is the sensitivity of the dirty price value to
* the underlying curves.
*
* The z-spread is a parallel shift applied to continuously compounded rates or periodic
* compounded rates of the discounting curve.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @param zSpread the z-spread
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the dirty price curve sensitivity of the security
*/
public PointSensitivityBuilder dirtyNominalPriceSensitivityWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
return dirtyNominalPriceSensitivityWithZSpread(
bond,
ratesProvider,
discountingProvider,
settlementDate,
zSpread,
compoundedRateType,
periodsPerYear);
}
// calculate the dirty price sensitivity
PointSensitivityBuilder dirtyNominalPriceSensitivityWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
LocalDate settlementDate,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
double notional = bond.getNotional();
CurrencyAmount pv = presentValueWithZSpread(
bond, ratesProvider, discountingProvider, settlementDate, zSpread, compoundedRateType,
periodsPerYear);
RepoCurveDiscountFactors repoDf = repoCurveDf(bond, discountingProvider);
double df = repoDf.discountFactor(settlementDate);
PointSensitivityBuilder pvSensi = presentValueSensitivityWithZSpread(bond, ratesProvider, discountingProvider,
settlementDate, zSpread, compoundedRateType, periodsPerYear).multipliedBy(1d / (df * notional));
RepoCurveZeroRateSensitivity dfSensi =
repoDf.zeroRatePointSensitivity(settlementDate).multipliedBy(-pv.getAmount() / df / df / notional);
return pvSensi.combinedWith(dfSensi);
}
//-------------------------------------------------------------------------
/**
* Computes the dirty price from the conventional real yield.
*
* The resulting dirty price is real price or nominal price depending on the yield convention.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the yield
* @return the dirty price of the product
*/
public double dirtyPriceFromRealYield(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
ArgChecker.isTrue(settlementDate.isBefore(bond.getUnadjustedEndDate()),
"settlement date must be before end date");
int periodIndex = bond.findPeriodIndex(settlementDate)
.orElseThrow(() -> new IllegalArgumentException("Date outside range of bond"));
CapitalIndexedBondPaymentPeriod period = bond.getPeriodicPayments().get(periodIndex);
int nbCoupon = bond.getPeriodicPayments().size() - periodIndex;
double couponPerYear = bond.getFrequency().eventsPerYear();
CapitalIndexedBondYieldConvention yieldConvention = bond.getYieldConvention();
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.US_IL_REAL)) {
double pvAtFirstCoupon;
double cpnRate = bond.getPeriodicPayments().get(0).getRealCoupon();
if (Math.abs(yield) > 1.0E-8) {
double factorOnPeriod = 1d + yield / couponPerYear;
double vn = Math.pow(factorOnPeriod, 1 - nbCoupon);
pvAtFirstCoupon = cpnRate * couponPerYear / yield * (factorOnPeriod - vn) + vn;
} else {
pvAtFirstCoupon = cpnRate * nbCoupon + 1d;
}
return pvAtFirstCoupon / (1d + factorToNextCoupon(bond, settlementDate) * yield / couponPerYear);
}
double realRate = period.getRealCoupon();
double firstYearFraction = bond.yearFraction(period.getUnadjustedStartDate(), period.getUnadjustedEndDate());
double v = 1d / (1d + yield / couponPerYear);
double rs = ratioPeriodToNextCoupon(period, settlementDate);
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.GB_IL_FLOAT)) {
RateComputation obs = period.getRateComputation();
LocalDateDoubleTimeSeries ts = ratesProvider.priceIndexValues(bond.getRateCalculation().getIndex()).getFixings();
YearMonth lastKnownFixingMonth = YearMonth.from(ts.getLatestDate());
double indexRatio = ts.getLatestValue() / bond.getFirstIndexValue();
YearMonth endFixingMonth = null;
if (obs instanceof InflationEndInterpolatedRateComputation) {
endFixingMonth = ((InflationEndInterpolatedRateComputation) obs).getEndSecondObservation().getFixingMonth();
} else if (obs instanceof InflationEndMonthRateComputation) {
endFixingMonth = ((InflationEndMonthRateComputation) obs).getEndObservation().getFixingMonth();
} else {
throw new IllegalArgumentException("The rate observation " + obs.toString() + " is not supported.");
}
double nbMonth = Math.abs(MONTHS.between(endFixingMonth, lastKnownFixingMonth));
double u = Math.sqrt(1d / 1.03);
double a = indexRatio * Math.pow(u, nbMonth / 6d);
if (nbCoupon == 1) {
return (realRate + 1d) * a / u * Math.pow(u * v, rs);
} else {
double firstCashFlow = firstYearFraction * realRate * indexRatio * couponPerYear;
CapitalIndexedBondPaymentPeriod secondPeriod = bond.getPeriodicPayments().get(periodIndex + 1);
double secondYearFraction = bond.yearFraction(secondPeriod.getUnadjustedStartDate(), secondPeriod.getUnadjustedEndDate());
double secondCashFlow = secondYearFraction * realRate * indexRatio * couponPerYear;
double vn = Math.pow(v, nbCoupon - 1);
double pvAtFirstCoupon =
firstCashFlow + secondCashFlow * u * v + a * realRate * v * v * (1d - vn / v) / (1d - v) + a * vn;
return pvAtFirstCoupon * Math.pow(u * v, rs);
}
}
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.GB_IL_BOND)) {
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
double firstCashFlow = realRate * indexRatio * firstYearFraction * couponPerYear;
if (nbCoupon == 1) {
return Math.pow(v, rs) * (firstCashFlow + 1d);
} else {
CapitalIndexedBondPaymentPeriod secondPeriod = bond.getPeriodicPayments().get(periodIndex + 1);
double secondYearFraction = bond.yearFraction(secondPeriod.getUnadjustedStartDate(), secondPeriod.getUnadjustedEndDate());
double secondCashFlow = realRate * indexRatio * secondYearFraction * couponPerYear;
double vn = Math.pow(v, nbCoupon - 1);
double pvAtFirstCoupon = firstCashFlow + secondCashFlow * v + realRate * v * v * (1d - vn / v) / (1d - v) + vn;
return pvAtFirstCoupon * Math.pow(v, rs);
}
}
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.JP_IL_SIMPLE)) {
LocalDate maturityDate = bond.getEndDate();
double maturity = bond.yearFraction(settlementDate, maturityDate);
double cleanPrice = (1d + realRate * couponPerYear * maturity) / (1d + yield * maturity);
return dirtyRealPriceFromCleanRealPrice(bond, settlementDate, cleanPrice);
}
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.JP_IL_COMPOUND)) {
double pvAtFirstCoupon = 0d;
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
CapitalIndexedBondPaymentPeriod paymentPeriod = bond.getPeriodicPayments().get(loopcpn + periodIndex);
pvAtFirstCoupon += paymentPeriod.getRealCoupon() * Math.pow(v, loopcpn);
}
pvAtFirstCoupon += Math.pow(v, nbCoupon - 1);
double factorToNext = factorToNextCoupon(bond, settlementDate);
return pvAtFirstCoupon * Math.pow(v, factorToNext);
}
throw new IllegalArgumentException(
"The convention " + bond.getYieldConvention().toString() + " is not supported.");
}
/**
* Computes the dirty price from the conventional real yield and its derivative wrt the yield.
*
* The resulting dirty price is real price or nominal price depending on the yield convention.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the yield
* @return the dirty price of the product and its derivative
*/
public ValueDerivatives dirtyPriceFromRealYieldAd(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
ArgChecker.isTrue(settlementDate.isBefore(bond.getUnadjustedEndDate()),
"settlement date must be before end date");
int periodIndex = bond.findPeriodIndex(settlementDate)
.orElseThrow(() -> new IllegalArgumentException("Date outside range of bond"));
CapitalIndexedBondPaymentPeriod period = bond.getPeriodicPayments().get(periodIndex);
int nbCoupon = bond.getPeriodicPayments().size() - periodIndex;
double couponPerYear = bond.getFrequency().eventsPerYear();
CapitalIndexedBondYieldConvention yieldConvention = bond.getYieldConvention();
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.US_IL_REAL)) {
double pvAtFirstCoupon;
double pvAtFirstCouponDeriv;
double cpnRate = bond.getPeriodicPayments().get(0).getRealCoupon();
if (Math.abs(yield) > 1.0E-8) {
double factorOnPeriod = 1d + yield / couponPerYear;
double factorOnPeriodDeriv = 1d / couponPerYear;
double vn = Math.pow(factorOnPeriod, 1 - nbCoupon);
double vnDeriv = (1d - nbCoupon) * Math.pow(factorOnPeriod, -nbCoupon) * factorOnPeriodDeriv;
pvAtFirstCoupon = cpnRate * couponPerYear / yield * (factorOnPeriod - vn) + vn;
pvAtFirstCouponDeriv = -cpnRate * couponPerYear / yield / yield * (factorOnPeriod - vn) +
cpnRate * couponPerYear / yield * (factorOnPeriodDeriv - vnDeriv) + vnDeriv;
} else {
pvAtFirstCoupon = cpnRate * nbCoupon + 1d;
pvAtFirstCouponDeriv = (1d - nbCoupon) / couponPerYear + 0.5 * (1d - nbCoupon) * nbCoupon * cpnRate / couponPerYear;
}
double den = 1d + factorToNextCoupon(bond, settlementDate) * yield / couponPerYear;
double denDeriv = factorToNextCoupon(bond, settlementDate) / couponPerYear;
double price = pvAtFirstCoupon / den;
double priceDeriv = pvAtFirstCouponDeriv / den - pvAtFirstCoupon / den / den * denDeriv;
return ValueDerivatives.of(price, DoubleArray.of(priceDeriv));
}
double realRate = period.getRealCoupon();
double firstYearFraction = bond.yearFraction(period.getUnadjustedStartDate(), period.getUnadjustedEndDate());
double v = 1d / (1d + yield / couponPerYear);
double vDeriv = -v * v / couponPerYear;
double rs = ratioPeriodToNextCoupon(period, settlementDate);
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.GB_IL_FLOAT)) {
RateComputation obs = period.getRateComputation();
LocalDateDoubleTimeSeries ts = ratesProvider.priceIndexValues(bond.getRateCalculation().getIndex()).getFixings();
YearMonth lastKnownFixingMonth = YearMonth.from(ts.getLatestDate());
double indexRatio = ts.getLatestValue() / bond.getFirstIndexValue();
YearMonth endFixingMonth = null;
if (obs instanceof InflationEndInterpolatedRateComputation) {
endFixingMonth = ((InflationEndInterpolatedRateComputation) obs).getEndSecondObservation().getFixingMonth();
} else if (obs instanceof InflationEndMonthRateComputation) {
endFixingMonth = ((InflationEndMonthRateComputation) obs).getEndObservation().getFixingMonth();
} else {
throw new IllegalArgumentException("The rate observation " + obs.toString() + " is not supported.");
}
double nbMonth = Math.abs(MONTHS.between(endFixingMonth, lastKnownFixingMonth));
double u = Math.sqrt(1d / 1.03);
double a = indexRatio * Math.pow(u, nbMonth / 6d);
if (nbCoupon == 1) {
double price = (realRate + 1d) * a / u * Math.pow(u * v, rs);
double priceDeriv = (realRate + 1d) * a / u * Math.pow(u * v, rs - 1d) * rs * u * vDeriv;
return ValueDerivatives.of(price, DoubleArray.of(priceDeriv));
} else {
double firstCashFlow = firstYearFraction * realRate * indexRatio * couponPerYear;
CapitalIndexedBondPaymentPeriod secondPeriod = bond.getPeriodicPayments().get(periodIndex + 1);
double secondYearFraction = bond.yearFraction(secondPeriod.getUnadjustedStartDate(), secondPeriod.getUnadjustedEndDate());
double secondCashFlow = secondYearFraction * realRate * indexRatio * couponPerYear;
double vn = Math.pow(v, nbCoupon - 1);
double vnDeriv = (nbCoupon - 1d) * Math.pow(v, nbCoupon - 2) * vDeriv;
double pvAtFirstCoupon =
firstCashFlow + secondCashFlow * u * v + a * realRate * v * v * (1d - vn / v) / (1d - v) + a * vn;
double pvAtFirstCouponDeriv =
secondCashFlow * u * vDeriv + a * vnDeriv +
2d * a * realRate * v * vDeriv * (1d - vn / v) / (1d - v) -
a * realRate * v * vnDeriv / (1d - v) +
a * realRate * vn * vDeriv / (1d - v) +
a * realRate * v * v * (1d - vn / v) / Math.pow(1d - v, 2) * vDeriv;
double price = pvAtFirstCoupon * Math.pow(u * v, rs);
double priceDeriv = pvAtFirstCouponDeriv * Math.pow(u * v, rs) +
rs * u * vDeriv * pvAtFirstCoupon * Math.pow(u * v, rs - 1);
return ValueDerivatives.of(price, DoubleArray.of(priceDeriv));
}
}
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.GB_IL_BOND)) {
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
double firstCashFlow = realRate * indexRatio * firstYearFraction * couponPerYear;
if (nbCoupon == 1) {
double price = Math.pow(v, rs) * (firstCashFlow + 1d);
double priceDeriv = rs * vDeriv * Math.pow(v, rs - 1d) * (firstCashFlow + 1d);
return ValueDerivatives.of(price, DoubleArray.of(priceDeriv));
} else {
CapitalIndexedBondPaymentPeriod secondPeriod = bond.getPeriodicPayments().get(periodIndex + 1);
double secondYearFraction = bond.yearFraction(secondPeriod.getUnadjustedStartDate(), secondPeriod.getUnadjustedEndDate());
double secondCashFlow = realRate * indexRatio * secondYearFraction * couponPerYear;
double vn = Math.pow(v, nbCoupon - 1);
double vnDeriv = (nbCoupon - 1d) * vDeriv * Math.pow(v, nbCoupon - 2);
double pvAtFirstCoupon = firstCashFlow + secondCashFlow * v + realRate * v * v * (1d - vn / v) / (1d - v) + vn;
double pvAtFirstCouponDeriv = secondCashFlow * vDeriv + vnDeriv +
2d * realRate * v * vDeriv * (1d - vn / v) / (1d - v) -
realRate * v * vnDeriv / (1d - v) +
realRate * vDeriv * vn / (1d - v) +
realRate * v * v * vDeriv * (1d - vn / v) / Math.pow(1d - v, 2);
double price = pvAtFirstCoupon * Math.pow(v, rs);
double priceDeriv = pvAtFirstCouponDeriv * Math.pow(v, rs) + pvAtFirstCoupon * Math.pow(v, rs - 1) * vDeriv * rs;
return ValueDerivatives.of(price, DoubleArray.of(priceDeriv));
}
}
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.JP_IL_SIMPLE)) {
LocalDate maturityDate = bond.getEndDate();
double maturity = bond.yearFraction(settlementDate, maturityDate);
double cleanPrice = (1d + realRate * couponPerYear * maturity) / (1d + yield * maturity);
double cleanPriceDeriv = -cleanPrice * maturity / (1d + yield * maturity);
double price = dirtyRealPriceFromCleanRealPrice(bond, settlementDate, cleanPrice);
return ValueDerivatives.of(price, DoubleArray.of(cleanPriceDeriv));
}
if (yieldConvention.equals(CapitalIndexedBondYieldConvention.JP_IL_COMPOUND)) {
double pvAtFirstCoupon = 0d;
double pvAtFirstCouponDeriv = 0d;
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
CapitalIndexedBondPaymentPeriod paymentPeriod = bond.getPeriodicPayments().get(loopcpn + periodIndex);
pvAtFirstCoupon += paymentPeriod.getRealCoupon() * Math.pow(v, loopcpn);
pvAtFirstCouponDeriv += paymentPeriod.getRealCoupon() * loopcpn * Math.pow(v, loopcpn - 1) * vDeriv;
}
pvAtFirstCoupon += Math.pow(v, nbCoupon - 1);
pvAtFirstCouponDeriv += (nbCoupon - 1) * Math.pow(v, nbCoupon - 2) * vDeriv;
double factorToNext = factorToNextCoupon(bond, settlementDate);
double price = pvAtFirstCoupon * Math.pow(v, factorToNext);
double priceDeriv = pvAtFirstCouponDeriv * Math.pow(v, factorToNext) +
pvAtFirstCoupon * factorToNext * Math.pow(v, factorToNext - 1d) * vDeriv;
return ValueDerivatives.of(price, DoubleArray.of(priceDeriv));
}
throw new IllegalArgumentException(
"The convention " + bond.getYieldConvention().toString() + " is not supported.");
}
/**
* Computes the clean price from the conventional real yield.
*
* The resulting clean price is real price or nominal price depending on the yield convention.
*
* The input yield and output are expressed in fraction.
*
* Strata uses decimal prices for bonds. For example, a price of 99.32% is represented in Strata by 0.9932.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the yield
* @return the clean price of the product
*/
public double cleanPriceFromRealYield(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
double dirtyPrice = dirtyPriceFromRealYield(bond, ratesProvider, settlementDate, yield);
return cleanRealPriceFromDirtyRealPrice(bond, settlementDate, dirtyPrice);
}
/**
* Computes the conventional real yield from the dirty price.
*
* The input dirty price should be real price or nominal price depending on the yield convention. This is coherent to
* the implementation of {@link #dirtyPriceFromRealYield(ResolvedCapitalIndexedBond, RatesProvider, LocalDate, double)}.
*
* The input price and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param dirtyPrice the bond dirty price
* @return the yield of the product
*/
public double realYieldFromDirtyPrice(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double dirtyPrice) {
final Function priceResidual = new Function() {
@Override
public Double apply(Double y) {
return dirtyPriceFromRealYield(bond, ratesProvider, settlementDate, y) - dirtyPrice;
}
};
double[] range = ROOT_BRACKETER.getBracketedPoints(priceResidual, -0.05, 0.10);
double yield = ROOT_FINDER.getRoot(priceResidual, range[0], range[1]);
return yield;
}
/**
* Computes the conventional real yield from the dirty price and its derivaitve wrt the dirty price.
*
* The input dirty price should be real price or nominal price depending on the yield convention. This is coherent to
* the implementation of {@link #dirtyPriceFromRealYield(ResolvedCapitalIndexedBond, RatesProvider, LocalDate, double)}.
*
* The input price and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param dirtyPrice the bond dirty price
* @return the yield of the product and its derivative
*/
public ValueDerivatives realYieldFromDirtyPriceAd(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double dirtyPrice) {
final Function priceResidual = new Function() {
@Override
public Double apply(Double y) {
return dirtyPriceFromRealYield(bond, ratesProvider, settlementDate, y) - dirtyPrice;
}
};
double[] range = ROOT_BRACKETER.getBracketedPoints(priceResidual, -0.05, 0.10);
double yield = ROOT_FINDER.getRoot(priceResidual, range[0], range[1]);
ValueDerivatives priceDYield = dirtyPriceFromRealYieldAd(bond, ratesProvider, settlementDate, yield);
return ValueDerivatives.of(yield, DoubleArray.of(1.0 / priceDYield.getDerivative(0)));
}
/**
* Computes the conventional real yield from the curves.
*
* The yield is in the bill yield convention.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @return the yield of the product
*/
public double realYieldFromCurves(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
double dirtyNominalPrice =
dirtyNominalPriceFromCurves(bond, ratesProvider, discountingProvider, settlementDate);
double dirtyRealPrice = realPriceFromNominalPrice(bond, ratesProvider, settlementDate, dirtyNominalPrice);
return realYieldFromDirtyPrice(bond, ratesProvider, settlementDate, dirtyRealPrice);
}
/**
* Computes the dirty price from the standard yield.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the standard yield
* @return the dirty price of the product
*/
public double dirtyPriceFromStandardYield(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
int nbCoupon = bond.getPeriodicPayments().size();
double couponPerYear = bond.getFrequency().eventsPerYear();
double factorOnPeriod = 1d + yield / couponPerYear;
double pvAtFirstCoupon = 0d;
int pow = 0;
double factorToNext = factorToNextCoupon(bond, settlementDate);
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
CapitalIndexedBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
if ((bond.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
pvAtFirstCoupon += period.getRealCoupon() / Math.pow(factorOnPeriod, pow);
++pow;
}
}
pvAtFirstCoupon += 1d / Math.pow(factorOnPeriod, pow - 1);
return pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNext);
}
//-------------------------------------------------------------------------
/**
* Calculates the modified duration from the conventional real yield using finite difference approximation.
*
* The modified duration is defined as the minus of the first derivative of clean price with respect to yield,
* divided by the clean price.
*
* The clean price here is real price or nominal price depending on the yield convention. This is coherent to
* the implementation of {@link #dirtyPriceFromRealYield(ResolvedCapitalIndexedBond, RatesProvider, LocalDate, double)}.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the yield
* @return the modified duration of the product
*/
public double modifiedDurationFromRealYieldFiniteDifference(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
double price = cleanPriceFromRealYield(bond, ratesProvider, settlementDate, yield);
double priceplus = cleanPriceFromRealYield(bond, ratesProvider, settlementDate, yield + FD_EPS);
double priceminus = cleanPriceFromRealYield(bond, ratesProvider, settlementDate, yield - FD_EPS);
return -0.5 * (priceplus - priceminus) / (price * FD_EPS);
}
/**
* Calculates the convexity from the conventional real yield using finite difference approximation.
*
* The convexity is defined as the second derivative of clean price with respect to yield, divided by the clean price.
*
* The clean price here is real price or nominal price depending on the yield convention. This is coherent to
* the implementation of {@link #dirtyPriceFromRealYield(ResolvedCapitalIndexedBond, RatesProvider, LocalDate, double)}.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the yield
* @return the covexity of the product
*/
public double convexityFromRealYieldFiniteDifference(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
double price = cleanPriceFromRealYield(bond, ratesProvider, settlementDate, yield);
double priceplus = cleanPriceFromRealYield(bond, ratesProvider, settlementDate, yield + FD_EPS);
double priceminus = cleanPriceFromRealYield(bond, ratesProvider, settlementDate, yield - FD_EPS);
return (priceplus - 2 * price + priceminus) / (price * FD_EPS * FD_EPS);
}
/**
* Computes the modified duration from the standard yield.
*
* The modified duration is defined as the minus of the first derivative of dirty price with respect to yield,
* divided by the dirty price.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the standard yield
* @return the modified duration of the product
*/
public double modifiedDurationFromStandardYield(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
int nbCoupon = bond.getPeriodicPayments().size();
double couponPerYear = bond.getFrequency().eventsPerYear();
double factorOnPeriod = 1d + yield / couponPerYear;
double mdAtFirstCoupon = 0d;
double pvAtFirstCoupon = 0d;
int pow = 0;
double factorToNext = factorToNextCoupon(bond, settlementDate);
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
CapitalIndexedBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
if ((bond.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
mdAtFirstCoupon += period.getRealCoupon() / Math.pow(factorOnPeriod, pow + 1) *
(pow + factorToNext) / couponPerYear;
pvAtFirstCoupon += period.getRealCoupon() / Math.pow(factorOnPeriod, pow);
++pow;
}
}
mdAtFirstCoupon += (pow - 1d + factorToNext) / (couponPerYear * Math.pow(factorOnPeriod, pow));
pvAtFirstCoupon += 1d / Math.pow(factorOnPeriod, pow - 1);
double dp = pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNext);
double md = mdAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNext) / dp;
return md;
}
/**
* Computes the covexity from the standard yield.
*
* The convexity is defined as the second derivative of dirty price with respect to yield, divided by the dirty price.
*
* The input yield and output are expressed in fraction.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param yield the standard yield
* @return the convexity of the product
*/
public double convexityFromStandardYield(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double yield) {
int nbCoupon = bond.getPeriodicPayments().size();
double couponPerYear = bond.getFrequency().eventsPerYear();
double factorOnPeriod = 1d + yield / couponPerYear;
double cvAtFirstCoupon = 0;
double pvAtFirstCoupon = 0;
int pow = 0;
double factorToNext = factorToNextCoupon(bond, settlementDate);
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
CapitalIndexedBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
if ((bond.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!bond.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
cvAtFirstCoupon += period.getRealCoupon() * (pow + factorToNext) * (pow + factorToNext + 1d) /
(Math.pow(factorOnPeriod, pow + 2) * couponPerYear * couponPerYear);
pvAtFirstCoupon += period.getRealCoupon() / Math.pow(factorOnPeriod, pow);
++pow;
}
}
cvAtFirstCoupon += (pow - 1d + factorToNext) *
(pow + factorToNext) / (Math.pow(factorOnPeriod, pow + 1) * couponPerYear * couponPerYear);
pvAtFirstCoupon += 1d / Math.pow(factorOnPeriod, pow - 1);
double pv = pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNext);
double cv = cvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNext) / pv;
return cv;
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty real price of the bond from its settlement date and clean real price.
*
* @param bond the product
* @param settlementDate the settlement date
* @param cleanPrice the clean real price
* @return the price of the bond product
*/
public double dirtyRealPriceFromCleanRealPrice(
ResolvedCapitalIndexedBond bond,
LocalDate settlementDate,
double cleanPrice) {
double notional = bond.getNotional();
return cleanPrice + bond.accruedInterest(settlementDate) / notional;
}
/**
* Calculates the clean real price of the bond from its settlement date and dirty real price.
*
* @param bond the product
* @param settlementDate the settlement date
* @param dirtyPrice the dirty real price
* @return the price of the bond product
*/
public double cleanRealPriceFromDirtyRealPrice(
ResolvedCapitalIndexedBond bond,
LocalDate settlementDate,
double dirtyPrice) {
double notional = bond.getNotional();
return dirtyPrice - bond.accruedInterest(settlementDate) / notional;
}
/**
* Calculates the dirty nominal price of the bond from its settlement date and clean nominal price.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param cleanPrice the clean nominal price
* @return the price of the bond product
*/
public double dirtyNominalPriceFromCleanNominalPrice(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double cleanPrice) {
double notional = bond.getNotional();
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
return cleanPrice + bond.accruedInterest(settlementDate) / notional * indexRatio;
}
/**
* Calculates the clean nominal price of the bond from its settlement date and dirty nominal price.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param dirtyPrice the dirty nominal price
* @return the price of the bond product
*/
public double cleanNominalPriceFromDirtyNominalPrice(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double dirtyPrice) {
double notional = bond.getNotional();
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
return dirtyPrice - bond.accruedInterest(settlementDate) / notional * indexRatio;
}
/**
* Calculates the real price of the bond from its settlement date and nominal price.
*
* The input and output prices are both clean or dirty.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param nominalPrice the nominal price
* @return the price of the bond product
*/
public double realPriceFromNominalPrice(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double nominalPrice) {
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
return nominalPrice / indexRatio;
}
/**
* Calculates the real price of the bond from its settlement date and nominal price
* and its derivative wrt the nominal price.
*
* The input and output prices are both clean or dirty.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param nominalPrice the nominal price
* @return the price of the bond product and its derivative
*/
public ValueDerivatives realPriceFromNominalPriceAd(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double nominalPrice) {
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
return ValueDerivatives.of(nominalPrice / indexRatio, DoubleArray.of(1d / indexRatio));
}
/**
* Calculates the nominal price of the bond from its settlement date and real price.
*
* The input and output prices are both clean or dirty.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param settlementDate the settlement date
* @param realPrice the real price
* @return the price of the bond product
*/
public double nominalPriceFromRealPrice(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate,
double realPrice) {
double indexRatio = indexRatio(bond, ratesProvider, settlementDate);
return realPrice * indexRatio;
}
//-------------------------------------------------------------------------
/**
* Calculates the z-spread of the bond from curves and clean price.
*
* The input clean price is real price or nominal price depending on the yield convention.
*
* The z-spread is a parallel shift applied to continuously compounded rates or periodic
* compounded rates of the discounting curve associated to the bond (Issuer Entity)
* to match the present value.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @param cleanPrice the clean price
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the z-spread of the bond security
*/
public double zSpreadFromCurvesAndCleanPrice(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData,
double cleanPrice,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
final Function residual = new Function() {
@Override
public Double apply(Double z) {
double dirtyPrice = dirtyNominalPriceFromCurvesWithZSpread(
bond,
ratesProvider,
discountingProvider,
settlementDate,
z,
compoundedRateType,
periodsPerYear);
double dirtyRealPrice = realPriceFromNominalPrice(bond, ratesProvider, settlementDate, dirtyPrice);
return cleanRealPriceFromDirtyRealPrice(bond, settlementDate, dirtyRealPrice) - cleanPrice;
}
};
double[] range = ROOT_BRACKETER.getBracketedPoints(residual, -0.5, 0.5); // Starting range is [-1%, 1%]
return ROOT_FINDER.getRoot(residual, range[0], range[1]);
}
/**
* Calculates the z-spread of the bond from curves and present value.
*
* The z-spread is a parallel shift applied to continuously compounded rates or periodic
* compounded rates of the discounting curve associated to the bond (Issuer Entity)
* to match the present value.
*
* @param bond the product
* @param ratesProvider the rates provider, used to determine price index values
* @param discountingProvider the discount factors provider
* @param refData the reference data used to calculate the settlement date
* @param presentValue the present value
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the z-spread of the bond product
*/
public double zSpreadFromCurvesAndPv(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LegalEntityDiscountingProvider discountingProvider,
ReferenceData refData,
CurrencyAmount presentValue,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
validate(ratesProvider, discountingProvider);
LocalDate settlementDate = bond.calculateSettlementDateFromValuation(ratesProvider.getValuationDate(), refData);
final Function residual = new Function() {
@Override
public Double apply(Double z) {
return presentValueWithZSpread(bond, ratesProvider, discountingProvider, settlementDate,
z, compoundedRateType, periodsPerYear).getAmount() - presentValue.getAmount();
}
};
double[] range = ROOT_BRACKETER.getBracketedPoints(residual, -0.5, 0.5); // Starting range is [-1%, 1%]
return ROOT_FINDER.getRoot(residual, range[0], range[1]);
}
//-------------------------------------------------------------------------
private double ratioPeriodToNextCoupon(CapitalIndexedBondPaymentPeriod bond, LocalDate settlementDate) {
double nbDayToSpot = DAYS.between(settlementDate, bond.getUnadjustedEndDate());
double nbDaysPeriod = DAYS.between(bond.getUnadjustedStartDate(), bond.getUnadjustedEndDate());
return nbDayToSpot / nbDaysPeriod;
}
private double factorToNextCoupon(ResolvedCapitalIndexedBond bond, LocalDate settlementDate) {
if (bond.getUnadjustedStartDate().isAfter(settlementDate)) {
return 0d;
}
int periodIndex = bond.findPeriodIndex(settlementDate)
.orElseThrow(() -> new IllegalArgumentException("Date outside range of bond"));
CapitalIndexedBondPaymentPeriod period = bond.getPeriodicPayments().get(periodIndex);
LocalDate previousAccrualDate = period.getUnadjustedStartDate();
double factorSpot = bond.yearFraction(previousAccrualDate, settlementDate);
double factorPeriod = bond.yearFraction(previousAccrualDate, period.getUnadjustedEndDate());
return (factorPeriod - factorSpot) / factorPeriod;
}
double indexRatio(ResolvedCapitalIndexedBond bond, RatesProvider ratesProvider, LocalDate settlementDate) {
LocalDate endReferenceDate = settlementDate.isBefore(ratesProvider.getValuationDate()) ?
ratesProvider.getValuationDate() :
settlementDate;
RateComputation modifiedComputation = bond.getRateCalculation().createRateComputation(endReferenceDate);
return 1d + periodPricer.getRateComputationFn().rate(
modifiedComputation,
bond.getUnadjustedStartDate(), // dates not used
bond.getUnadjustedEndDate(),
ratesProvider);
}
PointSensitivityBuilder indexRatioSensitivity(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
LocalDate settlementDate) {
LocalDate endReferenceDate = settlementDate.isBefore(ratesProvider.getValuationDate()) ?
ratesProvider.getValuationDate() :
settlementDate;
RateComputation modifiedComputation = bond.getRateCalculation().createRateComputation(endReferenceDate);
return periodPricer.getRateComputationFn().rateSensitivity(
modifiedComputation,
bond.getUnadjustedStartDate(), // dates not used
bond.getUnadjustedEndDate(),
ratesProvider);
}
private void validate(RatesProvider ratesProvider, LegalEntityDiscountingProvider discountingProvider) {
ArgChecker.isTrue(ratesProvider.getValuationDate().isEqual(discountingProvider.getValuationDate()),
"the rates providers should be for the same date");
}
//-------------------------------------------------------------------------
// compute pv of coupon payment(s) s.t. referenceDate1 < coupon <= referenceDate2
double presentValueCoupon(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate1,
LocalDate referenceDate2) {
double pvDiff = 0d;
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate1) && !period.getDetachmentDate().isAfter(referenceDate2)) {
pvDiff += periodPricer.presentValue(period, ratesProvider, discountFactors);
}
}
return pvDiff;
}
// compute pv of coupon payment(s) s.t. referenceDate1 < coupon <= referenceDate2
double presentValueCouponWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate1,
LocalDate referenceDate2,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
double pvDiff = 0d;
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate1) && !period.getDetachmentDate().isAfter(referenceDate2)) {
pvDiff += periodPricer.presentValueWithZSpread(
period, ratesProvider, discountFactors, zSpread, compoundedRateType, periodsPerYear);
}
}
return pvDiff;
}
PointSensitivityBuilder presentValueSensitivityCoupon(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate1,
LocalDate referenceDate2) {
PointSensitivityBuilder pvSensiDiff = PointSensitivityBuilder.none();
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate1) && !period.getDetachmentDate().isAfter(referenceDate2)) {
pvSensiDiff = pvSensiDiff.combinedWith(periodPricer.presentValueSensitivity(period, ratesProvider, discountFactors));
}
}
return pvSensiDiff;
}
// compute pv sensitivity of coupon payment(s) s.t. referenceDate1 < coupon <= referenceDate2
PointSensitivityBuilder presentValueSensitivityCouponWithZSpread(
ResolvedCapitalIndexedBond bond,
RatesProvider ratesProvider,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate1,
LocalDate referenceDate2,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
PointSensitivityBuilder pvSensiDiff = PointSensitivityBuilder.none();
for (CapitalIndexedBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate1) && !period.getDetachmentDate().isAfter(referenceDate2)) {
pvSensiDiff = pvSensiDiff.combinedWith(periodPricer.presentValueSensitivityWithZSpread(
period, ratesProvider, discountFactors, zSpread, compoundedRateType, periodsPerYear));
}
}
return pvSensiDiff;
}
//-------------------------------------------------------------------------
// extracts the repo curve discount factors for the bond
static RepoCurveDiscountFactors repoCurveDf(ResolvedCapitalIndexedBond bond, LegalEntityDiscountingProvider provider) {
return provider.repoCurveDiscountFactors(bond.getSecurityId(), bond.getLegalEntityId(), bond.getCurrency());
}
// extracts the issuer curve discount factors for the bond
static IssuerCurveDiscountFactors issuerCurveDf(ResolvedCapitalIndexedBond bond, LegalEntityDiscountingProvider provider) {
return provider.issuerCurveDiscountFactors(bond.getLegalEntityId(), bond.getCurrency());
}
}