static void Main(string[] args) { // boost::timer timer; Date today = new Date(16, Month.October, 2007); Settings.setEvaluationDate(today); Console.WriteLine(); Console.WriteLine("Pricing a callable fixed rate bond using"); Console.WriteLine("Hull White model w/ reversion parameter = 0.03"); Console.WriteLine("BAC4.65 09/15/12 ISIN: US06060WBJ36"); Console.WriteLine("roughly five year tenor, quarterly coupon and call dates"); Console.WriteLine("reference date is : " + today.ToLongDateString()); Console.WriteLine(""); /* Bloomberg OAS1: "N" model (Hull White) * varying volatility parameter * * The curve entered into Bloomberg OAS1 is a flat curve, * at constant yield = 5.5%, semiannual compounding. * Assume here OAS1 curve uses an ACT/ACT day counter, * as documented in PFC1 as a "default" in the latter case. */ // set up a flat curve corresponding to Bloomberg flat curve double bbCurveRate = 0.055; DayCounter bbDayCounter = new ActualActual(ActualActual.Convention.Bond); InterestRate bbIR = new InterestRate(bbCurveRate, bbDayCounter, Compounding.Compounded, Frequency.Semiannual); Handle <YieldTermStructure> termStructure = new Handle <YieldTermStructure>(flatRate(today, bbIR.rate(), bbIR.dayCounter(), bbIR.compounding(), bbIR.frequency())); // set up the call schedule CallabilitySchedule callSchedule = new CallabilitySchedule(); double callPrice = 100.0; int numberOfCallDates = 24; Date callDate = new Date(15, Month.September, 2006); for (int i = 0; i < numberOfCallDates; i++) { Calendar nullCalendar = new NullCalendar(); Callability.Price myPrice = new Callability.Price(callPrice, Callability.Price.Type.Clean); callSchedule.Add(new Callability(myPrice, Callability.Type.Call, callDate)); callDate = nullCalendar.advance(callDate, 3, TimeUnit.Months); } // set up the callable bond Date dated = new Date(16, Month.September, 2004); Date issue = dated; Date maturity = new Date(15, Month.September, 2012); int settlementDays = 3; // Bloomberg OAS1 settle is Oct 19, 2007 Calendar bondCalendar = new UnitedStates(UnitedStates.Market.GovernmentBond); double coupon = .0465; Frequency frequency = Frequency.Quarterly; double redemption = 100.0; double faceAmount = 100.0; /* The 30/360 day counter Bloomberg uses for this bond cannot * reproduce the US Bond/ISMA (constant) cashflows used in PFC1. * Therefore use ActAct(Bond) */ DayCounter bondDayCounter = new ActualActual(ActualActual.Convention.Bond); // PFC1 shows no indication dates are being adjusted // for weekends/holidays for vanilla bonds BusinessDayConvention accrualConvention = BusinessDayConvention.Unadjusted; BusinessDayConvention paymentConvention = BusinessDayConvention.Unadjusted; Schedule sch = new Schedule(dated, maturity, new Period(frequency), bondCalendar, accrualConvention, accrualConvention, DateGeneration.Rule.Backward, false); int maxIterations = 1000; double accuracy = 1e-8; int gridIntervals = 40; double reversionParameter = .03; // output price/yield results for varying volatility parameter double sigma = Const.QL_EPSILON; // core dumps if zero on Cygwin ShortRateModel hw0 = new HullWhite(termStructure, reversionParameter, sigma); IPricingEngine engine0 = new TreeCallableFixedRateBondEngine(hw0, gridIntervals, termStructure); CallableFixedRateBond callableBond = new CallableFixedRateBond(settlementDays, faceAmount, sch, new InitializedList <double>(1, coupon), bondDayCounter, paymentConvention, redemption, issue, callSchedule); callableBond.setPricingEngine(engine0); Console.WriteLine("sigma/vol (%) = {0:0.00}", (100.0 * sigma)); Console.WriteLine("QLNet price/yld (%) {0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) 96,50 / 5,47"); Console.WriteLine(""); // sigma = .01; Console.WriteLine("sigma/vol (%) = {0:0.00}", (100.0 * sigma)); ShortRateModel hw1 = new HullWhite(termStructure, reversionParameter, sigma); IPricingEngine engine1 = new TreeCallableFixedRateBondEngine(hw1, gridIntervals, termStructure); callableBond.setPricingEngine(engine1); Console.WriteLine("QLNet price/yld (%) {0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) 95,68 / 5,66"); Console.WriteLine(""); // sigma = .03; Console.WriteLine("sigma/vol (%) = {0:0.00}", (100.0 * sigma)); ShortRateModel hw2 = new HullWhite(termStructure, reversionParameter, sigma); IPricingEngine engine2 = new TreeCallableFixedRateBondEngine(hw2, gridIntervals, termStructure); callableBond.setPricingEngine(engine2); Console.WriteLine("QLNet price/yld (%) {0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) 92,34 / 6,49"); Console.WriteLine(""); // sigma = .06; Console.WriteLine("sigma/vol (%) = {0:0.00}", (100.0 * sigma)); ShortRateModel hw3 = new HullWhite(termStructure, reversionParameter, sigma); IPricingEngine engine3 = new TreeCallableFixedRateBondEngine(hw3, gridIntervals, termStructure); callableBond.setPricingEngine(engine3); Console.WriteLine("QLNet price/yld (%) {0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) 87,16 / 7,83"); Console.WriteLine(""); // sigma = .12; Console.WriteLine("sigma/vol (%) = {0:0.00}", (100.0 * sigma)); ShortRateModel hw4 = new HullWhite(termStructure, reversionParameter, sigma); IPricingEngine engine4 = new TreeCallableFixedRateBondEngine(hw4, gridIntervals, termStructure); callableBond.setPricingEngine(engine4); Console.WriteLine("QLNet price/yld (%) {0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) 77,31 / 10,65"); }
public void testConversions() { InterestRateData[] cases = { // data from "Option Pricing Formulas", Haug, pag.181-182 // Rate,Compounding, Frequency, Time, Compounding2, Frequency2, Rate2, precision new InterestRateData(0.0800, Compounding.Compounded, Frequency.Quarterly, 1.00, Compounding.Continuous, Frequency.Annual, 0.0792, 4), new InterestRateData(0.1200, Compounding.Continuous, Frequency.Annual, 1.00, Compounding.Compounded, Frequency.Annual, 0.1275, 4), new InterestRateData(0.0800, Compounding.Compounded, Frequency.Quarterly, 1.00, Compounding.Compounded, Frequency.Annual, 0.0824, 4), new InterestRateData(0.0700, Compounding.Compounded, Frequency.Quarterly, 1.00, Compounding.Compounded, Frequency.Semiannual, 0.0706, 4), // undocumented, but reasonable :) new InterestRateData(0.0100, Compounding.Compounded, Frequency.Annual, 1.00, Compounding.Simple, Frequency.Annual, 0.0100, 4), new InterestRateData(0.0200, Compounding.Simple, Frequency.Annual, 1.00, Compounding.Compounded, Frequency.Annual, 0.0200, 4), new InterestRateData(0.0300, Compounding.Compounded, Frequency.Semiannual, 0.50, Compounding.Simple, Frequency.Annual, 0.0300, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Annual, 0.50, Compounding.Compounded, Frequency.Semiannual, 0.0400, 4), new InterestRateData(0.0500, Compounding.Compounded, Frequency.EveryFourthMonth, 1.0 / 3, Compounding.Simple, Frequency.Annual, 0.0500, 4), new InterestRateData(0.0600, Compounding.Simple, Frequency.Annual, 1.0 / 3, Compounding.Compounded, Frequency.EveryFourthMonth, 0.0600, 4), new InterestRateData(0.0500, Compounding.Compounded, Frequency.Quarterly, 0.25, Compounding.Simple, Frequency.Annual, 0.0500, 4), new InterestRateData(0.0600, Compounding.Simple, Frequency.Annual, 0.25, Compounding.Compounded, Frequency.Quarterly, 0.0600, 4), new InterestRateData(0.0700, Compounding.Compounded, Frequency.Bimonthly, 1.0 / 6, Compounding.Simple, Frequency.Annual, 0.0700, 4), new InterestRateData(0.0800, Compounding.Simple, Frequency.Annual, 1.0 / 6, Compounding.Compounded, Frequency.Bimonthly, 0.0800, 4), new InterestRateData(0.0900, Compounding.Compounded, Frequency.Monthly, 1.0 / 12, Compounding.Simple, Frequency.Annual, 0.0900, 4), new InterestRateData(0.1000, Compounding.Simple, Frequency.Annual, 1.0 / 12, Compounding.Compounded, Frequency.Monthly, 0.1000, 4), new InterestRateData(0.0300, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.25, Compounding.Simple, Frequency.Annual, 0.0300, 4), new InterestRateData(0.0300, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.25, Compounding.Simple, Frequency.Semiannual, 0.0300, 4), new InterestRateData(0.0300, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.25, Compounding.Simple, Frequency.Quarterly, 0.0300, 4), new InterestRateData(0.0300, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.50, Compounding.Simple, Frequency.Annual, 0.0300, 4), new InterestRateData(0.0300, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.50, Compounding.Simple, Frequency.Semiannual, 0.0300, 4), new InterestRateData(0.0300, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.75, Compounding.Compounded, Frequency.Semiannual, 0.0300, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Semiannual, 0.25, Compounding.SimpleThenCompounded, Frequency.Quarterly, 0.0400, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Semiannual, 0.25, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.0400, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Semiannual, 0.25, Compounding.SimpleThenCompounded, Frequency.Annual, 0.0400, 4), new InterestRateData(0.0400, Compounding.Compounded, Frequency.Quarterly, 0.50, Compounding.SimpleThenCompounded, Frequency.Quarterly, 0.0400, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Semiannual, 0.50, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.0400, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Semiannual, 0.50, Compounding.SimpleThenCompounded, Frequency.Annual, 0.0400, 4), new InterestRateData(0.0400, Compounding.Compounded, Frequency.Quarterly, 0.75, Compounding.SimpleThenCompounded, Frequency.Quarterly, 0.0400, 4), new InterestRateData(0.0400, Compounding.Compounded, Frequency.Semiannual, 0.75, Compounding.SimpleThenCompounded, Frequency.Semiannual, 0.0400, 4), new InterestRateData(0.0400, Compounding.Simple, Frequency.Semiannual, 0.75, Compounding.SimpleThenCompounded, Frequency.Annual, 0.0400, 4) }; Rounding roundingPrecision; double r3; double r2; Date d1 = Date.Today; Date d2; InterestRate ir; InterestRate ir2; InterestRate ir3; InterestRate expectedIR; double compoundf; double error; double disc; for (int i = 0; i < cases.Length - 1; i++) { ir = new InterestRate(cases[i].r, new Actual360(), cases[i].comp, cases[i].freq); d2 = d1 + new Period((int)(360 * cases[i].t + 0.5), TimeUnit.Days); roundingPrecision = new Rounding(cases[i].precision); // check that the compound factor is the inverse of the discount factor compoundf = ir.compoundFactor(d1, d2); disc = ir.discountFactor(d1, d2); error = Math.Abs(disc - 1.0 / compoundf); if (error > 1e-15) { QAssert.Fail(ir + " 1.0/compound_factor: " + 1.0 / compoundf); } // check that the equivalent InterestRate with *same* daycounter, // compounding, and frequency is the *same* InterestRate //ir2 = ir.equivalentRate(d1, d2, ir.dayCounter(), ir.compounding(), ir.frequency()); ir2 = ir.equivalentRate(ir.dayCounter(), ir.compounding(), ir.frequency(), d1, d2); error = Math.Abs(ir.rate() - ir2.rate()); if (error > 1e-15) { QAssert.Fail("original interest rate: " + ir + " equivalent interest rate: " + ir2 + " rate error: " + error); } if (ir.dayCounter() != ir2.dayCounter()) { QAssert.Fail("day counter error original interest rate: " + ir + " equivalent interest rate: " + ir2); } if (ir.compounding() != ir2.compounding()) { QAssert.Fail("compounding error original interest rate: " + ir + " equivalent interest rate: " + ir2); } if (ir.frequency() != ir2.frequency()) { QAssert.Fail("frequency error original interest rate: " + ir + " equivalent interest rate: " + ir2); } // check that the equivalent rate with *same* daycounter, // compounding, and frequency is the *same* rate //r2 = ir.equivalentRate(d1, d2, ir.dayCounter(), ir.compounding(), ir.frequency()).rate(); r2 = ir.equivalentRate(ir.dayCounter(), ir.compounding(), ir.frequency(), d1, d2).value(); error = Math.Abs(ir.rate() - r2); if (error > 1e-15) { QAssert.Fail("original rate: " + ir + " equivalent rate: " + r2 + " error: " + error); } // check that the equivalent InterestRate with *different* // compounding, and frequency is the *expected* InterestRate //ir3 = ir.equivalentRate(d1, d2, ir.dayCounter(), cases[i].comp2, cases[i].freq2); ir3 = ir.equivalentRate(ir.dayCounter(), cases[i].comp2, cases[i].freq2, d1, d2); expectedIR = new InterestRate(cases[i].expected, ir.dayCounter(), cases[i].comp2, cases[i].freq2); r3 = roundingPrecision.Round(ir3.rate()); error = Math.Abs(r3 - expectedIR.rate()); if (error > 1.0e-17) { QAssert.Fail("original interest rate: " + ir + " calculated equivalent interest rate: " + ir3 + " truncated equivalent rate: " + r3 + " expected equivalent interest rate: " + expectedIR + " rate error: " + error); } if (ir3.dayCounter() != expectedIR.dayCounter()) { QAssert.Fail("day counter error original interest rate: " + ir3 + " equivalent interest rate: " + expectedIR); } if (ir3.compounding() != expectedIR.compounding()) { QAssert.Fail("compounding error original interest rate: " + ir3 + " equivalent interest rate: " + expectedIR); } if (ir3.frequency() != expectedIR.frequency()) { QAssert.Fail("frequency error original interest rate: " + ir3 + " equivalent interest rate: " + expectedIR); } // check that the equivalent rate with *different* // compounding, and frequency is the *expected* rate //r3 = ir.equivalentRate(d1, d2, ir.dayCounter(), cases[i].comp2, cases[i].freq2).rate(); r3 = ir.equivalentRate(ir.dayCounter(), cases[i].comp2, cases[i].freq2, d1, d2).value(); r3 = roundingPrecision.Round(r3); error = Math.Abs(r3 - cases[i].expected); if (error > 1.0e-17) { QAssert.Fail("calculated equivalent rate: " + r3 + " expected equivalent rate: " + cases[i].expected + " error: " + error); } } }
public void testBrazilianCached() { //("Testing Brazilian public bond prices against cached values..."); CommonVars vars = new CommonVars(); double faceAmount = 1000.0; double redemption = 100.0; Date issueDate = new Date(1, Month.January, 2007); Date today = new Date(6, Month.June, 2007); Settings.setEvaluationDate(today); // NTN-F maturity dates InitializedList <Date> maturityDates = new InitializedList <Date>(6); maturityDates[0] = new Date(1, Month.January, 2008); maturityDates[1] = new Date(1, Month.January, 2010); maturityDates[2] = new Date(1, Month.July, 2010); maturityDates[3] = new Date(1, Month.January, 2012); maturityDates[4] = new Date(1, Month.January, 2014); maturityDates[5] = new Date(1, Month.January, 2017); // NTN-F yields InitializedList <double> yields = new InitializedList <double>(6); yields[0] = 0.114614; yields[1] = 0.105726; yields[2] = 0.105328; yields[3] = 0.104283; yields[4] = 0.103218; yields[5] = 0.102948; // NTN-F prices InitializedList <double> prices = new InitializedList <double>(6); prices[0] = 1034.63031372; prices[1] = 1030.09919487; prices[2] = 1029.98307160; prices[3] = 1028.13585068; prices[4] = 1028.33383817; prices[5] = 1026.19716497; int settlementDays = 1; vars.faceAmount = 1000.0; // The tolerance is high because Andima truncate yields double tolerance = 1.0e-4; InitializedList <InterestRate> couponRates = new InitializedList <InterestRate>(1); couponRates[0] = new InterestRate(0.1, new Thirty360(), Compounding.Compounded, Frequency.Annual); for (int bondIndex = 0; bondIndex < maturityDates.Count; bondIndex++) { // plain InterestRate yield = new InterestRate(yields[bondIndex], new Business252(new Brazil()), Compounding.Compounded, Frequency.Annual); Schedule schedule = new Schedule(new Date(1, Month.January, 2007), maturityDates[bondIndex], new Period(Frequency.Semiannual), new Brazil(Brazil.Market.Settlement), BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); FixedRateBond bond = new FixedRateBond(settlementDays, faceAmount, schedule, couponRates, BusinessDayConvention.Following, redemption, issueDate); double cachedPrice = prices[bondIndex]; double price = vars.faceAmount * (bond.cleanPrice(yield.rate(), yield.dayCounter(), yield.compounding(), yield.frequency(), today) + bond.accruedAmount(today)) / 100; if (Math.Abs(price - cachedPrice) > tolerance) { Assert.Fail("failed to reproduce cached price:\n" + " calculated: " + price + "\n" + " expected: " + cachedPrice + "\n" + " error: " + (price - cachedPrice) + "\n" ); } } }