/*
Copyright (C) 2010 Zahid Hussain
This source code is release under the BSD License.
This file is part of JQuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://jquantlib.org/
JQuantLib is free software: you can redistribute it and/or modify it
under the terms of the JQuantLib license. You should have received a
copy of the license along with this program; if not, please email
. The license is also available online at
.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the license for more details.
JQuantLib is based on QuantLib. http://quantlib.org/
When applicable, the original copyright notice follows this notice.
*/
/*
Copyright (C) 2006 StatPro Italia srl
This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/
QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license. You should have received a
copy of the license along with this program; if not, please email
. The license is also available online at
.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the license for more details.
*/
package org.jquantlib.testsuite.instruments;
import static org.junit.Assert.fail;
import org.jquantlib.QL;
import org.jquantlib.SavedSettings;
import org.jquantlib.Settings;
import org.jquantlib.cashflow.BlackIborCouponPricer;
import org.jquantlib.cashflow.IborCouponPricer;
import org.jquantlib.cashflow.PricerSetter;
import org.jquantlib.daycounters.Actual360;
import org.jquantlib.daycounters.DayCounter;
import org.jquantlib.exercise.AmericanExercise;
import org.jquantlib.exercise.EuropeanExercise;
import org.jquantlib.exercise.Exercise;
import org.jquantlib.indexes.Euribor1Y;
import org.jquantlib.indexes.IborIndex;
import org.jquantlib.instruments.CallabilitySchedule;
import org.jquantlib.instruments.DividendSchedule;
import org.jquantlib.instruments.Option;
import org.jquantlib.instruments.PlainVanillaPayoff;
import org.jquantlib.instruments.StrikedTypePayoff;
import org.jquantlib.instruments.VanillaOption;
import org.jquantlib.instruments.bonds.ConvertibleFixedCouponBond;
import org.jquantlib.instruments.bonds.ConvertibleFloatingRateBond;
import org.jquantlib.instruments.bonds.ConvertibleZeroCouponBond;
import org.jquantlib.instruments.bonds.FixedRateBond;
import org.jquantlib.instruments.bonds.FloatingRateBond;
import org.jquantlib.instruments.bonds.ZeroCouponBond;
import org.jquantlib.math.matrixutilities.Array;
import org.jquantlib.methods.lattices.CoxRossRubinstein;
import org.jquantlib.pricingengines.BinomialConvertibleEngine;
import org.jquantlib.pricingengines.PricingEngine;
import org.jquantlib.pricingengines.bond.DiscountingBondEngine;
import org.jquantlib.pricingengines.vanilla.BinomialVanillaEngine;
import org.jquantlib.processes.BlackScholesMertonProcess;
import org.jquantlib.quotes.Handle;
import org.jquantlib.quotes.Quote;
import org.jquantlib.quotes.RelinkableHandle;
import org.jquantlib.quotes.SimpleQuote;
import org.jquantlib.termstructures.BlackVolTermStructure;
import org.jquantlib.termstructures.YieldTermStructure;
import org.jquantlib.termstructures.volatilities.optionlet.OptionletVolatilityStructure;
import org.jquantlib.termstructures.yieldcurves.ForwardSpreadedTermStructure;
import org.jquantlib.testsuite.util.Utilities;
import org.jquantlib.time.BusinessDayConvention;
import org.jquantlib.time.Calendar;
import org.jquantlib.time.Date;
import org.jquantlib.time.DateGeneration;
import org.jquantlib.time.Frequency;
import org.jquantlib.time.MakeSchedule;
import org.jquantlib.time.Period;
import org.jquantlib.time.Schedule;
import org.jquantlib.time.TimeUnit;
import org.jquantlib.time.calendars.Target;
import org.junit.Test;
/**
* @author Zahid Hussain
*/
public class ConvertibleBondTest {
public ConvertibleBondTest() {
QL.info("::::: " + this.getClass().getSimpleName() + " :::::");
}
private class CommonVars {
// global data
Date today, issueDate, maturityDate;
Calendar calendar;
DayCounter dayCounter;
Frequency frequency;
int settlementDays;
RelinkableHandle underlying = new RelinkableHandle();
RelinkableHandle dividendYield = new RelinkableHandle();
RelinkableHandle riskFreeRate = new RelinkableHandle();
RelinkableHandle volatility = new RelinkableHandle();
BlackScholesMertonProcess process;
RelinkableHandle creditSpread = new RelinkableHandle();
CallabilitySchedule no_callability = new CallabilitySchedule();
DividendSchedule no_dividends = new DividendSchedule();
double faceAmount, redemption, conversionRatio;
// cleanup
SavedSettings backup = new SavedSettings();
// setup
CommonVars() {
calendar = new Target();
today = calendar.adjust(Date.todaysDate());
new Settings().setEvaluationDate(today);
dayCounter = new Actual360();
frequency = Frequency.Annual;
settlementDays = 3;
issueDate = calendar.advance(today, 2, TimeUnit.Days);
maturityDate = calendar.advance(issueDate, 10, TimeUnit.Years);
// reset to avoid inconsistencies as the schedule is backwards
issueDate = calendar.advance(maturityDate, -10, TimeUnit.Years);
underlying.linkTo(new SimpleQuote(50.0));
dividendYield.linkTo(Utilities.flatRate(today, 0.02, dayCounter));
riskFreeRate.linkTo(Utilities.flatRate(today, 0.05, dayCounter));
volatility.linkTo(Utilities.flatVol(today, 0.15, dayCounter));
process = new BlackScholesMertonProcess(underlying, dividendYield,
riskFreeRate, volatility);
creditSpread.linkTo(new SimpleQuote(0.005));
// it fails with 1000000
// faceAmount = 1000000.0;
faceAmount = 100.0;
redemption = 100.0;
conversionRatio = redemption / underlying.currentLink().value();
}
}
@Test
public void testBond() {
System.setProperty("EXPERIMENTAL", "true");
/*
* when deeply out-of-the-money, the value of the convertible bond
* should equal that of the underlying plain-vanilla bond.
*/
QL.info("Testing out-of-the-money convertible bonds against vanilla bonds...");
final CommonVars vars = new CommonVars();
vars.conversionRatio = 1.0e-16;
final Exercise euExercise = new EuropeanExercise(vars.maturityDate);
final Exercise amExercise = new AmericanExercise(vars.issueDate,
vars.maturityDate);
final int timeSteps = 1001;
final PricingEngine engine = new BinomialConvertibleEngine(
CoxRossRubinstein.class,
vars.process, timeSteps);
final Handle discountCurve = new Handle(
new ForwardSpreadedTermStructure(vars.riskFreeRate,
vars.creditSpread));
// zero-coupon
Schedule schedule = new MakeSchedule(vars.issueDate, vars.maturityDate,
new Period(Frequency.Once), vars.calendar,
BusinessDayConvention.Following).backwards().schedule();
final ConvertibleZeroCouponBond euZero = new ConvertibleZeroCouponBond(
euExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, vars.dayCounter, schedule, vars.redemption);
euZero.setPricingEngine(engine);
final ConvertibleZeroCouponBond amZero = new ConvertibleZeroCouponBond(
amExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, vars.dayCounter, schedule, vars.redemption);
amZero.setPricingEngine(engine);
final ZeroCouponBond zero = new ZeroCouponBond(vars.settlementDays,
vars.calendar, 100.0, vars.maturityDate,
BusinessDayConvention.Following, vars.redemption,
vars.issueDate);
final PricingEngine bondEngine = new DiscountingBondEngine(discountCurve);
zero.setPricingEngine(bondEngine);
double tolerance = 1.0e-2 * (vars.faceAmount / 100.0);
double error = Math.abs(euZero.NPV() - zero.settlementValue());
if (error > tolerance) {
fail("failed to reproduce zero-coupon bond price:"
+ "\n calculated: " + euZero.NPV()
+ "\n expected: " + zero.settlementValue()
+ "\n error: " + error);
}
error = Math.abs(amZero.NPV() - zero.settlementValue());
if (error > tolerance) {
fail("failed to reproduce zero-coupon bond price:"
+ "\n calculated: " + amZero.NPV()
+ "\n expected: " + zero.settlementValue()
+ "\n error: " + error);
}
// coupon
final double[] coupons = { 0.05 };
schedule = new MakeSchedule(vars.issueDate, vars.maturityDate,
new Period(vars.frequency), vars.calendar,
BusinessDayConvention.Following).backwards().schedule();
final ConvertibleFixedCouponBond euFixed = new ConvertibleFixedCouponBond(
euExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, coupons, vars.dayCounter, schedule,
vars.redemption);
euFixed.setPricingEngine(engine);
final ConvertibleFixedCouponBond amFixed = new ConvertibleFixedCouponBond(
amExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, coupons, vars.dayCounter, schedule,
vars.redemption);
amFixed.setPricingEngine(engine);
final FixedRateBond fixed = new FixedRateBond(vars.settlementDays,
vars.faceAmount, schedule, coupons, vars.dayCounter,
BusinessDayConvention.Following, vars.redemption,
vars.issueDate);
fixed.setPricingEngine(bondEngine);
tolerance = 2.0e-2 * (vars.faceAmount / 100.0);
error = Math.abs(euFixed.NPV() - fixed.settlementValue());
if (error > tolerance) {
fail("failed to reproduce fixed-coupon bond price:"
+ "\n calculated: " + euFixed.NPV()
+ "\n expected: " + fixed.settlementValue()
+ "\n error: " + error);
}
error = Math.abs(amFixed.NPV() - fixed.settlementValue());
if (error > tolerance) {
fail("failed to reproduce fixed-coupon bond price:"
+ "\n calculated: " + amFixed.NPV()
+ "\n expected: " + fixed.settlementValue()
+ "\n error: " + error);
}
// floating-rate
final IborIndex index = new Euribor1Y(discountCurve);
final int fixingDays = 2;
final Array gearings = new Array(1).fill(1.0);
final Array spreadsArr = new Array(0);
final double[] spreads = { 0 };
final ConvertibleFloatingRateBond euFloating = new ConvertibleFloatingRateBond(
euExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, index, fixingDays, spreads,
vars.dayCounter, schedule, vars.redemption);
euFloating.setPricingEngine(engine);
final ConvertibleFloatingRateBond amFloating = new ConvertibleFloatingRateBond(
amExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, index, fixingDays, spreads,
vars.dayCounter, schedule, vars.redemption);
amFloating.setPricingEngine(engine);
final IborCouponPricer pricer = new BlackIborCouponPricer(
new Handle());
final Schedule floatSchedule = new Schedule(vars.issueDate,
vars.maturityDate, new Period(vars.frequency), vars.calendar,
BusinessDayConvention.Following,
BusinessDayConvention.Following, DateGeneration.Rule.Backward,
false);
final FloatingRateBond floating = new FloatingRateBond(vars.settlementDays,
vars.faceAmount, floatSchedule, index, vars.dayCounter,
BusinessDayConvention.Following, fixingDays, gearings,
spreadsArr, new Array(0), new Array(0), false, vars.redemption,
vars.issueDate);
floating.setPricingEngine(bondEngine);
PricerSetter.setCouponPricer(floating.cashflows(), pricer);
tolerance = 2.0e-2 * (vars.faceAmount / 100.0);
error = Math.abs(euFloating.NPV() - floating.settlementValue());
if (error > tolerance) {
fail("failed to reproduce floating-rate bond price:"
+ "\n calculated: " + euFloating.NPV()
+ "\n expected: " + floating.settlementValue()
+ "\n error: " + error);
}
error = Math.abs(amFloating.NPV() - floating.settlementValue());
if (error > tolerance) {
fail("failed to reproduce floating-rate bond price:"
+ "\n calculated: " + amFloating.NPV()
+ "\n expected: " + floating.settlementValue()
+ "\n error: " + error);
}
}
@Test
public void testOption() {
System.setProperty("EXPERIMENTAL", "true");
/*
* a zero-coupon convertible bond with no credit spread is equivalent to
* a call option.
*/
QL.info("Testing zero-coupon convertible bonds against vanilla option...");
final CommonVars vars = new CommonVars();
final Exercise euExercise = new EuropeanExercise(vars.maturityDate);
vars.settlementDays = 0;
final int timeSteps = 1001;
final PricingEngine engine = new BinomialConvertibleEngine(CoxRossRubinstein.class, vars.process, timeSteps);
final PricingEngine vanillaEngine = new BinomialVanillaEngine(CoxRossRubinstein.class, vars.process, timeSteps);
vars.creditSpread.linkTo(new SimpleQuote(0.0));
final double conversionStrike = vars.redemption / vars.conversionRatio;
final StrikedTypePayoff payoff = new PlainVanillaPayoff(Option.Type.Call,
conversionStrike);
final Schedule schedule = new MakeSchedule(vars.issueDate, vars.maturityDate,
new Period(Frequency.Once), vars.calendar,
BusinessDayConvention.Following).backwards().schedule();
final ConvertibleZeroCouponBond euZero = new ConvertibleZeroCouponBond(
euExercise, vars.conversionRatio, vars.no_dividends,
vars.no_callability, vars.creditSpread, vars.issueDate,
vars.settlementDays, vars.dayCounter, schedule, vars.redemption);
euZero.setPricingEngine(engine);
final VanillaOption euOption = new VanillaOption(payoff, euExercise);
euOption.setPricingEngine(vanillaEngine);
final double tolerance = 5.0e-2 * (vars.faceAmount / 100.0);
final double expected = vars.faceAmount
/ 100.0
* (vars.redemption
* vars.riskFreeRate.currentLink().discount(
vars.maturityDate) + vars.conversionRatio
* euOption.NPV());
final double error = Math.abs(euZero.NPV() - expected);
if (error > tolerance) {
fail("failed to reproduce plain-option price:"
+ "\n calculated: " + euZero.NPV()
+ "\n expected: " + expected + "\n error: "
+ error);
}
}
}
Ads help maintain this website.