/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.bond;
import static com.opengamma.strata.product.bond.FixedCouponBondYieldConvention.DE_BONDS;
import static com.opengamma.strata.product.bond.FixedCouponBondYieldConvention.GB_BUMP_DMO;
import static com.opengamma.strata.product.bond.FixedCouponBondYieldConvention.JP_SIMPLE;
import static com.opengamma.strata.product.bond.FixedCouponBondYieldConvention.US_STREET;
import java.time.LocalDate;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.StandardId;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.Payment;
import com.opengamma.strata.collect.ArgChecker;
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.DiscountingPaymentPricer;
import com.opengamma.strata.pricer.ZeroRateSensitivity;
import com.opengamma.strata.product.Security;
import com.opengamma.strata.product.bond.FixedCouponBondPaymentPeriod;
import com.opengamma.strata.product.bond.FixedCouponBondYieldConvention;
import com.opengamma.strata.product.bond.ResolvedFixedCouponBond;
/**
* Pricer for for rate fixed coupon bond products.
* <p>
* This function provides the ability to price a {@link ResolvedFixedCouponBond}.
*
* <h4>Price</h4>
* Strata uses <i>decimal prices</i> 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 DiscountingFixedCouponBondProductPricer {
/**
* Default implementation.
*/
public static final DiscountingFixedCouponBondProductPricer DEFAULT = new DiscountingFixedCouponBondProductPricer(
DiscountingFixedCouponBondPaymentPeriodPricer.DEFAULT,
DiscountingPaymentPricer.DEFAULT);
/**
* The root finder.
*/
private static final RealSingleRootFinder ROOT_FINDER = new BrentSingleRootFinder();
/**
* Brackets a root.
*/
private static final BracketRoot ROOT_BRACKETER = new BracketRoot();
/**
* Pricer for {@link Payment}.
*/
private final DiscountingPaymentPricer nominalPricer;
/**
* Pricer for {@link FixedCouponBondPaymentPeriod}.
*/
private final DiscountingFixedCouponBondPaymentPeriodPricer periodPricer;
/**
* Creates an instance.
*
* @param periodPricer the pricer for {@link FixedCouponBondPaymentPeriod}
* @param nominalPricer the pricer for {@link Payment}
*/
public DiscountingFixedCouponBondProductPricer(
DiscountingFixedCouponBondPaymentPeriodPricer periodPricer,
DiscountingPaymentPricer nominalPricer) {
this.nominalPricer = ArgChecker.notNull(nominalPricer, "nominalPricer");
this.periodPricer = ArgChecker.notNull(periodPricer, "periodPricer");
}
//-------------------------------------------------------------------------
/**
* Calculates the present value of the fixed coupon bond product.
* <p>
* The present value of the product is the value on the valuation date.
* The result is expressed using the payment currency of the bond.
* <p>
* Coupon payments of the product are considered based on the valuation date.
*
* @param bond the product
* @param provider the discounting provider
* @return the present value of the fixed coupon bond product
*/
public CurrencyAmount presentValue(ResolvedFixedCouponBond bond, LegalEntityDiscountingProvider provider) {
return presentValue(bond, provider, provider.getValuationDate());
}
// calculate the present value
CurrencyAmount presentValue(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
LocalDate referenceDate) {
IssuerCurveDiscountFactors discountFactors = provider.issuerCurveDiscountFactors(
bond.getLegalEntityId(), bond.getCurrency());
CurrencyAmount pvNominal =
nominalPricer.presentValue(bond.getNominalPayment(), discountFactors.getDiscountFactors());
CurrencyAmount pvCoupon = presentValueCoupon(bond, discountFactors, referenceDate);
return pvNominal.plus(pvCoupon);
}
/**
* Calculates the present value of the fixed coupon bond product with z-spread.
* <p>
* The present value of the product is the value on the valuation date.
* The result is expressed using the payment currency of the bond.
* <p>
* 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 provider the discounting 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 fixed coupon bond product
*/
public CurrencyAmount presentValueWithZSpread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
return presentValueWithZSpread(
bond, provider, zSpread, compoundedRateType, periodsPerYear, provider.getValuationDate());
}
// calculate the present value
CurrencyAmount presentValueWithZSpread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear,
LocalDate referenceDate) {
IssuerCurveDiscountFactors discountFactors = provider.issuerCurveDiscountFactors(
bond.getLegalEntityId(), bond.getCurrency());
CurrencyAmount pvNominal = nominalPricer.presentValueWithSpread(
bond.getNominalPayment(), discountFactors.getDiscountFactors(), zSpread, compoundedRateType, periodsPerYear);
CurrencyAmount pvCoupon = presentValueCouponFromZSpread(
bond, discountFactors, zSpread, compoundedRateType, periodsPerYear, referenceDate);
return pvNominal.plus(pvCoupon);
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty price of the fixed coupon bond.
* <p>
* The fixed coupon bond is represented as {@link Security} where standard ID of the bond is stored.
* <p>
* Strata uses <i>decimal prices</i> for bonds. For example, a price of 99.32% is represented in Strata by 0.9932.
*
* @param bond the product
* @param provider the discounting provider
* @param refData the reference data used to calculate the settlement date
* @return the dirty price of the fixed coupon bond security
*/
public double dirtyPriceFromCurves(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
ReferenceData refData) {
LocalDate settlementDate = bond.getSettlementDateOffset().adjust(provider.getValuationDate(), refData);
return dirtyPriceFromCurves(bond, provider, settlementDate);
}
/**
* Calculates the dirty price of the fixed coupon bond under the specified settlement date.
* <p>
* The fixed coupon bond is represented as {@link Security} where standard ID of the bond is stored.
*
* @param bond the product
* @param provider the discounting provider
* @param settlementDate the settlement date
* @return the dirty price of the fixed coupon bond security
*/
public double dirtyPriceFromCurves(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
LocalDate settlementDate) {
CurrencyAmount pv = presentValue(bond, provider, settlementDate);
StandardId legalEntityId = bond.getLegalEntityId();
double df = provider.repoCurveDiscountFactors(
bond.getSecurityId(), legalEntityId, bond.getCurrency()).discountFactor(settlementDate);
double notional = bond.getNotional();
return pv.getAmount() / df / notional;
}
/**
* Calculates the dirty price of the fixed coupon bond with z-spread.
* <p>
* The z-spread is a parallel shift applied to continuously compounded rates or periodic
* compounded rates of the discounting curve.
* <p>
* The fixed coupon bond is represented as {@link Security} where standard ID of the bond is stored.
*
* @param bond the product
* @param provider the discounting 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 fixed coupon bond security
*/
public double dirtyPriceFromCurvesWithZSpread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
ReferenceData refData,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
LocalDate settlementDate = bond.getSettlementDateOffset().adjust(provider.getValuationDate(), refData);
return dirtyPriceFromCurvesWithZSpread(bond, provider, zSpread, compoundedRateType, periodsPerYear, settlementDate);
}
/**
* Calculates the dirty price of the fixed coupon bond under the specified settlement date with z-spread.
* <p>
* The z-spread is a parallel shift applied to continuously compounded rates or periodic
* compounded rates of the discounting curve.
* <p>
* The fixed coupon bond is represented as {@link Security} where standard ID of the bond is stored.
*
* @param bond the product
* @param provider the discounting provider
* @param zSpread the z-spread
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @param settlementDate the settlement date
* @return the dirty price of the fixed coupon bond security
*/
public double dirtyPriceFromCurvesWithZSpread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear,
LocalDate settlementDate) {
CurrencyAmount pv = presentValueWithZSpread(bond, provider, zSpread, compoundedRateType, periodsPerYear, settlementDate);
StandardId legalEntityId = bond.getLegalEntityId();
double df = provider.repoCurveDiscountFactors(
bond.getSecurityId(), legalEntityId, bond.getCurrency()).discountFactor(settlementDate);
double notional = bond.getNotional();
return pv.getAmount() / df / notional;
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty price of the fixed coupon bond from its settlement date and clean price.
*
* @param bond the product
* @param settlementDate the settlement date
* @param cleanPrice the clean price
* @return the present value of the fixed coupon bond product
*/
public double dirtyPriceFromCleanPrice(ResolvedFixedCouponBond bond, LocalDate settlementDate, double cleanPrice) {
double notional = bond.getNotional();
double accruedInterest = accruedInterest(bond, settlementDate);
return cleanPrice + accruedInterest / notional;
}
/**
* Calculates the clean price of the fixed coupon bond from its settlement date and dirty price.
* <p>
* Strata uses <i>decimal prices</i> for bonds. For example, a price of 99.32% is represented in Strata by 0.9932.
*
* @param bond the product
* @param settlementDate the settlement date
* @param dirtyPrice the dirty price
* @return the present value of the fixed coupon bond product
*/
public double cleanPriceFromDirtyPrice(ResolvedFixedCouponBond bond, LocalDate settlementDate, double dirtyPrice) {
double notional = bond.getNotional();
double accruedInterest = accruedInterest(bond, settlementDate);
return dirtyPrice - accruedInterest / notional;
}
//-------------------------------------------------------------------------
/**
* Calculates the z-spread of the fixed coupon bond from curves and dirty price.
* <p>
* 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 dirty price.
*
* @param bond the product
* @param provider the discounting provider
* @param refData the reference data used to calculate the settlement date
* @param dirtyPrice the dirtyPrice
* @param compoundedRateType the compounded rate type
* @param periodsPerYear the number of periods per year
* @return the z-spread of the fixed coupon bond security
*/
public double zSpreadFromCurvesAndDirtyPrice(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
ReferenceData refData,
double dirtyPrice,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
final Function<Double, Double> residual = new Function<Double, Double>() {
@Override
public Double apply(final Double z) {
return dirtyPriceFromCurvesWithZSpread(
bond, provider, refData, z, compoundedRateType, periodsPerYear) - dirtyPrice;
}
};
double[] range = ROOT_BRACKETER.getBracketedPoints(residual, -0.01, 0.01); // Starting range is [-1%, 1%]
return ROOT_FINDER.getRoot(residual, range[0], range[1]);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity of the fixed coupon bond product.
* <p>
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* @param bond the product
* @param provider the discounting provider
* @return the present value curve sensitivity of the product
*/
public PointSensitivityBuilder presentValueSensitivity(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider) {
return presentValueSensitivity(bond, provider, provider.getValuationDate());
}
// calculate the present value sensitivity
PointSensitivityBuilder presentValueSensitivity(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
LocalDate referenceDate) {
IssuerCurveDiscountFactors discountFactors = provider.issuerCurveDiscountFactors(
bond.getLegalEntityId(), bond.getCurrency());
PointSensitivityBuilder pvNominal = presentValueSensitivityNominal(bond, discountFactors);
PointSensitivityBuilder pvCoupon = presentValueSensitivityCoupon(bond, discountFactors, referenceDate);
return pvNominal.combinedWith(pvCoupon);
}
/**
* Calculates the present value sensitivity of the fixed coupon bond with z-spread.
* <p>
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
* <p>
* 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 provider the discounting 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(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
return presentValueSensitivityWithZSpread(
bond, provider, zSpread, compoundedRateType, periodsPerYear, provider.getValuationDate());
}
// calculate the present value sensitivity
PointSensitivityBuilder presentValueSensitivityWithZSpread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear,
LocalDate referenceDate) {
IssuerCurveDiscountFactors discountFactors = provider.issuerCurveDiscountFactors(
bond.getLegalEntityId(), bond.getCurrency());
PointSensitivityBuilder pvNominal = presentValueSensitivityNominalFromZSpread(
bond, discountFactors, zSpread, compoundedRateType, periodsPerYear);
PointSensitivityBuilder pvCoupon = presentValueSensitivityCouponFromZSpread(
bond, discountFactors, zSpread, compoundedRateType, periodsPerYear, referenceDate);
return pvNominal.combinedWith(pvCoupon);
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty price sensitivity of the fixed coupon bond product.
* <p>
* The dirty price sensitivity of the security is the sensitivity of the present value to
* the underlying curves.
*
* @param bond the product
* @param provider the discounting provider
* @param refData the reference data used to calculate the settlement date
* @return the dirty price value curve sensitivity of the security
*/
public PointSensitivityBuilder dirtyPriceSensitivity(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
ReferenceData refData) {
LocalDate settlementDate = bond.getSettlementDateOffset().adjust(provider.getValuationDate(), refData);
return dirtyPriceSensitivity(bond, provider, settlementDate);
}
// calculate the dirty price sensitivity
PointSensitivityBuilder dirtyPriceSensitivity(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
LocalDate referenceDate) {
StandardId legalEntityId = bond.getLegalEntityId();
RepoCurveDiscountFactors discountFactors =
provider.repoCurveDiscountFactors(bond.getSecurityId(), legalEntityId, bond.getCurrency());
double df = discountFactors.discountFactor(referenceDate);
CurrencyAmount pv = presentValue(bond, provider);
double notional = bond.getNotional();
PointSensitivityBuilder pvSensi = presentValueSensitivity(bond, provider).multipliedBy(1d / df / notional);
RepoCurveZeroRateSensitivity dfSensi = discountFactors.zeroRatePointSensitivity(referenceDate)
.multipliedBy(-pv.getAmount() / df / df / notional);
return pvSensi.combinedWith(dfSensi);
}
/**
* Calculates the dirty price sensitivity of the fixed coupon bond with z-spread.
* <p>
* The dirty price sensitivity of the security is the sensitivity of the present value to
* the underlying curves.
* <p>
* 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 provider the discounting 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 dirtyPriceSensitivityWithZspread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
ReferenceData refData,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
LocalDate settlementDate = bond.getSettlementDateOffset().adjust(provider.getValuationDate(), refData);
return dirtyPriceSensitivityWithZspread(bond, provider, zSpread, compoundedRateType, periodsPerYear, settlementDate);
}
// calculate the dirty price sensitivity
PointSensitivityBuilder dirtyPriceSensitivityWithZspread(
ResolvedFixedCouponBond bond,
LegalEntityDiscountingProvider provider,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear,
LocalDate referenceDate) {
StandardId legalEntityId = bond.getLegalEntityId();
RepoCurveDiscountFactors discountFactors =
provider.repoCurveDiscountFactors(bond.getSecurityId(), legalEntityId, bond.getCurrency());
double df = discountFactors.discountFactor(referenceDate);
CurrencyAmount pv = presentValueWithZSpread(bond, provider, zSpread, compoundedRateType, periodsPerYear);
double notional = bond.getNotional();
PointSensitivityBuilder pvSensi = presentValueSensitivityWithZSpread(
bond, provider, zSpread, compoundedRateType, periodsPerYear).multipliedBy(1d / df / notional);
RepoCurveZeroRateSensitivity dfSensi = discountFactors.zeroRatePointSensitivity(referenceDate)
.multipliedBy(-pv.getAmount() / df / df / notional);
return pvSensi.combinedWith(dfSensi);
}
//-------------------------------------------------------------------------
/**
* Calculates the accrued interest of the fixed coupon bond with the specified settlement date.
*
* @param bond the product
* @param settlementDate the settlement date
* @return the accrued interest of the product
*/
public double accruedInterest(ResolvedFixedCouponBond bond, LocalDate settlementDate) {
double notional = bond.getNotional();
return accruedYearFraction(bond, settlementDate) * bond.getFixedRate() * notional;
}
/**
* Calculates the accrued year fraction of the fixed coupon bond with the specified settlement date.
*
* @param bond the product
* @param settlementDate the settlement date
* @return the accrued year fraction of the product
*/
public double accruedYearFraction(ResolvedFixedCouponBond bond, LocalDate settlementDate) {
if (bond.getUnadjustedStartDate().isAfter(settlementDate)) {
return 0d;
}
FixedCouponBondPaymentPeriod period = bond.findPeriod(settlementDate)
.orElseThrow(() -> new IllegalArgumentException("Date outside range of bond"));
LocalDate previousAccrualDate = period.getUnadjustedStartDate();
double accruedYearFraction = bond.yearFraction(previousAccrualDate, settlementDate);
double result = 0d;
if (settlementDate.isAfter(period.getDetachmentDate())) {
result = accruedYearFraction - period.getYearFraction();
} else {
result = accruedYearFraction;
}
return result;
}
//-------------------------------------------------------------------------
/**
* Calculates the dirty price of the fixed coupon bond from yield.
* <p>
* The yield must be fractional.
* The dirty price is computed for {@link FixedCouponBondYieldConvention}, and the result is expressed in fraction.
*
* @param bond the product
* @param settlementDate the settlement date
* @param yield the yield
* @return the dirty price of the product
*/
public double dirtyPriceFromYield(ResolvedFixedCouponBond bond, LocalDate settlementDate, double yield) {
ImmutableList<FixedCouponBondPaymentPeriod> payments = bond.getPeriodicPayments();
int nCoupon = payments.size() - couponIndex(payments, settlementDate);
FixedCouponBondYieldConvention yieldConv = bond.getYieldConvention();
if (nCoupon == 1) {
if (yieldConv.equals(US_STREET) || yieldConv.equals(DE_BONDS)) {
FixedCouponBondPaymentPeriod payment = payments.get(payments.size() - 1);
return (1d + payment.getFixedRate() * payment.getYearFraction()) /
(1d + factorToNextCoupon(bond, settlementDate) * yield /
((double) bond.getFrequency().eventsPerYear()));
}
}
if ((yieldConv.equals(US_STREET)) || (yieldConv.equals(GB_BUMP_DMO)) || (yieldConv.equals(DE_BONDS))) {
return dirtyPriceFromYieldStandard(bond, settlementDate, yield);
}
if (yieldConv.equals(JP_SIMPLE)) {
LocalDate maturityDate = bond.getUnadjustedEndDate();
if (settlementDate.isAfter(maturityDate)) {
return 0d;
}
double maturity = bond.getDayCount().relativeYearFraction(settlementDate, maturityDate);
double cleanPrice = (1d + bond.getFixedRate() * maturity) / (1d + yield * maturity);
return dirtyPriceFromCleanPrice(bond, settlementDate, cleanPrice);
}
throw new UnsupportedOperationException("The convention " + yieldConv.name() + " is not supported.");
}
private double dirtyPriceFromYieldStandard(
ResolvedFixedCouponBond bond,
LocalDate settlementDate,
double yield) {
int nbCoupon = bond.getPeriodicPayments().size();
double factorOnPeriod = 1 + yield / ((double) bond.getFrequency().eventsPerYear());
double fixedRate = bond.getFixedRate();
double pvAtFirstCoupon = 0;
int pow = 0;
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
FixedCouponBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
if ((period.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!period.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
pvAtFirstCoupon += fixedRate * period.getYearFraction() / Math.pow(factorOnPeriod, pow);
++pow;
}
}
pvAtFirstCoupon += 1d / Math.pow(factorOnPeriod, pow - 1);
return pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNextCoupon(bond, settlementDate));
}
/**
* Calculates the yield of the fixed coupon bond product from dirty price.
* <p>
* The dirty price must be fractional.
* If the analytic formula is not available, the yield is computed by solving
* a root-finding problem with {@link #dirtyPriceFromYield(ResolvedFixedCouponBond, LocalDate, double)}.
* The result is also expressed in fraction.
*
* @param bond the product
* @param settlementDate the settlement date
* @param dirtyPrice the dirty price
* @return the yield of the product
*/
public double yieldFromDirtyPrice(ResolvedFixedCouponBond bond, LocalDate settlementDate, double dirtyPrice) {
if (bond.getYieldConvention().equals(JP_SIMPLE)) {
double cleanPrice = cleanPriceFromDirtyPrice(bond, settlementDate, dirtyPrice);
LocalDate maturityDate = bond.getUnadjustedEndDate();
double maturity = bond.getDayCount().relativeYearFraction(settlementDate, maturityDate);
return (bond.getFixedRate() + (1d - cleanPrice) / maturity) / cleanPrice;
}
final Function<Double, Double> priceResidual = new Function<Double, Double>() {
@Override
public Double apply(final Double y) {
return dirtyPriceFromYield(bond, settlementDate, y) - dirtyPrice;
}
};
double[] range = ROOT_BRACKETER.getBracketedPoints(priceResidual, 0.00, 0.20);
double yield = ROOT_FINDER.getRoot(priceResidual, range[0], range[1]);
return yield;
}
//-------------------------------------------------------------------------
/**
* Calculates the modified duration of the fixed coupon bond product from yield.
* <p>
* The modified duration is defined as the minus of the first derivative of dirty price
* with respect to yield, divided by the dirty price.
* <p>
* The input yield must be fractional. The dirty price and its derivative are
* computed for {@link FixedCouponBondYieldConvention}, and the result is expressed in fraction.
*
* @param bond the product
* @param settlementDate the settlement date
* @param yield the yield
* @return the modified duration of the product
*/
public double modifiedDurationFromYield(ResolvedFixedCouponBond bond, LocalDate settlementDate, double yield) {
ImmutableList<FixedCouponBondPaymentPeriod> payments = bond.getPeriodicPayments();
int nCoupon = payments.size() - couponIndex(payments, settlementDate);
FixedCouponBondYieldConvention yieldConv = bond.getYieldConvention();
if (nCoupon == 1) {
if (yieldConv.equals(US_STREET) || yieldConv.equals(DE_BONDS)) {
double couponPerYear = bond.getFrequency().eventsPerYear();
double factor = factorToNextCoupon(bond, settlementDate);
return factor / couponPerYear / (1d + factor * yield / couponPerYear);
}
}
if (yieldConv.equals(US_STREET) || yieldConv.equals(GB_BUMP_DMO) || yieldConv.equals(DE_BONDS)) {
return modifiedDurationFromYieldStandard(bond, settlementDate, yield);
}
if (yieldConv.equals(JP_SIMPLE)) {
LocalDate maturityDate = bond.getUnadjustedEndDate();
if (settlementDate.isAfter(maturityDate)) {
return 0d;
}
double maturity = bond.getDayCount().relativeYearFraction(settlementDate, maturityDate);
double num = 1d + bond.getFixedRate() * maturity;
double den = 1d + yield * maturity;
double dirtyPrice = dirtyPriceFromCleanPrice(bond, settlementDate, num / den);
return num * maturity / den / den / dirtyPrice;
}
throw new UnsupportedOperationException("The convention " + yieldConv.name() + " is not supported.");
}
private double modifiedDurationFromYieldStandard(
ResolvedFixedCouponBond bond,
LocalDate settlementDate,
double yield) {
int nbCoupon = bond.getPeriodicPayments().size();
double couponPerYear = bond.getFrequency().eventsPerYear();
double factorToNextCoupon = factorToNextCoupon(bond, settlementDate);
double factorOnPeriod = 1 + yield / couponPerYear;
double nominal = bond.getNotional();
double fixedRate = bond.getFixedRate();
double mdAtFirstCoupon = 0d;
double pvAtFirstCoupon = 0d;
int pow = 0;
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
FixedCouponBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
if ((period.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!period.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
mdAtFirstCoupon += period.getYearFraction() / Math.pow(factorOnPeriod, pow + 1) *
(pow + factorToNextCoupon) / couponPerYear;
pvAtFirstCoupon += period.getYearFraction() / Math.pow(factorOnPeriod, pow);
++pow;
}
}
mdAtFirstCoupon *= fixedRate * nominal;
pvAtFirstCoupon *= fixedRate * nominal;
mdAtFirstCoupon += nominal / Math.pow(factorOnPeriod, pow) * (pow - 1 + factorToNextCoupon) /
couponPerYear;
pvAtFirstCoupon += nominal / Math.pow(factorOnPeriod, pow - 1);
double md = mdAtFirstCoupon / pvAtFirstCoupon;
return md;
}
/**
* Calculates the Macaulay duration of the fixed coupon bond product from yield.
* <p>
* Macaulay defined an alternative way of weighting the future cash flows.
* <p>
* The input yield must be fractional. The dirty price and its derivative are
* computed for {@link FixedCouponBondYieldConvention}, and the result is expressed in fraction.
*
* @param bond the product
* @param settlementDate the settlement date
* @param yield the yield
* @return the modified duration of the product
*/
public double macaulayDurationFromYield(ResolvedFixedCouponBond bond, LocalDate settlementDate, double yield) {
ImmutableList<FixedCouponBondPaymentPeriod> payments = bond.getPeriodicPayments();
int nCoupon = payments.size() - couponIndex(payments, settlementDate);
FixedCouponBondYieldConvention yieldConv = bond.getYieldConvention();
if ((yieldConv.equals(US_STREET)) && (nCoupon == 1)) {
return factorToNextCoupon(bond, settlementDate) /
bond.getFrequency().eventsPerYear();
}
if ((yieldConv.equals(US_STREET)) || (yieldConv.equals(GB_BUMP_DMO)) || (yieldConv.equals(DE_BONDS))) {
return modifiedDurationFromYield(bond, settlementDate, yield) *
(1d + yield / bond.getFrequency().eventsPerYear());
}
throw new UnsupportedOperationException("The convention " + yieldConv.name() + " is not supported.");
}
/**
* Calculates the convexity of the fixed coupon bond product from yield.
* <p>
* The convexity is defined as the second derivative of dirty price with respect
* to yield, divided by the dirty price.
* <p>
* The input yield must be fractional. The dirty price and its derivative are
* computed for {@link FixedCouponBondYieldConvention}, and the result is expressed in fraction.
*
* @param bond the product
* @param settlementDate the settlement date
* @param yield the yield
* @return the convexity of the product
*/
public double convexityFromYield(ResolvedFixedCouponBond bond, LocalDate settlementDate, double yield) {
ImmutableList<FixedCouponBondPaymentPeriod> payments = bond.getPeriodicPayments();
int nCoupon = payments.size() - couponIndex(payments, settlementDate);
FixedCouponBondYieldConvention yieldConv = bond.getYieldConvention();
if (nCoupon == 1) {
if (yieldConv.equals(US_STREET) || yieldConv.equals(DE_BONDS)) {
double couponPerYear = bond.getFrequency().eventsPerYear();
double factorToNextCoupon = factorToNextCoupon(bond, settlementDate);
double timeToPay = factorToNextCoupon / couponPerYear;
double disc = (1d + factorToNextCoupon * yield / couponPerYear);
return 2d * timeToPay * timeToPay / (disc * disc);
}
}
if (yieldConv.equals(US_STREET) || yieldConv.equals(GB_BUMP_DMO) || yieldConv.equals(DE_BONDS)) {
return convexityFromYieldStandard(bond, settlementDate, yield);
}
if (yieldConv.equals(JP_SIMPLE)) {
LocalDate maturityDate = bond.getUnadjustedEndDate();
if (settlementDate.isAfter(maturityDate)) {
return 0d;
}
double maturity = bond.getDayCount().relativeYearFraction(settlementDate, maturityDate);
double num = 1d + bond.getFixedRate() * maturity;
double den = 1d + yield * maturity;
double dirtyPrice = dirtyPriceFromCleanPrice(bond, settlementDate, num / den);
return 2d * num * Math.pow(maturity, 2) * Math.pow(den, -3) / dirtyPrice;
}
throw new UnsupportedOperationException("The convention " + yieldConv.name() + " is not supported.");
}
// assumes notional and coupon rate are constant across the payments.
private double convexityFromYieldStandard(
ResolvedFixedCouponBond bond,
LocalDate settlementDate,
double yield) {
int nbCoupon = bond.getPeriodicPayments().size();
double couponPerYear = bond.getFrequency().eventsPerYear();
double factorToNextCoupon = factorToNextCoupon(bond, settlementDate);
double factorOnPeriod = 1 + yield / couponPerYear;
double nominal = bond.getNotional();
double fixedRate = bond.getFixedRate();
double cvAtFirstCoupon = 0;
double pvAtFirstCoupon = 0;
int pow = 0;
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
FixedCouponBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
if ((period.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!period.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
cvAtFirstCoupon += period.getYearFraction() / Math.pow(factorOnPeriod, pow + 2) *
(pow + factorToNextCoupon) * (pow + factorToNextCoupon + 1);
pvAtFirstCoupon += period.getYearFraction() / Math.pow(factorOnPeriod, pow);
++pow;
}
}
cvAtFirstCoupon *= fixedRate * nominal / (couponPerYear * couponPerYear);
pvAtFirstCoupon *= fixedRate * nominal;
cvAtFirstCoupon += nominal / Math.pow(factorOnPeriod, pow + 1) * (pow - 1 + factorToNextCoupon) *
(pow + factorToNextCoupon) / (couponPerYear * couponPerYear);
pvAtFirstCoupon += nominal / Math.pow(factorOnPeriod, pow - 1);
final double pv = pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNextCoupon);
final double cv = cvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNextCoupon) / pv;
return cv;
}
//-------------------------------------------------------------------------
private double factorToNextCoupon(ResolvedFixedCouponBond bond, LocalDate settlementDate) {
if (bond.getPeriodicPayments().get(0).getStartDate().isAfter(settlementDate)) {
return 0d;
}
int couponIndex = couponIndex(bond.getPeriodicPayments(), settlementDate);
double factorSpot = accruedYearFraction(bond, settlementDate);
double factorPeriod = bond.getPeriodicPayments().get(couponIndex).getYearFraction();
return (factorPeriod - factorSpot) / factorPeriod;
}
private int couponIndex(ImmutableList<FixedCouponBondPaymentPeriod> list, LocalDate date) {
int nbCoupon = list.size();
int couponIndex = 0;
for (int loopcpn = 0; loopcpn < nbCoupon; ++loopcpn) {
if (list.get(loopcpn).getEndDate().isAfter(date)) {
couponIndex = loopcpn;
break;
}
}
return couponIndex;
}
//-------------------------------------------------------------------------
private CurrencyAmount presentValueCoupon(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate) {
double total = 0d;
for (FixedCouponBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate)) {
total += periodPricer.presentValue(period, discountFactors);
}
}
return CurrencyAmount.of(bond.getCurrency(), total);
}
private CurrencyAmount presentValueCouponFromZSpread(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear,
LocalDate referenceDate) {
double total = 0d;
for (FixedCouponBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate)) {
total += periodPricer.presentValueWithSpread(period, discountFactors, zSpread, compoundedRateType, periodsPerYear);
}
}
return CurrencyAmount.of(bond.getCurrency(), total);
}
//-------------------------------------------------------------------------
private PointSensitivityBuilder presentValueSensitivityCoupon(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate) {
PointSensitivityBuilder builder = PointSensitivityBuilder.none();
for (FixedCouponBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate)) {
builder = builder.combinedWith(periodPricer.presentValueSensitivity(period, discountFactors));
}
}
return builder;
}
private PointSensitivityBuilder presentValueSensitivityCouponFromZSpread(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear,
LocalDate referenceDate) {
PointSensitivityBuilder builder = PointSensitivityBuilder.none();
for (FixedCouponBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate)) {
builder = builder.combinedWith(periodPricer.presentValueSensitivityWithSpread(
period, discountFactors, zSpread, compoundedRateType, periodsPerYear));
}
}
return builder;
}
private PointSensitivityBuilder presentValueSensitivityNominal(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors) {
Payment nominal = bond.getNominalPayment();
PointSensitivityBuilder pt = nominalPricer.presentValueSensitivity(nominal, discountFactors.getDiscountFactors());
if (pt instanceof ZeroRateSensitivity) {
return IssuerCurveZeroRateSensitivity.of((ZeroRateSensitivity) pt, discountFactors.getLegalEntityGroup());
}
return pt; // NoPointSensitivity
}
private PointSensitivityBuilder presentValueSensitivityNominalFromZSpread(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
Payment nominal = bond.getNominalPayment();
PointSensitivityBuilder pt = nominalPricer.presentValueSensitivityWithSpread(
nominal, discountFactors.getDiscountFactors(), zSpread, compoundedRateType, periodsPerYear);
if (pt instanceof ZeroRateSensitivity) {
return IssuerCurveZeroRateSensitivity.of((ZeroRateSensitivity) pt, discountFactors.getLegalEntityGroup());
}
return pt; // NoPointSensitivity
}
//-------------------------------------------------------------------------
// compute pv of coupon payment(s) s.t. referenceDate1 < coupon <= referenceDate2
double presentValueCoupon(
ResolvedFixedCouponBond bond,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate1,
LocalDate referenceDate2) {
double pvDiff = 0d;
for (FixedCouponBondPaymentPeriod period : bond.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate1) && !period.getDetachmentDate().isAfter(referenceDate2)) {
pvDiff += periodPricer.presentValue(period, discountFactors);
}
}
return pvDiff;
}
// compute pv of coupon payment(s) s.t. referenceDate1 < coupon <= referenceDate2
double presentValueCouponWithZSpread(
ResolvedFixedCouponBond expanded,
IssuerCurveDiscountFactors discountFactors,
LocalDate referenceDate1,
LocalDate referenceDate2,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {
double pvDiff = 0d;
for (FixedCouponBondPaymentPeriod period : expanded.getPeriodicPayments()) {
if (period.getDetachmentDate().isAfter(referenceDate1) && !period.getDetachmentDate().isAfter(referenceDate2)) {
pvDiff += periodPricer.presentValueWithSpread(period, discountFactors, zSpread, compoundedRateType, periodsPerYear);
}
}
return pvDiff;
}
}