Fitting a Curve in QuantLib

Here is a simple example of fitting a curve in QuantLib, reworked from the original CurveFitting.cpp file and adjusted to compile with the current versions of QuantLib:

// Bojan Nikolic <bojan@bnikolic.co.uk>
//
// After orignal by Nicolas Di Cesare <Nicolas.Dicesare@free.fr> ?

#include <iostream>
#include <fstream>
#include <cmath>
#include <vector>

#include <boost/assign/std/vector.hpp>

#include <ql/quantlib.hpp>

using namespace std;

/*!
  Brace volatility term structure function
  (similar to Nelson-Siegel function for zero rate)

  sigma(t) = (a+b*t)*exp(-c*t) + d
*/
class BraceVolatilityFunction
{
  //! coefficients of the function
  double a_, b_, c_, d_;
public :
  //! Default constructor to set coeffs
  inline BraceVolatilityFunction(double a,
                                 double b,
                                 double c,
                                 double d):
    a_(a),
    b_(b),
    c_(c),
    d_(d)
  {}

  ~BraceVolatilityFunction()
  {}

  //! operator to evaluate the function at given time to maturity
  double operator() (double t)
  {
    return (a_+b_*t)*exp(-c_*t) + d_;
  }

  //! First derivative with respect to a
  double da(double t)
  {
    return exp(-c_*t);
  }

  //! First derivative with respect to b
  double db(double t)
  {
    return t*exp(-c_*t);
  }

  //! First derivative with respect to c
  double dc(double t)
  {
    return -t*(a_+b_*t)*exp(-c_*t);
  }

  //! First derivative with respect to d
  double dd(double t)
  {
    return 1.;
  }

};


/*!
  Curve Fitting Problem
 */
class CurveFittingProblem:
  public QuantLib::LeastSquareProblem
{
  const QuantLib::Array &ttm_;
  const QuantLib::Array &target_;
public :
  /*!
    Default constructor : set time to maturity vector
    and target value
  */
  CurveFittingProblem(const QuantLib::Array &ttm,
                      const QuantLib::Array &target):
    ttm_(ttm),
    target_(target)
  {}

  //! Destructor
  virtual ~CurveFittingProblem() {}

  //! Size of the least square problem
  virtual size_t size()
  {
    return ttm_.size();
  }

  //! return function and target values
  virtual void targetAndValue(const QuantLib::Array& abcd,
                              QuantLib::Array& target,
                              QuantLib::Array& fct2fit)
  {
    BraceVolatilityFunction bvf(abcd[0],
                                abcd[1],
                                abcd[2],
                                abcd[3]);

    target = target_;// target values
    for (int i=0; i<ttm_.size(); ++i)
        fct2fit[i] = bvf(ttm_[i]);

  }

  //! return function, target and first derivatives values
  virtual void targetValueAndGradient(const QuantLib::Array& abcd,
                                      QuantLib::Matrix& grad_fct2fit,
                                      QuantLib::Array& target,
                                      QuantLib::Array& fct2fit)
  {
    BraceVolatilityFunction bvf(abcd[0],abcd[1],abcd[2],abcd[3]);
    target = target_;// target values
    for (int i=0; i<ttm_.size(); ++i) {
        // function value at current point abcd
        fct2fit[i] = bvf(ttm_[i]);
        /*
            matrix of first derivatives :
            the derivatives with respect to the parameter a,b,c,d
            are stored by row.
        */
        grad_fct2fit[i][0] = bvf.da(ttm_[i]);
        grad_fct2fit[i][1] = bvf.db(ttm_[i]);
        grad_fct2fit[i][2] = bvf.dc(ttm_[i]);
        grad_fct2fit[i][3] = bvf.dd(ttm_[i]);
    }

  }
};

/*
    We define here an inverse problem to show how to fit
    parametric function to data.
*/
int main()
{
  using namespace boost::assign;

  /*
    Parameter values that produce the volatility hump.
    Consider it as optimal values of the curve fitting
    problem.
  */
  std::vector<double> abcd_;
  abcd_  +=
    0.147014,
    0.057302,
    0.249964,
    0.148556;


  QuantLib::Array abcd(abcd_.begin(), abcd_.end());


  cout << "Optimal values  : " << abcd << endl;

  // Define the target volatility function
  BraceVolatilityFunction bvf(abcd[0],
                              abcd[1],
                              abcd[2],
                              abcd[3]);

  try {

    // start date of volatilty
    double startDate = 0.0;
    // end date of volatility
    double endDate = 20.;
    // period length between values (in year fraction : quarterly)
    double period = 0.25;

    int i;
    // number of period
    size_t periodNumber = (size_t)(endDate / period);

    QuantLib::Array targetValue(periodNumber);
    QuantLib::Array timeToMaturity(periodNumber);

    // Fill target and time to maturity arrays
    for (i=0; i<periodNumber; ++i)
    {
      const double t = startDate + i*period;
      timeToMaturity[i] = t;
      targetValue[i] = bvf(t);
    }

    // Accuracy of the optimization method
    QuantLib::Real accuracy = 1e-10;// It is the square of the accuracy
    // Maximum number of iterations
    QuantLib::Size maxiter = 10000;

    QuantLib::Array initialValue(4, 0.1);

    // Least square optimizer
    QuantLib::NoConstraint nc;
    QuantLib::NonLinearLeastSquare lsqnonlin(nc,
                                             accuracy,
                                             maxiter);


    // Define the least square problem
    CurveFittingProblem cfp(timeToMaturity,
                            targetValue);

    // Set initial values
    lsqnonlin.setInitialValue(initialValue);
    // perform fitting
    QuantLib::Array solution = lsqnonlin.perform(cfp);

    cout << endl
         << "Optimal values  : " << abcd
         << "Solution values : " << solution
         << "Solution Error  : " << (solution - abcd ) << endl;

    // save in given format
    BraceVolatilityFunction bvf2(solution[0],
                                 solution[1],
                                 solution[2],
                                 solution[3]);

    std::ofstream file("curve.csv");
    const char separator[] = ", ";
    // CSV format
    for (i=0; i<periodNumber; ++i)
    {
      const double t = startDate + i*period;
      file<<t
          <<separator
          <<bvf(t)
          <<separator
          <<bvf2(t)<< endl;
    }
    file.close();

  }
  catch(QuantLib::Error & e)
  {
    cerr<<e.what()
        << endl;;
  }

  return 1;
}

Notes on updates

  • 10 August 2011 -- Update the example for the interface change in the QuantLib::Array, which is now based on an iterator range rather than vector reference