2525
2626#include < ql/math/comparison.hpp>
2727#include < ql/termstructures/interpolatedcurve.hpp>
28+ #include < ql/termstructures/yield/forwardstructure.hpp>
29+ #include < ql/termstructures/yield/zeroyieldstructure.hpp>
2830#include < ql/termstructures/yieldtermstructure.hpp>
2931
3032namespace QuantExt {
@@ -86,7 +88,7 @@ class InterpolatedPillarOnlyDiscountCurve : public YieldTermStructure, protected
8688 \ingroup termstructures
8789*/
8890template <class Interpolator >
89- class InterpolatedPillarOnlyZeroCurve : public YieldTermStructure , protected InterpolatedCurve <Interpolator> {
91+ class InterpolatedPillarOnlyZeroCurve : public ZeroYieldStructure , protected InterpolatedCurve <Interpolator> {
9092public:
9193 // ! \name Constructors
9294 // @{
@@ -102,9 +104,9 @@ class InterpolatedPillarOnlyZeroCurve : public YieldTermStructure, protected Int
102104 // @}
103105
104106protected:
105- // ! \name YieldTermStructure implementation
107+ // ! \name ZeroYieldStructure implementation
106108 // @{
107- DiscountFactor discountImpl (Time t) const override ;
109+ DiscountFactor zeroYieldImpl (Time t) const override ;
108110 // @}
109111
110112private:
@@ -126,7 +128,7 @@ class InterpolatedPillarOnlyZeroCurve : public YieldTermStructure, protected Int
126128 \ingroup termstructures
127129*/
128130template <class Interpolator >
129- class InterpolatedPillarOnlyForwardCurve : public YieldTermStructure , protected InterpolatedCurve <Interpolator> {
131+ class InterpolatedPillarOnlyForwardCurve : public ForwardRateStructure , protected InterpolatedCurve <Interpolator> {
130132public:
131133 // ! \name Constructors
132134 // @{
@@ -142,9 +144,10 @@ class InterpolatedPillarOnlyForwardCurve : public YieldTermStructure, protected
142144 // @}
143145
144146protected:
145- // ! \name YieldTermStructure implementation
147+ // ! \name ForwardRateStructure implementation
146148 // @{
147- DiscountFactor discountImpl (Time t) const override ;
149+ Rate forwardImpl (Time t) const override ;
150+ Rate zeroYieldImpl (Time t) const override ;
148151 // @}
149152
150153private:
@@ -200,21 +203,29 @@ DiscountFactor InterpolatedPillarOnlyDiscountCurve<Interpolator>::discountImpl(T
200203
201204 // Interpolate on pillars for times within the curve range
202205 if (t <= this ->times_ .back ()) {
203- return this ->interpolation_ (t, this -> allowsExtrapolation () );
206+ return this ->interpolation_ (t, true );
204207 }
205208
206209 // Flat forward extrapolation beyond the last pillar
207210 Time tMax = this ->times_ .back ();
208211 DiscountFactor dMax = this ->data_ .back ();
209- Rate instFwdMax = -this ->interpolation_ .derivative (tMax) / dMax;
210- return dMax * std::exp (-instFwdMax * (t - tMax));
212+ if (extrapolation_ == YieldTermStructure::Extrapolation::ContinuousForward) {
213+ Rate instFwdMax = -this ->interpolation_ .derivative (tMax, true ) / dMax;
214+ return dMax * std::exp (-instFwdMax * (t - tMax));
215+ } else if (extrapolation_ == YieldTermStructure::Extrapolation::DiscreteForward) {
216+ Time tMax_m = this ->timeFromReference (dates_.back () - 1 );
217+ DiscountFactor dMax_m = this ->interpolation_ (tMax_m, true );
218+ return dMax * std::pow (dMax / dMax_m, (t - tMax) / (tMax - tMax_m));
219+ } else {
220+ QL_FAIL (" extrapolation method not handled." );
221+ }
211222}
212223
213224template <class Interpolator >
214225InterpolatedPillarOnlyZeroCurve<Interpolator>::InterpolatedPillarOnlyZeroCurve(
215226 const Date& referenceDate, const std::vector<Date>& dates, const std::vector<Rate>& zeroRates,
216227 const DayCounter& dayCounter, const Interpolator& interpolator, const Extrapolation extrapolation)
217- : YieldTermStructure (referenceDate, Calendar(), dayCounter), InterpolatedCurve<Interpolator>(interpolator),
228+ : ZeroYieldStructure (referenceDate, Calendar(), dayCounter), InterpolatedCurve<Interpolator>(interpolator),
218229 extrapolation_ (extrapolation), dates_(dates) {
219230
220231 QL_REQUIRE (!dates.empty (), " InterpolatedPillarOnlyZeroCurve: dates cannot be empty" );
@@ -237,35 +248,35 @@ template <class Interpolator> Date InterpolatedPillarOnlyZeroCurve<Interpolator>
237248 return dates_.back ();
238249}
239250
240- template <class Interpolator > DiscountFactor InterpolatedPillarOnlyZeroCurve<Interpolator>::discountImpl(Time t) const {
241- // At reference date (t=0), return 1.0 by definition
242- if (t <= 0.0 || QuantLib::close_enough (t, 0.0 )) {
243- return 1.0 ;
244- }
245-
246- Rate zeroRate;
247- // For times between t=0 and first pillar, flat extrapolation using first pillar rate
251+ template <class Interpolator > DiscountFactor InterpolatedPillarOnlyZeroCurve<Interpolator>::zeroYieldImpl(Time t) const {
248252 if (t < this ->times_ .front ()) {
249- zeroRate = this ->data_ .front ();
250- }
251- // Interpolate on pillars for times within the curve range
252- else if (t <= this ->times_ .back ()) {
253- zeroRate = this ->interpolation_ (t, this ->allowsExtrapolation ());
254- }
255- // Flat extrapolation beyond the last pillar
256- else {
257- zeroRate = this ->data_ .back ();
253+ // For times between t=0 and first pillar, flat extrapolation using first pillar rate
254+ return this ->data_ .front ();
255+ } else if (t <= this ->times_ .back ()) {
256+ // Interpolate on pillars for times within the curve range
257+ return this ->interpolation_ (t, true );
258+ } else {
259+ // flat fwd extrapolation
260+ Time tMax = this ->times_ .back ();
261+ Rate zMax = this ->data_ .back ();
262+ if (extrapolation_ == YieldTermStructure::Extrapolation::ContinuousForward) {
263+ Rate instFwdMax = zMax + tMax * this ->interpolation_ .derivative (tMax, true );
264+ return (zMax * tMax + instFwdMax * (t - tMax)) / t;
265+ } else if (extrapolation_ == YieldTermStructure::Extrapolation::DiscreteForward) {
266+ Time tMax_m = this ->timeFromReference (dates_.back () - 1 );
267+ Rate dz = this ->interpolation_ (tMax) - this ->interpolation_ (tMax_m, true );
268+ return (zMax * tMax + dz * (t - tMax) / (tMax - tMax_m)) / t;
269+ } else {
270+ QL_FAIL (" extrapolation method not handled." );
271+ }
258272 }
259-
260- // Convert zero rate to discount factor: DF = exp(-r * t)
261- return std::exp (-zeroRate * t);
262273}
263274
264275template <class Interpolator >
265276InterpolatedPillarOnlyForwardCurve<Interpolator>::InterpolatedPillarOnlyForwardCurve(
266277 const Date& referenceDate, const std::vector<Date>& dates, const std::vector<Rate>& forwardRates,
267278 const DayCounter& dayCounter, const Interpolator& interpolator, const Extrapolation extrapolation)
268- : YieldTermStructure (referenceDate, Calendar(), dayCounter), InterpolatedCurve<Interpolator>(interpolator),
279+ : ForwardRateStructure (referenceDate, Calendar(), dayCounter), InterpolatedCurve<Interpolator>(interpolator),
269280 extrapolation_ (extrapolation), dates_(dates) {
270281
271282 QL_REQUIRE (!dates.empty (), " InterpolatedPillarOnlyForwardCurve: dates cannot be empty" );
@@ -289,29 +300,58 @@ template <class Interpolator> Date InterpolatedPillarOnlyForwardCurve<Interpolat
289300}
290301
291302template <class Interpolator >
292- DiscountFactor InterpolatedPillarOnlyForwardCurve<Interpolator>::discountImpl (Time t) const {
293- // At reference date (t=0), return 1.0 by definition
294- if (t <= 0.0 || QuantLib::close_enough (t, 0.0 )) {
295- return 1.0 ;
303+ DiscountFactor InterpolatedPillarOnlyForwardCurve<Interpolator>::forwardImpl (Time t) const {
304+ if (t < this -> times_ . front ()) {
305+ Rate f = this -> data_ . front ();
306+ return f ;
296307 }
297308
309+ if (t <= this ->times_ .back ()) {
310+ return this ->interpolation_ (t, true );
311+ } else {
312+ // flat fwd extrapolation
313+ if (extrapolation_ == YieldTermStructure::Extrapolation::ContinuousForward) {
314+ return this ->data_ .back ();
315+ } else if (extrapolation_ == YieldTermStructure::Extrapolation::DiscreteForward) {
316+ Time tMax = this ->times_ .back ();
317+ Time tMax_m = this ->timeFromReference (dates_.back () - 1 );
318+ Real iMax = this ->interpolation_ .primitive (tMax, true );
319+ Real iMax_m = this ->interpolation_ .primitive (tMax_m, true );
320+ return (iMax - iMax_m) / (tMax - tMax_m);
321+ } else {
322+ QL_FAIL (" extrapolation method not handled." );
323+ }
324+ }
325+ }
326+
327+ template <class Interpolator >
328+ DiscountFactor InterpolatedPillarOnlyForwardCurve<Interpolator>::zeroYieldImpl(Time t) const {
298329 // For times between t=0 and first pillar, use flat forward from first pillar
299330 if (t < this ->times_ .front ()) {
300331 Rate f = this ->data_ .front ();
301- return std::exp (-f * t) ;
332+ return f ;
302333 }
303334
304335 // For times within the curve range, integrate forward rates
336+ Real integral;
305337 if (t <= this ->times_ .back ()) {
306- // Use numerical integration of forward rates
307- return std::exp (-this ->interpolation_ .primitive (t, this ->allowsExtrapolation ()));
338+ integral = this ->interpolation_ .primitive (t, true );
339+ } else {
340+ // flat fwd extrapolation
341+ if (extrapolation_ == YieldTermStructure::Extrapolation::ContinuousForward) {
342+ integral = this ->interpolation_ .primitive (this ->times_ .back (), true ) +
343+ this ->data_ .back () * (t - this ->times_ .back ());
344+ } else if (extrapolation_ == YieldTermStructure::Extrapolation::DiscreteForward) {
345+ Time tMax = this ->times_ .back ();
346+ Time tMax_m = this ->timeFromReference (dates_.back () - 1 );
347+ Real iMax = this ->interpolation_ .primitive (tMax, true );
348+ Real iMax_m = this ->interpolation_ .primitive (tMax_m, true );
349+ integral = iMax + (iMax - iMax_m) * (t - tMax) / (tMax - tMax_m);
350+ } else {
351+ QL_FAIL (" extrapolation method not handled." );
352+ }
308353 }
309-
310- // Flat extrapolation beyond the last pillar
311- Rate fMax = this ->data_ .back ();
312- Time tMax = this ->times_ .back ();
313- DiscountFactor dMax = std::exp (-this ->interpolation_ .primitive (tMax, this ->allowsExtrapolation ()));
314- return dMax * std::exp (-fMax * (t - tMax));
354+ return integral / t;
315355}
316356
317357} // namespace QuantExt
0 commit comments