Login  Register

possible bug in convert pricer: when notional is not 100

classic Classic list List threaded Threaded
2 messages Options Options
Embed post
Permalink
Reply | Threaded
Open this post in threaded view
| More
Print post
Permalink

possible bug in convert pricer: when notional is not 100

P Nelnik
36 posts
It seems that in the convertible bond pricer,
the coupons paid ignore the 'redemption' input and assume that the redemption is 100
 
So if we have zero interest rate, a coupon of 10% paid with a redeption of 1000 units of currency in a 1 year convert,
with a very low convertion ratio,
then I would expect a value of 1100 units of currency.
But in fact I get (approximately) 1010 units of currency.
i.e. the 10% coupon has been paid on a notional of 100,
even though I specified it to be 1000 rather than 100.
 
Perhpas this isn't a bug it is a 'feature' ?
 
To see this in action, I've made a few small changes to the ConvertibleBond example:
 
 
 
 // the only header you need to use QuantLib
#include <ql/quantlib.hpp>
#ifdef BOOST_MSVC
/* Uncomment the following lines to unmask floating-point
   exceptions. Warning: unpredictable results can arise...
   See http://www.wilmott.com/messageview.cfm?catid=10&threadid=9481
   Is there anyone with a definitive word about this?
*/
// #include <float.h>
// namespace { unsigned int u = _controlfp(_EM_INEXACT, _MCW_EM); }
#endif
#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

int main(int, char* []) {
    try {
        boost::timer timer;
        std::cout << std::endl;
        Option::Type type(Option::Put);
        Real underlying        = 100.0;
        Real spreadRate        = 0.000;
        Spread dividendYield   = 0.00;
        Rate riskFreeRate      = 0.00;
        Volatility volatility  = 0.01;
        Integer settlementDays = 1;
        Integer length         = 1;
        Real redemption        = 1000.0;
        Real conversionRatio   = 0.01 * redemption/underlying; // 2 * at the money
        // set up dates/schedules
        Calendar calendar = TARGET();
        Date today = calendar.adjust(Date::todaysDate());
        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.1); // a 10% coupon
        DayCounter bondDayCount = Thirty360();
/* no callability
  Integer callLength[] = { 1 };  // just one call date at maturity
        Integer putLength[] = {  }; // no put date
        Real callPrices[] = { 101.5, 100.85 };
        Real putPrices[]= { 105.0 };
        // Load call schedules
        for (Size i=0; i<LENGTH(callLength); i++) {
            callability.push_back(
                   boost::shared_ptr<Callability>(
                       new SoftCallability(Callability::Price(
                                                   callPrices[i],
                                                   Callability::Price::Clean),
                                           schedule.date(callLength[i]),
                                           1.20)));
        }
        for (Size j=0; j<LENGTH(putLength); j++) {
            callability.push_back(
                   boost::shared_ptr<Callability>(
                           new Callability(Callability::Price(
                                                   putPrices[j],
                                                   Callability::Price::Clean),
                                           Callability::Put,
                                           schedule.date(putLength[j]))));
        }
*/
        // Assume dividends are paid every 6 months, but here they are zero.
        for (Date d = today + 6*Months; d < exerciseDate; d += 6*Months) {
            dividends.push_back(
                      boost::shared_ptr<Dividend>(new FixedDividend(0, d)));
        }
        DayCounter dayCounter = Actual365Fixed();
        Time maturity = dayCounter.yearFraction(settlementDate,
                                                exerciseDate);
        std::cout << "option type = "  << type << std::endl;
        std::cout << "Time to maturity = "        << maturity
                  << std::endl;
        std::cout << "Underlying price = "        << underlying
                  << std::endl;
        std::cout << "Risk-free interest rate = " << io::rate(riskFreeRate)
                  << std::endl;
        std::cout << "Dividend yield = " << io::rate(dividendYield)
                  << std::endl;
        std::cout << "Volatility = " << io::volatility(volatility)
                  << std::endl;
        std::cout << std::endl;
        std::string method;
        std::cout << std::endl ;
        // write column headings
        Size widths[] = { 35, 14, 14 };
        Size totalWidth = widths[0] + widths[1] + widths[2];
        std::string rule(totalWidth, '-'), dblrule(totalWidth, '=');
        std::cout << dblrule << std::endl;
        std::cout << "Tsiveriotis-Fernandes method" << std::endl;
        std::cout << dblrule << std::endl;
        std::cout << std::setw(widths[0]) << std::left << "Tree type"
                  << std::setw(widths[1]) << std::left << "European"
                  << std::setw(widths[1]) << std::left << "American"
                  << std::endl;
        std::cout << rule << std::endl;
        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)));
        boost::shared_ptr<PricingEngine> engine(
                  new BinomialConvertibleEngine<JarrowRudd>(stochasticProcess,
                                                            timeSteps));
        ConvertibleFixedCouponBond europeanBond(
                            exercise, conversionRatio, dividends, callability,
                            creditSpread, issueDate, settlementDays,
                            coupons, bondDayCount, schedule, redemption);
        europeanBond.setPricingEngine(engine);
        ConvertibleFixedCouponBond americanBond(
                          amExercise, conversionRatio, dividends, callability,
                          creditSpread, issueDate, settlementDays,
                          coupons, bondDayCount, schedule, redemption);
        americanBond.setPricingEngine(engine);
        method = "Jarrow-Rudd";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                  new BinomialConvertibleEngine<JarrowRudd>(stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                  new BinomialConvertibleEngine<JarrowRudd>(stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        method = "Cox-Ross-Rubinstein";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
           new BinomialConvertibleEngine<CoxRossRubinstein>(stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
           new BinomialConvertibleEngine<CoxRossRubinstein>(stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        method = "Additive equiprobabilities";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                   new BinomialConvertibleEngine<AdditiveEQPBinomialTree>(
                                                            stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                   new BinomialConvertibleEngine<AdditiveEQPBinomialTree>(
                                                            stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        method = "Trigeorgis";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                  new BinomialConvertibleEngine<Trigeorgis>(stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                  new BinomialConvertibleEngine<Trigeorgis>(stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        method = "Tian";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                        new BinomialConvertibleEngine<Tian>(stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                        new BinomialConvertibleEngine<Tian>(stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        method = "Leisen-Reimer";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                new BinomialConvertibleEngine<LeisenReimer>(stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                new BinomialConvertibleEngine<LeisenReimer>(stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        method = "Joshi";
        europeanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                      new BinomialConvertibleEngine<Joshi4>(stochasticProcess,
                                                            timeSteps)));
        americanBond.setPricingEngine(boost::shared_ptr<PricingEngine>(
                      new BinomialConvertibleEngine<Joshi4>(stochasticProcess,
                                                            timeSteps)));
        std::cout << std::setw(widths[0]) << std::left << method
                  << std::fixed
                  << std::setw(widths[1]) << std::left << europeanBond.NPV()
                  << std::setw(widths[2]) << std::left << americanBond.NPV()
                  << std::endl;
        std::cout << dblrule << std::endl;
        Real 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;
    }
}

------------------------------------------------------------------------------
This SF.net email is sponsored by Sprint
What will you do first with EVO, the first 4G phone?
Visit sprint.com/first -- http://p.sf.net/sfu/sprint-com-first
_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users
Reply | Threaded
Open this post in threaded view
| More
Print post
Permalink

Re: possible bug in convert pricer: when notional is not 100

P Nelnik
36 posts
Thanks  David.


On Thu, Jul 15, 2010 at 12:17 PM, David Klein <[hidden email]> wrote:
As I mentioned before, I am not really familiar with Quantlib, but I am familiar with convertible bonds.

Where are two different (related) concepts. "Notional" (aka "face value") and "redemption". Convertible bonds are typically quoted on a percent of notional basis, so a bond with a notional of 1,000 and a price of 110 would be worth 1,100 units of currency. Convertible bonds also typically redeem at 100% of notional, so at maturity one receives the coupon plus the notional. What I am guessing is happening in Quantlib is that by changing the redemption to 1,000 you have setup a bond with the following structure:

Notional = 500 (or any other number, not really relevant since the returned price is in percent notional)
Redemption = 1000% (of notional. I.e. a cash amount of 5,000)
Coupon=10% (of notional, I.e. a cash amount of 50)
Present value approximately 1010% (of notional, I.e. a cash amount of 5050, assuming one year to redemption)

So 1010 is the correct value.

P.S. A redemption other than 100% is called a non-par redemption. They are very common for zero coupon CBs, rare otherwise.
P.P.S. Feel free to post reply to the mailing list.



------------------------------------------------------------------------------
This SF.net email is sponsored by Sprint
What will you do first with EVO, the first 4G phone?
Visit sprint.com/first -- http://p.sf.net/sfu/sprint-com-first
_______________________________________________
QuantLib-users mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/quantlib-users