com.opengamma.strata.pricer.impl.rate.ForwardOvernightCompoundedRateComputationFn Maven / Gradle / Ivy
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.impl.rate;
import java.time.LocalDate;
import java.util.OptionalDouble;
import com.opengamma.strata.basics.date.DayCount;
import com.opengamma.strata.basics.index.OvernightIndex;
import com.opengamma.strata.basics.index.OvernightIndexObservation;
import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeries;
import com.opengamma.strata.collect.tuple.ObjDoublePair;
import com.opengamma.strata.market.explain.ExplainKey;
import com.opengamma.strata.market.explain.ExplainMapBuilder;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.PricingException;
import com.opengamma.strata.pricer.rate.OvernightIndexRates;
import com.opengamma.strata.pricer.rate.RateComputationFn;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation;
/**
* Rate computation implementation for a rate based on a single overnight index that is compounded.
*
* Rates that are already fixed are retrieved from the time series of the {@link RatesProvider}.
* Rates that are in the future and not in the cut-off period are computed as unique forward rate in the full future period.
* Rates that are in the cut-off period (already fixed or forward) are compounded.
*/
public class ForwardOvernightCompoundedRateComputationFn
implements RateComputationFn {
/**
* Default implementation.
*/
public static final ForwardOvernightCompoundedRateComputationFn DEFAULT =
new ForwardOvernightCompoundedRateComputationFn();
/**
* Creates an instance.
*/
public ForwardOvernightCompoundedRateComputationFn() {
}
//-------------------------------------------------------------------------
@Override
public double rate(
OvernightCompoundedRateComputation computation,
LocalDate startDate,
LocalDate endDate,
RatesProvider provider) {
OvernightIndexRates rates = provider.overnightIndexRates(computation.getIndex());
ObservationDetails details = new ObservationDetails(computation, rates);
return details.calculateRate();
}
@Override
public PointSensitivityBuilder rateSensitivity(
OvernightCompoundedRateComputation computation,
LocalDate startDate,
LocalDate endDate,
RatesProvider provider) {
OvernightIndexRates rates = provider.overnightIndexRates(computation.getIndex());
ObservationDetails details = new ObservationDetails(computation, rates);
return details.calculateRateSensitivity();
}
@Override
public double explainRate(
OvernightCompoundedRateComputation computation,
LocalDate startDate,
LocalDate endDate,
RatesProvider provider,
ExplainMapBuilder builder) {
double rate = rate(computation, startDate, endDate, provider);
builder.put(ExplainKey.COMBINED_RATE, rate);
return rate;
}
//-------------------------------------------------------------------------
// Internal class. Observation details stored in a separate class to clarify the construction.
private static final class ObservationDetails {
private final OvernightCompoundedRateComputation computation;
private final OvernightIndexRates rates;
private final LocalDateDoubleTimeSeries indexFixingDateSeries;
private final DayCount dayCount;
private final int cutoffOffset;
private final LocalDate firstFixing; // The date of the first fixing
private final LocalDate lastFixingP1; // The date after the last fixing
private final LocalDate lastFixing; // The date of the last fixing
private final LocalDate lastFixingNonCutoff; // The last fixing not in the cutoff period.
private final double accrualFactorTotal; // Total accrual factor
private final double[] accrualFactorCutoff; // Accrual factors for the sub-periods using the cutoff rate.
private LocalDate nextFixing; // Running variable through the different methods: next fixing date to be analyzed
private ObservationDetails(OvernightCompoundedRateComputation computation, OvernightIndexRates rates) {
this.computation = computation;
this.rates = rates;
this.indexFixingDateSeries = rates.getFixings();
this.dayCount = computation.getIndex().getDayCount();
// Details of the cutoff period
this.firstFixing = computation.getStartDate();
this.lastFixingP1 = computation.getEndDate();
this.lastFixing = computation.getFixingCalendar().previous(lastFixingP1);
this.cutoffOffset = Math.max(computation.getRateCutOffDays(), 1);
this.accrualFactorCutoff = new double[cutoffOffset - 1];
LocalDate currentFixing = lastFixing;
for (int i = 0; i < cutoffOffset - 1; i++) {
currentFixing = computation.getFixingCalendar().previous(currentFixing);
LocalDate effectiveDate = computation.calculateEffectiveFromFixing(currentFixing);
LocalDate maturityDate = computation.calculateMaturityFromEffective(effectiveDate);
accrualFactorCutoff[i] = dayCount.yearFraction(effectiveDate, maturityDate);
}
this.lastFixingNonCutoff = currentFixing;
LocalDate startUnderlyingPeriod = computation.calculateEffectiveFromFixing(firstFixing);
LocalDate endUnderlyingPeriod = computation.calculateMaturityFromFixing(lastFixing);
this.accrualFactorTotal = dayCount.yearFraction(startUnderlyingPeriod, endUnderlyingPeriod);
}
// Composition - publication strictly before valuation date: try accessing fixing time-series
private double pastCompositionFactor() {
double compositionFactor = 1.0d;
LocalDate currentFixing = firstFixing;
LocalDate currentPublication = computation.calculatePublicationFromFixing(currentFixing);
while ((currentFixing.isBefore(lastFixingNonCutoff)) && // fixing in the non-cutoff period
rates.getValuationDate().isAfter(currentPublication)) { // publication before valuation
LocalDate effectiveDate = computation.calculateEffectiveFromFixing(currentFixing);
LocalDate maturityDate = computation.calculateMaturityFromEffective(effectiveDate);
double accrualFactor = dayCount.yearFraction(effectiveDate, maturityDate);
compositionFactor *= 1.0d + accrualFactor * checkedFixing(currentFixing, indexFixingDateSeries, computation.getIndex());
currentFixing = computation.getFixingCalendar().next(currentFixing);
currentPublication = computation.calculatePublicationFromFixing(currentFixing);
}
if (currentFixing.equals(lastFixingNonCutoff) && // fixing is on the last non-cutoff date, cutoff period known
rates.getValuationDate().isAfter(currentPublication)) { // publication before valuation
double rate = checkedFixing(currentFixing, indexFixingDateSeries, computation.getIndex());
LocalDate effectiveDate = computation.calculateEffectiveFromFixing(currentFixing);
LocalDate maturityDate = computation.calculateMaturityFromEffective(effectiveDate);
double accrualFactor = dayCount.yearFraction(effectiveDate, maturityDate);
compositionFactor *= 1.0d + accrualFactor * rate;
for (int i = 0; i < cutoffOffset - 1; i++) {
compositionFactor *= 1.0d + accrualFactorCutoff[i] * rate;
}
currentFixing = computation.getFixingCalendar().next(currentFixing);
}
nextFixing = currentFixing;
return compositionFactor;
}
// Composition - publication on valuation date: Check if a fixing is available on current date
private double valuationCompositionFactor() {
LocalDate currentFixing = nextFixing;
LocalDate currentPublication = computation.calculatePublicationFromFixing(currentFixing);
if (rates.getValuationDate().equals(currentPublication) &&
!(currentFixing.isAfter(lastFixingNonCutoff))) { // If currentFixing > lastFixingNonCutoff, everything fixed
OptionalDouble fixedRate = indexFixingDateSeries.get(currentFixing);
if (fixedRate.isPresent()) {
nextFixing = computation.getFixingCalendar().next(nextFixing);
LocalDate effectiveDate = computation.calculateEffectiveFromFixing(currentFixing);
LocalDate maturityDate = computation.calculateMaturityFromEffective(effectiveDate);
double accrualFactor = dayCount.yearFraction(effectiveDate, maturityDate);
if (currentFixing.isBefore(lastFixingNonCutoff)) {
return 1.0d + accrualFactor * fixedRate.getAsDouble();
}
double compositionFactor = 1.0d + accrualFactor * fixedRate.getAsDouble();
for (int i = 0; i < cutoffOffset - 1; i++) {
compositionFactor *= 1.0d + accrualFactorCutoff[i] * fixedRate.getAsDouble();
}
return compositionFactor;
}
}
return 1.0d;
}
// Composition - forward part in non-cutoff period; past/valuation date case dealt with in previous methods
private double compositionFactorNonCutoff() {
if (!nextFixing.isAfter(lastFixingNonCutoff)) {
OvernightIndexObservation obs = computation.observeOn(nextFixing);
LocalDate startDate = obs.getEffectiveDate();
LocalDate endDate = computation.calculateMaturityFromFixing(lastFixingNonCutoff);
double accrualFactor = dayCount.yearFraction(startDate, endDate);
double rate = rates.periodRate(obs, endDate);
return 1.0d + accrualFactor * rate;
}
return 1.0d;
}
// Composition - forward part in non-cutoff period; past/valuation date case dealt with in previous methods
private ObjDoublePair compositionFactorAndSensitivityNonCutoff() {
if (!nextFixing.isAfter(lastFixingNonCutoff)) {
OvernightIndexObservation obs = computation.observeOn(nextFixing);
LocalDate startDate = obs.getEffectiveDate();
LocalDate endDate = computation.calculateMaturityFromFixing(lastFixingNonCutoff);
double accrualFactor = dayCount.yearFraction(startDate, endDate);
double rate = rates.periodRate(obs, endDate);
PointSensitivityBuilder rateSensitivity = rates.periodRatePointSensitivity(obs, endDate);
rateSensitivity = rateSensitivity.multipliedBy(accrualFactor);
return ObjDoublePair.of(rateSensitivity, 1.0d + accrualFactor * rate);
}
return ObjDoublePair.of(PointSensitivityBuilder.none(), 1.0d);
}
// Composition - forward part in the cutoff period; past/valuation date case dealt with in previous methods
private double compositionFactorCutoff() {
if (!nextFixing.isAfter(lastFixingNonCutoff)) {
OvernightIndexObservation obs = computation.observeOn(lastFixingNonCutoff);
double rate = rates.rate(obs);
double compositionFactor = 1.0d;
for (int i = 0; i < cutoffOffset - 1; i++) {
compositionFactor *= 1.0d + accrualFactorCutoff[i] * rate;
}
return compositionFactor;
}
return 1.0d;
}
// Composition - forward part in the cutoff period; past/valuation date case dealt with in previous methods
private ObjDoublePair compositionFactorAndSensitivityCutoff() {
OvernightIndexObservation obs = computation.observeOn(lastFixingNonCutoff);
if (!nextFixing.isAfter(lastFixingNonCutoff)) {
double rate = rates.rate(obs);
double compositionFactor = 1.0d;
double compositionFactorDerivative = 0.0;
for (int i = 0; i < cutoffOffset - 1; i++) {
compositionFactor *= 1.0d + accrualFactorCutoff[i] * rate;
compositionFactorDerivative += accrualFactorCutoff[i] / (1.0d + accrualFactorCutoff[i] * rate);
}
compositionFactorDerivative *= compositionFactor;
PointSensitivityBuilder rateSensitivity =
cutoffOffset <= 1 ? PointSensitivityBuilder.none() : rates.ratePointSensitivity(obs);
rateSensitivity = rateSensitivity.multipliedBy(compositionFactorDerivative);
return ObjDoublePair.of(rateSensitivity, compositionFactor);
}
return ObjDoublePair.of(PointSensitivityBuilder.none(), 1.0d);
}
// Calculate the total rate
private double calculateRate() {
return (pastCompositionFactor() * valuationCompositionFactor() *
compositionFactorNonCutoff() * compositionFactorCutoff() - 1.0d) / accrualFactorTotal;
}
// Calculate the total rate sensitivity
private PointSensitivityBuilder calculateRateSensitivity() {
double factor = pastCompositionFactor() * valuationCompositionFactor() / accrualFactorTotal;
ObjDoublePair compositionFactorAndSensitivityNonCutoff =
compositionFactorAndSensitivityNonCutoff();
ObjDoublePair compositionFactorAndSensitivityCutoff = compositionFactorAndSensitivityCutoff();
PointSensitivityBuilder combinedPointSensitivity = compositionFactorAndSensitivityNonCutoff.getFirst()
.multipliedBy(compositionFactorAndSensitivityCutoff.getSecond() * factor);
combinedPointSensitivity = combinedPointSensitivity.combinedWith(compositionFactorAndSensitivityCutoff
.getFirst().multipliedBy(compositionFactorAndSensitivityNonCutoff.getSecond() * factor));
return combinedPointSensitivity;
}
// Check that the fixing is present. Throws an exception if not and return the rate as double.
private static double checkedFixing(
LocalDate currentFixingTs,
LocalDateDoubleTimeSeries indexFixingDateSeries,
OvernightIndex index) {
OptionalDouble fixedRate = indexFixingDateSeries.get(currentFixingTs);
return fixedRate.orElseThrow(() -> new PricingException(
"Could not get fixing value of index " + index.getName() + " for date " + currentFixingTs));
}
}
}