二叉樹

QuantLib 可轉換債券定價產生奇怪的 delta

  • June 8, 2020

我正在嘗試使用 QuantLib(1.14 版)函式為可轉換債券生成股權增量,但使用重新定價方法或直接從樹(程式碼如下)生成的增量都生成大於 1 的增量。

#include <ql/qldefines.hpp>
#ifdef BOOST_MSVC
#  include <ql/auto_link.hpp>
#endif
#include <ql/experimental/convertiblebonds/convertiblebond.hpp>
#include <ql/experimental/convertiblebonds/binomialconvertibleengine.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/daycounters/thirty360.hpp>
#include <ql/utilities/dataformatters.hpp>

#include <boost/timer.hpp>
#include <iostream>
#include <iomanip>

#define LENGTH(a) (sizeof(a)/sizeof(a[0]))

using namespace QuantLib;

#if defined(QL_ENABLE_SESSIONS)
namespace QuantLib {

   Integer sessionId() { return 0; }

}
#endif
template<typename Method>
auto calculate(double underlying)
{
   auto analysis_date = Date(8, May, 2020);
   Real spreadRate = 0.0125;
   Spread dividendYield = 0.0;
   Rate riskFreeRate = 0.03;
   Volatility volatility = 0.3436553822850044;
   Integer settlementDays = 0;
   Integer length = 3;
   Real redemption = 100.0;
   Real conversionRatio = 100 / 12.1; // at the money
   // set up dates/schedules
   Calendar calendar = TARGET();
   Date today = calendar.adjust(analysis_date);
   Settings::instance().evaluationDate() = today;
   Date settlementDate = calendar.advance(today, settlementDays, Days);
   Date exerciseDate = calendar.advance(settlementDate, length, Years);
   Date issueDate = calendar.advance(exerciseDate, -length, Years);
   BusinessDayConvention convention = ModifiedFollowing;
   Frequency frequency = Annual;
   Schedule schedule(issueDate, exerciseDate, Period(frequency), calendar, convention, convention, DateGeneration::Backward, false);
   DividendSchedule dividends;
   CallabilitySchedule callability;
   std::vector<Real> coupons(1, 0.05);
   DayCounter bondDayCount = Thirty360();

   for (Date d = today + 6 * Months; d < exerciseDate; d += 6 * Months)
   {
       dividends.push_back(boost::shared_ptr<Dividend>(new FixedDividend(1, d)));
   }
   DayCounter dayCounter = Actual365Fixed();
   boost::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
   boost::shared_ptr<Exercise> amExercise(new AmericanExercise(settlementDate, exerciseDate));
   Handle<Quote> underlyingH(boost::shared_ptr<Quote>(new SimpleQuote(underlying)));
   Handle<YieldTermStructure> flatTermStructure(boost::shared_ptr<YieldTermStructure>(new FlatForward(settlementDate, riskFreeRate, dayCounter)));
   Handle<YieldTermStructure> flatDividendTS(boost::shared_ptr<YieldTermStructure>(new FlatForward(settlementDate, dividendYield, dayCounter)));
   Handle<BlackVolTermStructure> flatVolTS(boost::shared_ptr<BlackVolTermStructure>(new BlackConstantVol(settlementDate, calendar, volatility, dayCounter)));
   boost::shared_ptr<BlackScholesMertonProcess> stochasticProcess(new BlackScholesMertonProcess(underlyingH, flatDividendTS, flatTermStructure, flatVolTS));
   Size timeSteps = 801;
   Handle<Quote> creditSpread(boost::shared_ptr<Quote>(new SimpleQuote(spreadRate)));
   boost::shared_ptr<Quote> rate(new SimpleQuote(riskFreeRate));
   Handle<YieldTermStructure> discountCurve(boost::shared_ptr<YieldTermStructure>(new FlatForward(today, Handle<Quote>(rate), dayCounter)));
   ConvertibleFixedCouponBond americanBond(amExercise, conversionRatio, dividends, callability, creditSpread, issueDate, settlementDays, coupons, bondDayCount,
           schedule, redemption);
   americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(new BinomialConvertibleEngine<Method>(stochasticProcess, timeSteps)));
   Real npv = americanBond.NPV();
   Real delta = americanBond.delta();
   return std::make_pair(npv, delta);
}

template<typename Method> void calc_sensitivity()
{
   auto spot=10.34;
   auto [npv, delta]= calculate<Method>(spot);
   auto [npv2, delta2]=calculate<Method>(spot*1.01);
   delta2 = (npv2 - npv) / (spot*0.01);
   // write column headings
   Size widths[] =
   { 14, 14, 14, 14 };
   Size totalWidth = widths[0] + widths[1] + widths[2] + widths[3];
   std::string rule(totalWidth, '-'), dblrule(totalWidth, '=');
   std::cout << typeid(Method).name() << std::endl;
   std::cout << dblrule << std::endl;
   std::cout << std::setw(widths[0]) << std::left << "PV0" << std::setw(widths[1]) << std::left << "PV1" << std::setw(widths[2]) << std::left
           << "Tree Delta" << std::setw(widths[3]) << std::left << "Iterative Delta" << std::endl;
   std::cout << rule << std::endl;
   std::cout << std::setw(widths[0]) << std::left << npv << std::fixed << std::setw(widths[1]) << std::left << npv2 << std::setw(widths[2]) << std::left
           << delta << std::setw(widths[2]) << std::left << delta2 << std::endl;

   std::cout << dblrule << std::endl;
}

int main(int, char*[])
{

   try
   {
       boost::timer timer;
       std::cout << std::endl;

       calc_sensitivity<JarrowRudd>();
       calc_sensitivity<CoxRossRubinstein>();
       calc_sensitivity<AdditiveEQPBinomialTree>();

       double seconds = timer.elapsed();
       Integer hours = int(seconds / 3600);
       seconds -= hours * 3600;
       Integer minutes = int(seconds / 60);
       seconds -= minutes * 60;
       std::cout << " \nRun completed in ";
       if (hours > 0)
           std::cout << hours << " h ";
       if (hours > 0 || minutes > 0)
           std::cout << minutes << " m ";
       std::cout << std::fixed << std::setprecision(0) << seconds << " s\n" << std::endl;

       return 0;
   } catch (std::exception &e)
   {
       std::cerr << e.what() << std::endl;
       return 1;
   } catch (...)
   {
       std::cerr << "unknown error" << std::endl;
       return 1;
   }

}

下面的程式碼用於在 binomialconvertibleengine.hpp 中生成增量:

convertible.initialize(lattice, maturity);
convertible.rollback(time_grid[1]);
auto value_up = convertible.values()[1];
auto value_down = convertible.values()[0];
auto s_up = tree->underlying(1, 1);
auto s_down = tree->underlying(1, 0);
auto delta = (value_up - value_down) / (s_up - s_down);

下面是結果:

N8QuantLib10JarrowRuddE
========================================================
PV0           PV1           Tree Delta    Iterative Delta
--------------------------------------------------------
104.455       104.677186    1.985473      2.148005      
========================================================
N8QuantLib17CoxRossRubinsteinE
========================================================
PV0           PV1           Tree Delta    Iterative Delta
--------------------------------------------------------
104.454988    104.673906    1.984574      2.117198      
========================================================
N8QuantLib23AdditiveEQPBinomialTreeE
========================================================
PV0           PV1           Tree Delta    Iterative Delta
--------------------------------------------------------
104.480229    104.704433    1.996642      2.168310      
========================================================

Remark: Should have divided by the conversion ratio, the updated result is as below:
========================================================
PV0           PV1           Tree Delta    Iterative Delta
--------------------------------------------------------
110.041       110.544284    0.530003      0.588715      
========================================================
now the deltas looks nice.

你的轉化率是 $ 100/12.1 \approx 8.26 $ ,因此可兌換性是大約 8 只標的股票的一種選擇,並且增量相應地縮放。不過,我不熟悉它的引用方式。您是否期望它是一單位股票的增量?

引用自:https://quant.stackexchange.com/questions/54733