amaggiulli / QLNet

QLNet C# Library
https://amaggiulli.github.io/QLNet/
BSD 3-Clause "New" or "Revised" License
381 stars 175 forks source link

InterpolatedZeroCurve construction using tenor dates, rates, and reference date #103

Closed grantathon closed 7 years ago

grantathon commented 8 years ago

When I create an InterpolatedZeroCurve using a specific set of dates, set of yields, day counter, interpolator, compounding, and frequency, my first date always gets anchored to time zero, which is expected. Unfortunately I'm not allowed to add a reference date within that specific overload, so any interpolations that are performed must first have the difference between the true reference date and the first tenor date subtracted from the input to the interpolation method... I need to be able to properly interpolated across the curve as well as extrapolate to the reference rate (which is one of the main reasons I need this object), which means I must include the reference date in some way without performing this reference date-first tenor date difference.

Is there be another way to supply a reference date using the above information to create an interpolated zero curve? I could use all the help I could get at this point.

QLNet Location: https://github.com/amaggiulli/qlnet/blob/master/QLNet/Termstructures/Yield/ZeroCurve.cs

Constructor: public InterpolatedZeroCurve(List<Date> dates, List<double> yields, DayCounter dayCounter, Interpolator interpolator, Compounding compounding = Compounding.Continuous, Frequency frequency = Frequency.Annual)

amaggiulli commented 8 years ago

I don'y know why initialize method of InterpolatedZeroCurve always add a 0.0 to the list of dates.

You can access ( and modify ) the list of dates after construct the InterpolatedZeroCurve :

InterpolatedZeroCurve.times_

The first time is 0.0, the second is your reference date .

grantathon commented 8 years ago

It doesn't add zero to dates. The first time 0.0 should be related to the reference date, but it's related to the first tenor date (this is not the reference date, it's the first point on the curve). The problem with this is that when you interpolate/extrapolate for the true reference rate using interpolation_.value(0.0), you get the first tenor rate which is not expected. What is expected is to get the the rate at the reference date, which is an extrapolation since this value cannot be determined in real life without this method. If I want to get the tenor rate, I just use it's tenor time, like 0.25 or whatever values I used to construct the curve.

Do you understand the problem now?

amaggiulli commented 8 years ago

I'm trying :) . Can we build a test togheter to help me understand what you need ?

grantathon commented 8 years ago

Below would be an appropriate unit test (added to T_TermStructures.cs in my local repo):

`[TestMethod()] public void testInterpolatedZeroCurveWithRefDateAndTenorDates() { CommonVars vars = new CommonVars();

        // Create the interpolated curve
        var refDate = new Date(1, 10, 2015);
        var dates = new List<Date>()
        {
            new Date(30,12,2015),
            new Date(30,3,2016),
            new Date(30,9,2016),
            new Date(29,9,2017),
            new Date(28,9,2018),
            new Date(30,9,2019),
            new Date(30,9,2020),
            new Date(30,9,2021),
            new Date(30,9,2022),
            new Date(29,9,2023),
            new Date(30,9,2024),
            new Date(30,9,2025),
            new Date(30,9,2030),
            new Date(28,9,2035),
            new Date(29,9,2045),
        };
        var yields = new List<double>()
        {
            -0.002558362,
            -0.002478462,
            -0.00248845,
            -0.002498437,
            -0.00196903,
            -0.001219628,
            -0.000209989,
            0.000940221,
            0.00220121,
            0.003493045,
            0.004785712,
            0.00602906,
            0.010909594,
            0.013132837,
            0.01403893
        };
        var curve = new InterpolatedZeroCurve<Linear>(
            refDate,
            dates,
            yields,
            new ActualActual(ActualActual.Convention.ISMA),
            new Linear(),
            Compounding.Continuous,
            Frequency.Annual);

        // Make sure the points come back as expected
        var tenors = new[] { 0.25, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 20.0, 30.0 };
        for (int i = 0; i < tenors.Length; i++)
        {
            var test = curve.interpolation_.value(tenors[i]);
            Assert.AreEqual(yields[i], curve.interpolation_.value(tenors[i]));
        }
        Assert.AreNotEqual(yields[0], curve.interpolation_.value(0.0));
    }`

Below would be the constructor for InterpolatedZeroCurve:

public InterpolatedZeroCurve(Date referenceDate, List<Date> dates, List<double> yields, DayCounter dayCounter, Interpolator interpolator, Compounding compounding = Compounding.Continuous, Frequency frequency = Frequency.Annual) : base(referenceDate, null, dayCounter) { times_ = new List<double>(); dates_ = dates; data_ = yields; interpolator_ = interpolator; initialize(compounding, frequency); }

Try it out and let me know if it works out. I will work on it too, but I'm not as familiar with the QLNet library. :)

amaggiulli commented 8 years ago

Understand now :) . Your test fail actually , what do you think doing like this :

1) Add new Date(1, 10, 2015) to the list of dates. 2) Add 0 to the list of yields 3) call unmodified ctor

this is the working test :


      [TestMethod()]
      public void testInterpolatedZeroCurveWithRefDateAndTenorDates()
      {
         CommonVars vars = new CommonVars();

        // Create the interpolated curve
        var refDate = new Date(1, 10, 2015);
        var dates = new List<Date>()
        {
            new Date(1, 10, 2015),
            new Date(30,12,2015),
            new Date(30,3,2016),
            new Date(30,9,2016),
            new Date(29,9,2017),
            new Date(28,9,2018),
            new Date(30,9,2019),
            new Date(30,9,2020),
            new Date(30,9,2021),
            new Date(30,9,2022),
            new Date(29,9,2023),
            new Date(30,9,2024),
            new Date(30,9,2025),
            new Date(30,9,2030),
            new Date(28,9,2035),
            new Date(29,9,2045),
        };
        var yields = new List<double>()
        {
           0,
            -0.002558362,
            -0.002478462,
            -0.00248845,
            -0.002498437,
            -0.00196903,
            -0.001219628,
            -0.000209989,
            0.000940221,
            0.00220121,
            0.003493045,
            0.004785712,
            0.00602906,
            0.010909594,
            0.013132837,
            0.01403893
        };
        var curve = new InterpolatedZeroCurve<Linear>(
            dates,
            yields,
            new ActualActual(ActualActual.Convention.ISMA),
            new Linear(),
            Compounding.Continuous,
            Frequency.Annual);

        // Make sure the points come back as expected
        var tenors = new[] { 0, 0.25, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 20.0, 30.0 };
        for (int i = 0; i < tenors.Length; i++)
        {
            var test = curve.interpolation_.value(tenors[i]);
            Assert.AreEqual(yields[i], curve.interpolation_.value(tenors[i]));
        }
        Assert.AreNotEqual(yields[1], curve.interpolation_.value(0.0));
    }
grantathon commented 8 years ago

This doesn't solve the problem. Again, the problem is that one must be able to extrapolate from the given dates and yields to the reference rate while still being able to interpolate across the previously set values. Due to the fact that the true value is unknown, we cannot assume that it is set to zero (which is what you've done here) and, therefore, must extrapolate using a properly created interpolated curve.

This is a very basic concept and really worries me that this is an issue in QLNet. I'm surprised no one's brought this up, but maybe not many people use the interpolation curves? Seems unlikely.

Ultimately the goal is to set the dates on the curve using the tenor dates and yields without the front tenor being anchored to zero (the first problem I brought up) and to supply the reference date (and not the reference yield, because this value is unknown in reality). With these issues resolved, we should be able to properly (a) interpolate across the curve and (b) extrapolate outside of the curve. Currently only (a) has been achieved, but only through the hack you provided in your previous post. Anyone who uses this class the way that I was using it (apparently this is the intended way) will get wrong values when both interpolating and extrapolating.

igitur commented 8 years ago

@amaggiulli hates it when I ask this ;)

But what is currently happening in QuantLib? Same problem?

grantathon commented 8 years ago

I haven't tried it in QuantLib but I probably should. Either way, the implementation is numerically wrong and I'll probably just create a wrapper class to resolve the issue. I will notify this thread with the code after I've done so.

grantathon commented 8 years ago

I just created a wrapper class to allow the proper interpolation and extrapolation, but it appears QLNet throws an exception when interpolating negative numbers.

public class InterpolatedCurveAdapter
{
    private InterpolatedCurve Curve { get; set; }

    private double TenorOffset { get; set; }

    public Date ReferenceDate { get; set; }

    public List<double> Data
    {
        get
        {
            return Curve.data();
        }
        private set { }
    }

    public List<double> Times
    {
        get
        {
            return Curve.times().Select(x => x + TenorOffset).ToList();
        }
        private set { }
    }

    public double ReferenceRate
    {
        get
        {
            return this.Interpolate(0);
        }
        private set { }
    }

    public InterpolatedCurveAdapter(Date refDate, Dictionary<Date, double> tenors, DayCounter dayCounter, Compounding compounding,
        Frequency frequency, string interpolator)
    {
        Curve = new QLNet.InterpolatedZeroCurve<QLNet.Linear>(tenors.Select(x => x.Key).ToList(), tenors.Select(x => x.Value).ToList(), 
            dayCounter, new QLNet.Linear(), compounding, frequency);
        ReferenceDate = refDate;
        TenorOffset = Curve.times().Skip(1).Min();
    }

    public double Interpolate(double value)
    {
        return Curve.interpolation_.value(value - TenorOffset);
    }
}
amaggiulli commented 8 years ago

Thanks for contribution , still I think I miss something , interpolation and extrapolation works well in both libs and are widely used by users , maybe there is a way of using the current code that work as you expect. @igitur can you investigate on quantlib forums about this ? ( and no , I dont hate when you ask about it :) )

igitur commented 8 years ago

@amaggiulli I was just teasing :-P

@grantathon , I've seen you a few times on the QuantLib forum. So I'll first try to clear this up with them first and after that we'll come back and complete it here. (PS: But then you owe me that NQuantLib project that I requested a few times from you, ok?)

grantathon commented 8 years ago

Hey @igitur, sorry for the delay. The code used to create NQuantLib64 can be found here -> https://github.com/grantathon/nquantlib64, which also contains instructions on how to build it properly.

tournierjc commented 7 years ago

Hello,

I'm new to QLNet, but I really enjoy the work you have done, thank you.

I'm facing the same issue than @grantathon. As mentionned above, giving the 0.0 value on reference date is completely wrong and not accurate, specially today when the EONIA 1 DAY is around -0.35%.

Have you made some advances regarding this?

For now the only solution is to extrapolate the first point manually when building the curve.

Thanks

amaggiulli commented 7 years ago

Finally I found some time to work on this , think this commit fix the issue : https://github.com/amaggiulli/qlnet/commit/9dde204faa0689aeff2ecf0c687fc1049bd9854e please let me know

igitur commented 7 years ago

Any idea whether I should backport this to Quantlib? I have limited connectivity and can't check the Quantlib source myself now.

amaggiulli commented 7 years ago

I will pr this to Quantlib on positive feedback by QLNet users , also need more testing when Compounding is not continuous.

tournierjc commented 7 years ago

Hello,

I can perform some tests from tuesday next week. I will let you know. Thank you for your work.

Sorry I am not familiar enough with the library to submit something on github yet...

Le 23 déc. 2016 5:31 PM, "Andrea Maggiulli" notifications@github.com a écrit :

I will pr this to Quantlib on positive feedback by QLNet users , also need more testing when Compounding is not continuous.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/amaggiulli/qlnet/issues/103#issuecomment-269014389, or mute the thread https://github.com/notifications/unsubscribe-auth/AXZfhA0LqUYW11FBPt9m0JCMjS9yI1UAks5rK_dNgaJpZM4Ji2U_ .

tournierjc commented 7 years ago

Hello,

I have tried your improvement and it's working fine on my application with all type of Compounding.

I have also performed a test with Bond pricing and I have consistent results with Bloomberg.

amaggiulli commented 7 years ago

Great ! Thank you @jiskin , @grantathon can you test it too ? New version 1.9.0 coming in few days with all develop improvements.