Skip to content

Commit

Permalink
Small Year Fractions in SimpleDiscountFactors (#2573)
Browse files Browse the repository at this point in the history
  • Loading branch information
yukiiwashita authored May 16, 2023
1 parent 11aa308 commit 24fc863
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
package com.opengamma.strata.pricer;

import static com.opengamma.strata.pricer.SimpleDiscountFactors.EFFECTIVE_ZERO;
import static com.opengamma.strata.pricer.ZeroRatePeriodicDiscountFactors.EFFECTIVE_ZERO;

import java.time.LocalDate;
import java.util.Optional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public final class SimpleDiscountFactors
/**
* Year fraction used as an effective zero.
*/
static final double EFFECTIVE_ZERO = 1e-10;
private static final double EFFECTIVE_ZERO = 1e-8;

/**
* The currency that the discount factors are for.
Expand Down Expand Up @@ -171,49 +171,41 @@ public double relativeYearFraction(LocalDate date) {

@Override
public double discountFactor(double yearFraction) {
if (yearFraction <= EFFECTIVE_ZERO) {
return 1d;
}
// read discount factor directly off curve
return curve.yValue(yearFraction);
}

@Override
public double discountFactorTimeDerivative(double yearFraction) {
if (yearFraction <= EFFECTIVE_ZERO) {
return 0d;
}
return curve.firstDerivative(yearFraction);
}

@Override
public double zeroRate(double yearFraction) {
double yearFractionMod = Math.max(EFFECTIVE_ZERO, yearFraction);
// zero rate is undefined in general for tiny year fractions.
double yearFractionMod = modifyYearFraction(yearFraction);
double discountFactor = discountFactor(yearFractionMod);
return -Math.log(discountFactor) / yearFractionMod;
}

//-------------------------------------------------------------------------
@Override
public ZeroRateSensitivity zeroRatePointSensitivity(double yearFraction, Currency sensitivityCurrency) {
double discountFactor = discountFactor(yearFraction);
if (yearFraction <= EFFECTIVE_ZERO) {
return ZeroRateSensitivity.of(currency, yearFraction, sensitivityCurrency, 0d);
}
return ZeroRateSensitivity.of(currency, yearFraction, sensitivityCurrency, -discountFactor * yearFraction);
// zero rate sensitivity is undefined in general for tiny year fractions.
double yearFractionMod = modifyYearFraction(yearFraction);
double discountFactor = discountFactor(yearFractionMod);
return ZeroRateSensitivity.of(currency, yearFraction, sensitivityCurrency, -discountFactor * yearFractionMod);
}

//-------------------------------------------------------------------------
@Override
public CurrencyParameterSensitivities parameterSensitivity(ZeroRateSensitivity pointSens) {
double yearFraction = pointSens.getYearFraction();
if (yearFraction <= EFFECTIVE_ZERO) {
return CurrencyParameterSensitivities.empty();
}
double discountFactor = discountFactor(yearFraction);
UnitParameterSensitivity unitSens = curve.yValueParameterSensitivity(yearFraction);
// zero rate sensitivity is undefined in general for tiny year fractions,
// thus parameter sensitivities will be inaccurate.
double yearFractionMod = modifyYearFraction(pointSens.getYearFraction());
double discountFactor = discountFactor(yearFractionMod);
UnitParameterSensitivity unitSens = curve.yValueParameterSensitivity(yearFractionMod);
CurrencyParameterSensitivity curSens = unitSens
.multipliedBy(-1d / (yearFraction * discountFactor))
.multipliedBy(-1d / (yearFractionMod * discountFactor))
.multipliedBy(pointSens.getCurrency(), pointSens.getSensitivity());
return CurrencyParameterSensitivities.of(curSens);
}
Expand All @@ -223,6 +215,55 @@ public CurrencyParameterSensitivities createParameterSensitivity(Currency curren
return CurrencyParameterSensitivities.of(curve.createParameterSensitivity(currency, sensitivities));
}

//-------------------------------------------------------------------------
@Override
public double discountFactorWithSpread(
double yearFraction,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {

double df = discountFactor(yearFraction);
if (compoundedRateType.equals(CompoundedRateType.PERIODIC)) {
ArgChecker.notNegativeOrZero(periodsPerYear, "periodPerYear");
double yearFractionMod = modifyYearFraction(yearFraction);
double ratePeriodicAnnualPlusOne =
Math.pow(df, -1.0 / periodsPerYear / yearFractionMod) + zSpread / periodsPerYear;
return Math.pow(ratePeriodicAnnualPlusOne, -periodsPerYear * yearFractionMod);
} else {
return df * Math.exp(-zSpread * yearFraction);
}
}

@Override
public ZeroRateSensitivity zeroRatePointSensitivityWithSpread(
double yearFraction,
Currency sensitivityCurrency,
double zSpread,
CompoundedRateType compoundedRateType,
int periodsPerYear) {

ZeroRateSensitivity sensi = zeroRatePointSensitivity(yearFraction, sensitivityCurrency);
double factor;
if (compoundedRateType.equals(CompoundedRateType.PERIODIC)) {
double yearFractionMod = modifyYearFraction(yearFraction);
double df = discountFactor(yearFraction);
double dfRoot = Math.pow(df, -1d / periodsPerYear / yearFractionMod);
factor = dfRoot / df / Math.pow(dfRoot + zSpread / periodsPerYear, periodsPerYear * yearFractionMod + 1d);
} else {
factor = Math.exp(-zSpread * yearFraction);
}
return sensi.multipliedBy(factor);
}

//-------------------------------------------------------------------------
private double modifyYearFraction(double yearFraction) {
if (Math.abs(yearFraction) < EFFECTIVE_ZERO) {
return yearFraction < 0d ? -EFFECTIVE_ZERO : EFFECTIVE_ZERO;
}
return yearFraction;
}

//-------------------------------------------------------------------------
/**
* Returns a new instance with a different curve.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
*/
package com.opengamma.strata.pricer;

import static com.opengamma.strata.pricer.SimpleDiscountFactors.EFFECTIVE_ZERO;

import java.io.Serializable;
import java.time.LocalDate;
import java.util.Map;
Expand Down Expand Up @@ -56,6 +54,11 @@
public final class ZeroRateDiscountFactors
implements DiscountFactors, ImmutableBean, Serializable {

/**
* Year fraction used as an effective zero.
*/
private static final double EFFECTIVE_ZERO = 1e-10;

/**
* The currency that the discount factors are for.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public final class ZeroRatePeriodicDiscountFactors
/**
* Year fraction used as an effective zero.
*/
private static final double EFFECTIVE_ZERO = 1e-10;
static final double EFFECTIVE_ZERO = 1e-10;

/**
* The currency that the discount factors are for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.opengamma.strata.market.curve.interpolator.CurveInterpolator;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolators;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.market.param.CurrencyParameterSensitivity;

/**
* Test {@link SimpleDiscountFactors}.
Expand All @@ -55,6 +56,7 @@ public class SimpleDiscountFactorsTest {
private static final double TOL = 1.0e-12;
private static final double TOL_FD = 1.0e-8;
private static final double EPS = 1.0e-6;
private static final double TOL_SMALL_YF = 1.0e-9;

//-------------------------------------------------------------------------
@Test
Expand Down Expand Up @@ -146,7 +148,9 @@ public void test_discountFactor_withSpread_continuous() {
@Test
public void test_discountFactor_withSpread_continuous_beforeValDate() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
assertThat(test.discountFactorWithSpread(DATE_BEFORE, SPREAD, CONTINUOUS, 0)).isEqualTo(1d);
double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_BEFORE);
double expected = CURVE.yValue(relativeYearFraction) * Math.exp(-SPREAD * relativeYearFraction);
assertThat(test.discountFactorWithSpread(DATE_BEFORE, SPREAD, CONTINUOUS, 0)).isCloseTo(expected, offset(TOL));
}

@Test
Expand All @@ -164,13 +168,18 @@ public void test_discountFactor_withSpread_periodic() {
public void test_discountFactor_withSpread_periodic_beforeValDate() {
int periodPerYear = 4;
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
assertThat(test.discountFactorWithSpread(DATE_BEFORE, SPREAD, PERIODIC, periodPerYear)).isEqualTo(1d);
double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_BEFORE);
double discountFactorBase = test.discountFactor(DATE_BEFORE);
double rate = (Math.pow(discountFactorBase, -1d / periodPerYear / relativeYearFraction) - 1d) * periodPerYear;
double expected = discountFactorFromPeriodicallyCompoundedRate(rate + SPREAD, periodPerYear, relativeYearFraction);
assertThat(test.discountFactorWithSpread(DATE_BEFORE, SPREAD, PERIODIC, periodPerYear)).isCloseTo(expected, offset(TOL));
}

@Test
public void test_discountFactor_withSpread_smallYearFraction() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
assertThat(test.discountFactorWithSpread(DATE_VAL, SPREAD, PERIODIC, 2)).isEqualTo(1d);
assertThat(test.discountFactorWithSpread(DATE_VAL, SPREAD, PERIODIC, 2))
.isCloseTo(1d, offset(TOL_SMALL_YF));
}

private double discountFactorFromPeriodicallyCompoundedRate(double rate, double periodPerYear, double time) {
Expand All @@ -191,7 +200,8 @@ public void test_zeroRatePointSensitivity() {
public void test_zeroRatePointSensitivity_beforeValDate() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_BEFORE);
ZeroRateSensitivity expected = ZeroRateSensitivity.of(GBP, relativeYearFraction, 0d);
double df = CURVE.yValue(relativeYearFraction);
ZeroRateSensitivity expected = ZeroRateSensitivity.of(GBP, relativeYearFraction, -df * relativeYearFraction);
assertThat(test.zeroRatePointSensitivity(DATE_BEFORE)).isEqualTo(expected);
}

Expand Down Expand Up @@ -219,7 +229,8 @@ public void test_zeroRatePointSensitivityWithSpread_continuous_beforeValDate() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_BEFORE);
ZeroRateSensitivity expected = ZeroRateSensitivity.of(GBP, relativeYearFraction, 0d);
assertThat(test.zeroRatePointSensitivityWithSpread(DATE_BEFORE, SPREAD, CONTINUOUS, 0)).isEqualTo(expected);
assertThat(test.zeroRatePointSensitivityWithSpread(DATE_BEFORE, SPREAD, CONTINUOUS, 0).build()
.equalWithTolerance(expected.build(), TOL_SMALL_YF));
}

@Test
Expand Down Expand Up @@ -257,9 +268,17 @@ public void test_zeroRatePointSensitivityWithSpread_periodic_beforeValDate() {
int periodPerYear = 4;
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_BEFORE);
double df = CURVE.yValue(relativeYearFraction);
double discountFactorUp = df * Math.exp(-EPS * relativeYearFraction);
double discountFactorDw = df * Math.exp(EPS * relativeYearFraction);
double rateUp = (Math.pow(discountFactorUp, -1d / periodPerYear / relativeYearFraction) - 1d) * periodPerYear;
double rateDw = (Math.pow(discountFactorDw, -1d / periodPerYear / relativeYearFraction) - 1d) * periodPerYear;
double expectedValue = 0.5 / EPS * (
discountFactorFromPeriodicallyCompoundedRate(rateUp + SPREAD, periodPerYear, relativeYearFraction) -
discountFactorFromPeriodicallyCompoundedRate(rateDw + SPREAD, periodPerYear, relativeYearFraction));
ZeroRateSensitivity computed = test.zeroRatePointSensitivityWithSpread(
DATE_BEFORE, SPREAD, PERIODIC, periodPerYear);
assertThat(computed.getSensitivity()).isCloseTo(0d, offset(EPS));
assertThat(computed.getSensitivity()).isCloseTo(expectedValue, offset(EPS));
assertThat(computed.getCurrency()).isEqualTo(GBP);
assertThat(computed.getCurveCurrency()).isEqualTo(GBP);
assertThat(computed.getYearFraction()).isEqualTo(relativeYearFraction);
Expand Down Expand Up @@ -290,14 +309,16 @@ public void test_zeroRatePointSensitivityWithSpread_sensitivityCurrency_periodic
public void test_zeroRatePointSensitivityWithSpread_smallYearFraction() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
ZeroRateSensitivity expected = ZeroRateSensitivity.of(GBP, 0d, 0d);
assertThat(test.zeroRatePointSensitivityWithSpread(DATE_VAL, SPREAD, CONTINUOUS, 0)).isEqualTo(expected);
assertThat(test.zeroRatePointSensitivityWithSpread(DATE_VAL, SPREAD, CONTINUOUS, 0).build()
.equalWithTolerance(expected.build(), TOL_SMALL_YF));
}

@Test
public void test_zeroRatePointSensitivityWithSpread_sensitivityCurrency_smallYearFraction() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
ZeroRateSensitivity expected = ZeroRateSensitivity.of(GBP, 0d, USD, 0d);
assertThat(test.zeroRatePointSensitivityWithSpread(DATE_VAL, USD, SPREAD, PERIODIC, 2)).isEqualTo(expected);
assertThat(test.zeroRatePointSensitivityWithSpread(DATE_VAL, USD, SPREAD, PERIODIC, 2).build()
.equalWithTolerance(expected.build(), TOL_SMALL_YF));
}

//-------------------------------------------------------------------------
Expand All @@ -319,16 +340,28 @@ public void test_currencyParameterSensitivity() {
public void test_currencyParameterSensitivity_beforeValDate() {
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
ZeroRateSensitivity sens = test.zeroRatePointSensitivity(DATE_BEFORE);
assertThat(test.parameterSensitivity(sens)).isEqualTo(CurrencyParameterSensitivities.empty());

double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_BEFORE);
double discountFactor = CURVE.yValue(relativeYearFraction);
CurrencyParameterSensitivities expected = CurrencyParameterSensitivities.of(
CURVE.yValueParameterSensitivity(relativeYearFraction)
.multipliedBy(-1d / discountFactor / relativeYearFraction)
.multipliedBy(sens.getCurrency(), sens.getSensitivity()));
assertThat(test.parameterSensitivity(sens)).isEqualTo(expected);
}

//-------------------------------------------------------------------------
@Test
public void test_currencyParameterSensitivity_val_date() {
// Discount factor at valuation date is always 0, no sensitivity.
SimpleDiscountFactors test = SimpleDiscountFactors.of(GBP, DATE_VAL, CURVE);
ZeroRateSensitivity sens = test.zeroRatePointSensitivity(DATE_VAL);
assertThat(test.parameterSensitivity(sens)).isEqualTo(CurrencyParameterSensitivities.empty());

double relativeYearFraction = ACT_365F.relativeYearFraction(DATE_VAL, DATE_VAL);
double discountFactor = CURVE.yValue(relativeYearFraction);
CurrencyParameterSensitivities expected = CurrencyParameterSensitivities.of(
CurrencyParameterSensitivity.of(NAME, GBP, DoubleArray.of(1d, 0d)));
CurrencyParameterSensitivities tested = test.parameterSensitivity(sens);
assertThat(test.parameterSensitivity(sens).equalWithTolerance(expected, TOL));
}

//-------------------------------------------------------------------------
Expand Down

0 comments on commit 24fc863

Please sign in to comment.