I am trying to generate equity delta for convertible bond using QuantLib(version 1.14) functions, but the deltas generated either using a repricing approach or by directly obtaining from the tree(code as below) all generates deltas bigger than 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;
}
}
below code for generating the delta in 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);
below is the result:
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.
2.1683/8.26
is not0.588715
though? $\endgroup$