//------------------------------------------------------------------------- /// <summary> /// Calculates the present value rates sensitivity of the Ibor caplet/floorlet. /// <para> /// The present value rates sensitivity of the caplet/floorlet is the sensitivity /// of the present value to the underlying curves. /// /// </para> /// </summary> /// <param name="period"> the Ibor caplet/floorlet period </param> /// <param name="ratesProvider"> the rates provider </param> /// <param name="volatilities"> the volatilities </param> /// <returns> the present value curve sensitivity </returns> public virtual PointSensitivityBuilder presentValueSensitivityRates(IborCapletFloorletPeriod period, RatesProvider ratesProvider, IborCapletFloorletVolatilities volatilities) { validate(volatilities); Currency currency = period.Currency; if (ratesProvider.ValuationDate.isAfter(period.PaymentDate)) { return(PointSensitivityBuilder.none()); } double expiry = volatilities.relativeTime(period.FixingDateTime); PutCall putCall = period.PutCall; double strike = period.Strike; double indexRate = ratesProvider.iborIndexRates(period.Index).rate(period.IborRate.Observation); PointSensitivityBuilder dfSensi = ratesProvider.discountFactors(currency).zeroRatePointSensitivity(period.PaymentDate); if (expiry < 0d) { // Option has expired already double sign = putCall.Call ? 1d : -1d; double payoff = Math.Max(sign * (indexRate - strike), 0d); return(dfSensi.multipliedBy(payoff * period.YearFraction * period.Notional)); } PointSensitivityBuilder indexRateSensiSensi = ratesProvider.iborIndexRates(period.Index).ratePointSensitivity(period.IborRate.Observation); double volatility = volatilities.volatility(expiry, strike, indexRate); double df = ratesProvider.discountFactor(currency, period.PaymentDate); double factor = period.Notional * period.YearFraction; double fwdPv = factor * volatilities.price(expiry, putCall, strike, indexRate, volatility); double fwdDelta = factor * volatilities.priceDelta(expiry, putCall, strike, indexRate, volatility); return(dfSensi.multipliedBy(fwdPv).combinedWith(indexRateSensiSensi.multipliedBy(fwdDelta * df))); }
/// <summary> /// Test for the case where publication lag=0, effective offset=0 (GBP conventions) and no cutoff period. /// The arithmetic average coupons are used mainly in USD. This test is more for completeness than a real case. /// </summary> public virtual void rateGbpNoCutOffSensitivity() { OvernightIndexRates mockRates = mock(typeof(OvernightIndexRates)); when(mockRates.Index).thenReturn(GBP_SONIA); SimpleRatesProvider simpleProv = new SimpleRatesProvider(mockRates); for (int i = 0; i < GBP_OBS.Length; i++) { when(mockRates.rate(GBP_OBS[i])).thenReturn(FIXING_RATES[i]); OvernightRateSensitivity sensitivity = OvernightRateSensitivity.of(GBP_OBS[i], GBP_SONIA.Currency, 1d); when(mockRates.ratePointSensitivity(GBP_OBS[i])).thenReturn(sensitivity); } OvernightAveragedRateComputation ro = OvernightAveragedRateComputation.of(GBP_SONIA, START_DATE, END_DATE, 0, REF_DATA); ForwardOvernightAveragedRateComputationFn obsFn = ForwardOvernightAveragedRateComputationFn.DEFAULT; PointSensitivityBuilder sensitivityBuilderComputed = obsFn.rateSensitivity(ro, DUMMY_ACCRUAL_START_DATE, DUMMY_ACCRUAL_END_DATE, simpleProv); PointSensitivities sensitivityComputed = sensitivityBuilderComputed.build().normalized(); double?[] sensitivityExpected = computedSensitivityFD(ro, GBP_SONIA, GBP_OBS); assertEquals(sensitivityComputed.Sensitivities.size(), sensitivityExpected.Length); for (int i = 0; i < sensitivityExpected.Length; ++i) { assertEquals(sensitivityComputed.Sensitivities.get(i).Sensitivity, sensitivityExpected[i], EPS_FD); } }