Nelson Siegel with constraints

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Nelson Siegel with constraints

Laurent Millischer
Dear all,

I am using QL in Python and have translated parts of the example file of how to fit a yield curve with bonds in order to fit a Nelson-Siegel yield curve to a set of given calibration bonds.

As usual when performing such a non-linear fit, the results depend strongly on the initial conditions and many (economically meaningless) minima of the objective function exist. This is why putting constraints on the parameters is essential for success. To give an example, at times I get negative tau/lambda parameters and my yield curve diverges.

I did not find how these parameter contraints can be specified in the NelsonSiegelFitting or the FittedBondDiscountCurve classes. I could imagine that anyone performing NS fitting in QL will encounter the same issue.

Thanks a lot in advance for your help.

Regards,
Laurent

------------------------------------------------------------------------------

_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
Reply | Threaded
Open this post in threaded view
|

Re: Nelson Siegel with constraints

Andres Hernandez
Hi Laurent,
 
currently it is not possible. However, it is very easy to extend QL to allow it, but I think it needs to be done on the c++. So even though you are using QL in python, can you modify the c++ code and export a new binding? There are two ways that could be done, one is to give a new parameter to the FittedBondDiscountCurve with the constraint. This would anyway be a good long-term idea. However, I prefer a second option of having a nelson siegel fitting method which rescales the parameters from (-infinity, infinity) to (lower, higher) so that the optimizer can do unconstrianed optimization, as not all optimizers can handle all types of constraints. If you can touch the c++ code, then you can use the following code, if not then I could just check it into the code, but it will take some time for the pull request to be accepted. In case you can touch the code, you can add something like this:
 
in nonlinearfittingmethods.hpp:
 
 
  class NelsonSiegelConstrainedFitting
        : public FittedBondDiscountCurve::FittingMethod {
      public:
        NelsonSiegelConstrainedFitting(const Array& lower, const Array& upper,
                           const Array& weights = Array(),
                            boost::shared_ptr<OptimizationMethod> optimizationMethod
                                          = boost::shared_ptr<OptimizationMethod>());
        std::auto_ptr<FittedBondDiscountCurve::FittingMethod> clone() const;
      private:
        Size size() const;
        DiscountFactor discountFunction(const Array& x, Time t) const;
        Array lower_, upper_;
    };
 
 
in nonlinearfittingmethods.cpp:
    NelsonSiegelConstrainedFitting::NelsonSiegelConstrainedFitting(
                                             const Array& lower, const Array& upper, const Array& weights,
                                             boost::shared_ptr<OptimizationMethod> optimizationMethod)
    : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod),
      lower_(lower), upper_(upper){
        QL_REQUIRE(lower_.size() == 4, "Lower constraint must have 4 elements");
        QL_REQUIRE(upper_.size() == 4, "Lower constraint must have 4 elements");
    }
    std::auto_ptr<FittedBondDiscountCurve::FittingMethod>
    NelsonSiegelConstrainedFitting::clone() const {
        return std::auto_ptr<FittedBondDiscountCurve::FittingMethod>(
                                              new NelsonSiegelFitting(*this));
    }
    Size NelsonSiegelConstrainedFitting::size() const {
        return 4;
    }
    DiscountFactor NelsonSiegelConstrainedFitting::discountFunction(const Array& x,
                                                         Time t) const {
        ///extreme values of kappa result in colinear behaviour of x[1] and x[2], so it should be constrained not only
        ///to be positive, but also not very extreme
        Real kappa = lower_[3] + upper_[3]/(1.0+exp(-x[3]));
        Real x0 = lower_[0] + upper_[0]/(1.0+exp(-x[0])),
                x1 = lower_[1] + upper_[1]/(1.0+exp(-x[1])),
                x2 = lower_[2] + upper_[2]/(1.0+exp(-x[2])),;
        Real zeroRate = x0 + (x1 + x2)*
                            (1.0 - std::exp(-kappa*t))/
                            ((kappa+QL_EPSILON)*(t+QL_EPSILON)) -
                            x2*std::exp(-kappa*t);
        DiscountFactor d = std::exp(-zeroRate * t) ;
        return d;
    }
 
 
You then need to add it to the swig interface, but it should be trivial to do so.
 
cheers,
Andres
 
 
----- Original message -----
From: Laurent Millischer <[hidden email]>
To: [hidden email]
Cc:
Subject: [Quantlib-users] Nelson Siegel with constraints
Date: Wed, Aug 17, 2016 12:18 PM
Dear all,
 
I am using QL in Python and have translated parts of the example file of how to fit a yield curve with bonds in order to fit a Nelson-Siegel yield curve to a set of given calibration bonds.
 
As usual when performing such a non-linear fit, the results depend strongly on the initial conditions and many (economically meaningless) minima of the objective function exist. This is why putting constraints on the parameters is essential for success. To give an example, at times I get negative tau/lambda parameters and my yield curve diverges.
 
I did not find how these parameter contraints can be specified in the NelsonSiegelFitting or the FittedBondDiscountCurve classes. I could imagine that anyone performing NS fitting in QL will encounter the same issue.
 
Thanks a lot in advance for your help.
 
Regards,
Laurent
------------------------------------------------------------------------------
_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
 


------------------------------------------------------------------------------

_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
Reply | Threaded
Open this post in threaded view
|

Re: Nelson Siegel with constraints

Laurent Millischer
Dear Andres,

thanks a lot for the answer. I will try and change my local C++ code following your instructions and recompile. However my C++ skills being limited, for the overall improvement of the library, I would be grateful if you could have a go at it.

There are a number of further features that are quite handy when fitting NS/NSS curves, which I currently cannot find (please correct me if I missed it) in QuantLib:

1) Constraints on combination of parameters. If one wants to constrain the long end of the curve, constraining c0 is enough. To constrain the short end one has to constrain a combination of parameters.

2) Speaking of which, the explicit formula given here (http://quantlib.org/reference/class_quant_lib_1_1_nelson_siegel_fitting.html) is lacking a parenthesis and is not consistent with the one implemented in the code here (https://github.com/lballabio/QuantLib/blob/053689252a9d4137d8ac47dbfb88717b0c369d35/ql/termstructures/yield/nonlinearfittingmethods.cpp). The short end of the curve is given by c0+c1 (which is the combination one would like to constrain as well).

3) Setting the number of iteration. That can be done via the FittedBondDiscountCurve constructor. However it is not possible to have him iterate only once (to graphically check the initial conditions, say) because maxStationaryStateIterations_ must be less than maxIterations_ without it being possible (maybe only in Pyhton) to control maxStationaryStateIterations_.

4) Fitting prices vs fitting yields. In some cases it is useful to define the objective/cost function as the sum of yield-to-maturity differences rather than the sum of price differences. Computations are slightly more complex in that case

5) Also, it would of course be great if one could create a YieldCurve object by simply inputting NS or NSS parameters. 

6) Finally, from here (http://quant.stackexchange.com/a/29464/24022) I understand it is not yet possible to set the minimisation method from within Python (so the Simplex optimisation process is used by default). 

Again, I juste wanted to post these remarks from a user's perspective and would gladly hear that I overlooked some features.

Kind regards,
Laurent

On 18 August 2016 at 08:45, Andres Hernandez <[hidden email]> wrote:
Hi Laurent,
 
currently it is not possible. However, it is very easy to extend QL to allow it, but I think it needs to be done on the c++. So even though you are using QL in python, can you modify the c++ code and export a new binding? There are two ways that could be done, one is to give a new parameter to the FittedBondDiscountCurve with the constraint. This would anyway be a good long-term idea. However, I prefer a second option of having a nelson siegel fitting method which rescales the parameters from (-infinity, infinity) to (lower, higher) so that the optimizer can do unconstrianed optimization, as not all optimizers can handle all types of constraints. If you can touch the c++ code, then you can use the following code, if not then I could just check it into the code, but it will take some time for the pull request to be accepted. In case you can touch the code, you can add something like this:
 
in nonlinearfittingmethods.hpp:
 
 
  class NelsonSiegelConstrainedFitting
        : public FittedBondDiscountCurve::FittingMethod {
      public:
        NelsonSiegelConstrainedFitting(const Array& lower, const Array& upper,
                           const Array& weights = Array(),
                            boost::shared_ptr<OptimizationMethod> optimizationMethod
                                          = boost::shared_ptr<OptimizationMethod>());
        std::auto_ptr<FittedBondDiscountCurve::FittingMethod> clone() const;
      private:
        Size size() const;
        DiscountFactor discountFunction(const Array& x, Time t) const;
        Array lower_, upper_;
    };
 
 
in nonlinearfittingmethods.cpp:
    NelsonSiegelConstrainedFitting::NelsonSiegelConstrainedFitting(
                                             const Array& lower, const Array& upper, const Array& weights,
                                             boost::shared_ptr<OptimizationMethod> optimizationMethod)
    : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod),
      lower_(lower), upper_(upper){
        QL_REQUIRE(lower_.size() == 4, "Lower constraint must have 4 elements");
        QL_REQUIRE(upper_.size() == 4, "Lower constraint must have 4 elements");
    }
    std::auto_ptr<FittedBondDiscountCurve::FittingMethod>
    NelsonSiegelConstrainedFitting::clone() const {
        return std::auto_ptr<FittedBondDiscountCurve::FittingMethod>(
                                              new NelsonSiegelFitting(*this));
    }
    Size NelsonSiegelConstrainedFitting::size() const {
        return 4;
    }
    DiscountFactor NelsonSiegelConstrainedFitting::discountFunction(const Array& x,
                                                         Time t) const {
        ///extreme values of kappa result in colinear behaviour of x[1] and x[2], so it should be constrained not only
        ///to be positive, but also not very extreme
        Real kappa = lower_[3] + upper_[3]/(1.0+exp(-x[3]));
        Real x0 = lower_[0] + upper_[0]/(1.0+exp(-x[0])),
                x1 = lower_[1] + upper_[1]/(1.0+exp(-x[1])),
                x2 = lower_[2] + upper_[2]/(1.0+exp(-x[2])),;
        Real zeroRate = x0 + (x1 + x2)*
                            (1.0 - std::exp(-kappa*t))/
                            ((kappa+QL_EPSILON)*(t+QL_EPSILON)) -
                            x2*std::exp(-kappa*t);
        DiscountFactor d = std::exp(-zeroRate * t) ;
        return d;
    }
 
 
You then need to add it to the swig interface, but it should be trivial to do so.
 
cheers,
Andres
 
 
----- Original message -----
From: Laurent Millischer <[hidden email]>
To: [hidden email]
Cc:
Subject: [Quantlib-users] Nelson Siegel with constraints
Date: Wed, Aug 17, 2016 12:18 PM
Dear all,
 
I am using QL in Python and have translated parts of the example file of how to fit a yield curve with bonds in order to fit a Nelson-Siegel yield curve to a set of given calibration bonds.
 
As usual when performing such a non-linear fit, the results depend strongly on the initial conditions and many (economically meaningless) minima of the objective function exist. This is why putting constraints on the parameters is essential for success. To give an example, at times I get negative tau/lambda parameters and my yield curve diverges.
 
I did not find how these parameter contraints can be specified in the NelsonSiegelFitting or the FittedBondDiscountCurve classes. I could imagine that anyone performing NS fitting in QL will encounter the same issue.
 
Thanks a lot in advance for your help.
 
Regards,
Laurent
------------------------------------------------------------------------------
_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
 



------------------------------------------------------------------------------

_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
Reply | Threaded
Open this post in threaded view
|

Re: Nelson Siegel with constraints

Andres Hernandez
Dear Laurent,
 
1) In the implementation that we use, we actually have x2 = exp(x[2]) + x1, so it constraints x2 > x1, instead of lower < x2 < higher, however, I didn't want to give too much complication in my answer. You can certainly change the constraints how you prefer. I had actually not checked it in because it seemed complicated to make it generic.
 
3) The max stationary iterations can be controlled in c++, so if it cannot be done from python it would simply be a matter of modifying the swig definitions.
 
4) Fitting prices vs fitting yields should be possible. I have some further changes that I wanted to do, and this could be part of it, but hadn't had time. Basically I want to move the fitted curve away from being specific for bonds, to simply using generic bootstrap helpers. In that case then, the choice of error should be left up to the helper, like it is for the calibration helpers.
 
5) About the YieldCurve object from parameters (without calibrating to instruments), if you give the maxEvaluations equal to 0, then the curve doesn't ever try to calibrate to instruments, and just uses the current parameters.
 
6) Same as 3, as it is controllable from c++, if it is not already possible from python, it would simply be a matter of modifying the swig definitions.
 
cheers,
Andres
 
----- Original message -----
From: Laurent Millischer <[hidden email]>
To: Andres Hernandez/Germany/IBM@IBMDE
Cc: [hidden email]
Subject: Re: [Quantlib-users] Nelson Siegel with constraints
Date: Thu, Aug 18, 2016 11:18 AM
 
Dear Andres,
 
thanks a lot for the answer. I will try and change my local C++ code following your instructions and recompile. However my C++ skills being limited, for the overall improvement of the library, I would be grateful if you could have a go at it.
 
There are a number of further features that are quite handy when fitting NS/NSS curves, which I currently cannot find (please correct me if I missed it) in QuantLib:
 
1) Constraints on combination of parameters. If one wants to constrain the long end of the curve, constraining c0 is enough. To constrain the short end one has to constrain a combination of parameters.
 
2) Speaking of which, the explicit formula given here (http://quantlib.org/reference/class_quant_lib_1_1_nelson_siegel_fitting.html) is lacking a parenthesis and is not consistent with the one implemented in the code here (https://github.com/lballabio/QuantLib/blob/053689252a9d4137d8ac47dbfb88717b0c369d35/ql/termstructures/yield/nonlinearfittingmethods.cpp). The short end of the curve is given by c0+c1 (which is the combination one would like to constrain as well).
 
3) Setting the number of iteration. That can be done via the FittedBondDiscountCurve constructor. However it is not possible to have him iterate only once (to graphically check the initial conditions, say) because maxStationaryStateIterations_ must be less than maxIterations_ without it being possible (maybe only in Pyhton) to control maxStationaryStateIterations_.
 
4) Fitting prices vs fitting yields. In some cases it is useful to define the objective/cost function as the sum of yield-to-maturity differences rather than the sum of price differences. Computations are slightly more complex in that case
 
5) Also, it would of course be great if one could create a YieldCurve object by simply inputting NS or NSS parameters. 
 
6) Finally, from here (http://quant.stackexchange.com/a/29464/24022) I understand it is not yet possible to set the minimisation method from within Python (so the Simplex optimisation process is used by default). 
 
Again, I juste wanted to post these remarks from a user's perspective and would gladly hear that I overlooked some features.
 
Kind regards,
Laurent
 
On 18 August 2016 at 08:45, Andres Hernandez <[hidden email]> wrote:
Hi Laurent,
 
currently it is not possible. However, it is very easy to extend QL to allow it, but I think it needs to be done on the c++. So even though you are using QL in python, can you modify the c++ code and export a new binding? There are two ways that could be done, one is to give a new parameter to the FittedBondDiscountCurve with the constraint. This would anyway be a good long-term idea. However, I prefer a second option of having a nelson siegel fitting method which rescales the parameters from (-infinity, infinity) to (lower, higher) so that the optimizer can do unconstrianed optimization, as not all optimizers can handle all types of constraints. If you can touch the c++ code, then you can use the following code, if not then I could just check it into the code, but it will take some time for the pull request to be accepted. In case you can touch the code, you can add something like this:
 
in nonlinearfittingmethods.hpp:
 
 
  class NelsonSiegelConstrainedFitting
        : public FittedBondDiscountCurve::FittingMethod {
      public:
        NelsonSiegelConstrainedFitting(const Array& lower, const Array& upper,
                           const Array& weights = Array(),
                            boost::shared_ptr<OptimizationMethod> optimizationMethod
                                          = boost::shared_ptr<OptimizationMethod>());
        std::auto_ptr<FittedBondDiscountCurve::FittingMethod> clone() const;
      private:
        Size size() const;
        DiscountFactor discountFunction(const Array& x, Time t) const;
        Array lower_, upper_;
    };
 
 
in nonlinearfittingmethods.cpp:
    NelsonSiegelConstrainedFitting::NelsonSiegelConstrainedFitting(
                                             const Array& lower, const Array& upper, const Array& weights,
                                             boost::shared_ptr<OptimizationMethod> optimizationMethod)
    : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod),
      lower_(lower), upper_(upper){
        QL_REQUIRE(lower_.size() == 4, "Lower constraint must have 4 elements");
        QL_REQUIRE(upper_.size() == 4, "Lower constraint must have 4 elements");
    }
    std::auto_ptr<FittedBondDiscountCurve::FittingMethod>
    NelsonSiegelConstrainedFitting::clone() const {
        return std::auto_ptr<FittedBondDiscountCurve::FittingMethod>(
                                              new NelsonSiegelFitting(*this));
    }
    Size NelsonSiegelConstrainedFitting::size() const {
        return 4;
    }
    DiscountFactor NelsonSiegelConstrainedFitting::discountFunction(const Array& x,
                                                         Time t) const {
        ///extreme values of kappa result in colinear behaviour of x[1] and x[2], so it should be constrained not only
        ///to be positive, but also not very extreme
        Real kappa = lower_[3] + upper_[3]/(1.0+exp(-x[3]));
        Real x0 = lower_[0] + upper_[0]/(1.0+exp(-x[0])),
                x1 = lower_[1] + upper_[1]/(1.0+exp(-x[1])),
                x2 = lower_[2] + upper_[2]/(1.0+exp(-x[2])),;
        Real zeroRate = x0 + (x1 + x2)*
                            (1.0 - std::exp(-kappa*t))/
                            ((kappa+QL_EPSILON)*(t+QL_EPSILON)) -
                            x2*std::exp(-kappa*t);
        DiscountFactor d = std::exp(-zeroRate * t) ;
        return d;
    }
 
 
You then need to add it to the swig interface, but it should be trivial to do so.
 
cheers,
Andres
 
 
----- Original message -----
From: Laurent Millischer <[hidden email]>
To: [hidden email]
Cc:
Subject: [Quantlib-users] Nelson Siegel with constraints
Date: Wed, Aug 17, 2016 12:18 PM
Dear all,
 
I am using QL in Python and have translated parts of the example file of how to fit a yield curve with bonds in order to fit a Nelson-Siegel yield curve to a set of given calibration bonds.
 
As usual when performing such a non-linear fit, the results depend strongly on the initial conditions and many (economically meaningless) minima of the objective function exist. This is why putting constraints on the parameters is essential for success. To give an example, at times I get negative tau/lambda parameters and my yield curve diverges.
 
I did not find how these parameter contraints can be specified in the NelsonSiegelFitting or the FittedBondDiscountCurve classes. I could imagine that anyone performing NS fitting in QL will encounter the same issue.
 
Thanks a lot in advance for your help.
 
Regards,
Laurent
------------------------------------------------------------------------------
_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
 
 


------------------------------------------------------------------------------

_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users