3

I am using QL in Python and have translated parts of the example file http://quantlib.org/reference/_fitted_bond_curve_8cpp-example.html#_a25; 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 constraints 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.

Lee
  • 383
  • 4
  • 13

1 Answers1

2

Thanks to Andres Hernandez for the answer:

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? If yes, 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.