static void Main(string[] args) { DateTime timer = DateTime.Now; /********************* *** MARKET DATA *** *********************/ Calendar calendar = new TARGET(); Date settlementDate = new Date(18, Month.September, 2008); // must be a business day settlementDate = calendar.adjust(settlementDate); int fixingDays = 3; int settlementDays = 3; Date todaysDate = calendar.advance(settlementDate, -fixingDays, TimeUnit.Days); // nothing to do with Date::todaysDate Settings.setEvaluationDate(todaysDate); Console.WriteLine("Today: {0}, {1}", todaysDate.DayOfWeek, todaysDate); Console.WriteLine("Settlement date: {0}, {1}", settlementDate.DayOfWeek, settlementDate); // Building of the bonds discounting yield curve /********************* *** RATE HELPERS *** *********************/ // RateHelpers are built from the above quotes together with // other instrument dependant infos. Quotes are passed in // relinkable handles which could be relinked to some other // data source later. // Common data // ZC rates for the short end double zc3mQuote=0.0096; double zc6mQuote=0.0145; double zc1yQuote=0.0194; Quote zc3mRate = new SimpleQuote(zc3mQuote); Quote zc6mRate = new SimpleQuote(zc6mQuote); Quote zc1yRate = new SimpleQuote(zc1yQuote); DayCounter zcBondsDayCounter = new Actual365Fixed(); RateHelper zc3m = new DepositRateHelper(new Handle<Quote>(zc3mRate), new Period(3, TimeUnit.Months), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, zcBondsDayCounter); RateHelper zc6m = new DepositRateHelper(new Handle<Quote>(zc6mRate), new Period(6, TimeUnit.Months), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, zcBondsDayCounter); RateHelper zc1y = new DepositRateHelper(new Handle<Quote>(zc1yRate), new Period(1, TimeUnit.Years), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, zcBondsDayCounter); // setup bonds double redemption = 100.0; const int numberOfBonds = 5; Date[] issueDates = { new Date (15, Month.March, 2005), new Date (15, Month.June, 2005), new Date (30, Month.June, 2006), new Date (15, Month.November, 2002), new Date (15, Month.May, 1987) }; Date[] maturities = { new Date (31, Month.August, 2010), new Date (31, Month.August, 2011), new Date (31, Month.August, 2013), new Date (15, Month.August, 2018), new Date (15, Month.May, 2038) }; double[] couponRates = { 0.02375, 0.04625, 0.03125, 0.04000, 0.04500 }; double[] marketQuotes = { 100.390625, 106.21875, 100.59375, 101.6875, 102.140625 }; List<SimpleQuote> quote = new List<SimpleQuote>(); for (int i=0; i<numberOfBonds; i++) { SimpleQuote cp = new SimpleQuote(marketQuotes[i]); quote.Add(cp); } List<RelinkableHandle<Quote>> quoteHandle = new InitializedList<RelinkableHandle<Quote>>(numberOfBonds); for (int i=0; i<numberOfBonds; i++) { quoteHandle[i].linkTo(quote[i]); } // Definition of the rate helpers List<FixedRateBondHelper> bondsHelpers = new List<FixedRateBondHelper>(); for (int i=0; i<numberOfBonds; i++) { Schedule schedule = new Schedule(issueDates[i], maturities[i], new Period(Frequency.Semiannual), new UnitedStates(UnitedStates.Market.GovernmentBond), BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); FixedRateBondHelper bondHelper = new FixedRateBondHelper(quoteHandle[i], settlementDays, 100.0, schedule, new List<double>() { couponRates[i] }, new ActualActual(ActualActual.Convention.Bond), BusinessDayConvention.Unadjusted, redemption, issueDates[i]); bondsHelpers.Add(bondHelper); } /********************* ** CURVE BUILDING ** *********************/ // Any DayCounter would be fine. // ActualActual::ISDA ensures that 30 years is 30.0 DayCounter termStructureDayCounter = new ActualActual(ActualActual.Convention.ISDA); double tolerance = 1.0e-15; // A depo-bond curve List<RateHelper> bondInstruments = new List<RateHelper>(); // Adding the ZC bonds to the curve for the short end bondInstruments.Add(zc3m); bondInstruments.Add(zc6m); bondInstruments.Add(zc1y); // Adding the Fixed rate bonds to the curve for the long end for (int i=0; i<numberOfBonds; i++) { bondInstruments.Add(bondsHelpers[i]); } YieldTermStructure bondDiscountingTermStructure = new PiecewiseYieldCurve<Discount,LogLinear>( settlementDate, bondInstruments, termStructureDayCounter, new List<Handle<Quote>>(), new List<Date>(), tolerance); // Building of the Libor forecasting curve // deposits double d1wQuote=0.043375; double d1mQuote=0.031875; double d3mQuote=0.0320375; double d6mQuote=0.03385; double d9mQuote=0.0338125; double d1yQuote=0.0335125; // swaps double s2yQuote=0.0295; double s3yQuote=0.0323; double s5yQuote=0.0359; double s10yQuote=0.0412; double s15yQuote=0.0433; /******************** *** QUOTES *** ********************/ // SimpleQuote stores a value which can be manually changed; // other Quote subclasses could read the value from a database // or some kind of data feed. // deposits Quote d1wRate = new SimpleQuote(d1wQuote); Quote d1mRate = new SimpleQuote(d1mQuote); Quote d3mRate = new SimpleQuote(d3mQuote); Quote d6mRate = new SimpleQuote(d6mQuote); Quote d9mRate = new SimpleQuote(d9mQuote); Quote d1yRate = new SimpleQuote(d1yQuote); // swaps Quote s2yRate = new SimpleQuote(s2yQuote); Quote s3yRate = new SimpleQuote(s3yQuote); Quote s5yRate = new SimpleQuote(s5yQuote); Quote s10yRate = new SimpleQuote(s10yQuote); Quote s15yRate = new SimpleQuote(s15yQuote); /********************* *** RATE HELPERS *** *********************/ // RateHelpers are built from the above quotes together with // other instrument dependant infos. Quotes are passed in // relinkable handles which could be relinked to some other // data source later. // deposits DayCounter depositDayCounter = new Actual360(); RateHelper d1w = new DepositRateHelper( new Handle<Quote>(d1wRate), new Period(1, TimeUnit.Weeks), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, depositDayCounter); RateHelper d1m = new DepositRateHelper( new Handle<Quote>(d1mRate), new Period(1, TimeUnit.Months), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, depositDayCounter); RateHelper d3m = new DepositRateHelper( new Handle<Quote>(d3mRate), new Period(3, TimeUnit.Months), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, depositDayCounter); RateHelper d6m = new DepositRateHelper( new Handle<Quote>(d6mRate), new Period(6, TimeUnit.Months), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, depositDayCounter); RateHelper d9m = new DepositRateHelper( new Handle<Quote>(d9mRate), new Period(9, TimeUnit.Months), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, depositDayCounter); RateHelper d1y = new DepositRateHelper( new Handle<Quote>(d1yRate), new Period(1, TimeUnit.Years), fixingDays, calendar, BusinessDayConvention.ModifiedFollowing, true, depositDayCounter); // setup swaps Frequency swFixedLegFrequency =Frequency.Annual; BusinessDayConvention swFixedLegConvention = BusinessDayConvention.Unadjusted; DayCounter swFixedLegDayCounter = new Thirty360(Thirty360.Thirty360Convention.European); IborIndex swFloatingLegIndex = new Euribor6M(); Period forwardStart = new Period(1, TimeUnit.Days); RateHelper s2y = new SwapRateHelper( new Handle<Quote>(s2yRate), new Period(2, TimeUnit.Years), calendar, swFixedLegFrequency, swFixedLegConvention, swFixedLegDayCounter, swFloatingLegIndex, new Handle<Quote>(),forwardStart); RateHelper s3y = new SwapRateHelper( new Handle<Quote>(s3yRate), new Period(3, TimeUnit.Years), calendar, swFixedLegFrequency, swFixedLegConvention, swFixedLegDayCounter, swFloatingLegIndex, new Handle<Quote>(),forwardStart); RateHelper s5y = new SwapRateHelper( new Handle<Quote>(s5yRate), new Period(5, TimeUnit.Years), calendar, swFixedLegFrequency, swFixedLegConvention, swFixedLegDayCounter, swFloatingLegIndex, new Handle<Quote>(),forwardStart); RateHelper s10y = new SwapRateHelper( new Handle<Quote>(s10yRate), new Period(10, TimeUnit.Years), calendar, swFixedLegFrequency, swFixedLegConvention, swFixedLegDayCounter, swFloatingLegIndex, new Handle<Quote>(),forwardStart); RateHelper s15y = new SwapRateHelper( new Handle<Quote>(s15yRate), new Period(15, TimeUnit.Years), calendar, swFixedLegFrequency, swFixedLegConvention, swFixedLegDayCounter, swFloatingLegIndex, new Handle<Quote>(),forwardStart); /********************* ** CURVE BUILDING ** *********************/ // Any DayCounter would be fine. // ActualActual::ISDA ensures that 30 years is 30.0 // A depo-swap curve List<RateHelper> depoSwapInstruments = new List<RateHelper>(); depoSwapInstruments.Add(d1w); depoSwapInstruments.Add(d1m); depoSwapInstruments.Add(d3m); depoSwapInstruments.Add(d6m); depoSwapInstruments.Add(d9m); depoSwapInstruments.Add(d1y); depoSwapInstruments.Add(s2y); depoSwapInstruments.Add(s3y); depoSwapInstruments.Add(s5y); depoSwapInstruments.Add(s10y); depoSwapInstruments.Add(s15y); YieldTermStructure depoSwapTermStructure = new PiecewiseYieldCurve<Discount,LogLinear>( settlementDate, depoSwapInstruments, termStructureDayCounter, new List<Handle<Quote> >(), new List<Date>(), tolerance); // Term structures that will be used for pricing: // the one used for discounting cash flows RelinkableHandle<YieldTermStructure> discountingTermStructure = new RelinkableHandle<YieldTermStructure>(); // the one used for forward rate forecasting RelinkableHandle<YieldTermStructure> forecastingTermStructure = new RelinkableHandle<YieldTermStructure>(); /********************* * BONDS TO BE PRICED * **********************/ // Common data double faceAmount = 100; // Pricing engine IPricingEngine bondEngine = new DiscountingBondEngine(discountingTermStructure); // Zero coupon bond ZeroCouponBond zeroCouponBond = new ZeroCouponBond( settlementDays, new UnitedStates(UnitedStates.Market.GovernmentBond), faceAmount, new Date(15, Month.August,2013), BusinessDayConvention.Following, 116.92, new Date(15, Month.August,2003)); zeroCouponBond.setPricingEngine(bondEngine); // Fixed 4.5% US Treasury Note Schedule fixedBondSchedule = new Schedule(new Date(15, Month.May, 2007), new Date(15,Month.May,2017), new Period(Frequency.Semiannual), new UnitedStates(UnitedStates.Market.GovernmentBond), BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); FixedRateBond fixedRateBond = new FixedRateBond( settlementDays, faceAmount, fixedBondSchedule, new List<double>() { 0.045 }, new ActualActual(ActualActual.Convention.Bond), BusinessDayConvention.ModifiedFollowing, 100.0, new Date(15, Month.May, 2007)); fixedRateBond.setPricingEngine(bondEngine); // Floating rate bond (3M USD Libor + 0.1%) // Should and will be priced on another curve later... RelinkableHandle<YieldTermStructure> liborTermStructure = new RelinkableHandle<YieldTermStructure>(); IborIndex libor3m = new USDLibor(new Period(3, TimeUnit.Months), liborTermStructure); libor3m.addFixing(new Date(17, Month.July, 2008),0.0278625); Schedule floatingBondSchedule = new Schedule(new Date(21, Month.October, 2005), new Date(21, Month.October, 2010), new Period(Frequency.Quarterly), new UnitedStates(UnitedStates.Market.NYSE), BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, true); FloatingRateBond floatingRateBond = new FloatingRateBond( settlementDays, faceAmount, floatingBondSchedule, libor3m, new Actual360(), BusinessDayConvention.ModifiedFollowing, 2, // Gearings new List<double>() { 1.0 }, // Spreads new List<double>() { 0.001 }, // Caps new List<double>(), // Floors new List<double>(), // Fixing in arrears true, 100.0, new Date(21, Month.October, 2005)); floatingRateBond.setPricingEngine(bondEngine); // Coupon pricers IborCouponPricer pricer = new BlackIborCouponPricer(); // optionLet volatilities double volatility = 0.0; Handle<OptionletVolatilityStructure> vol; vol = new Handle<OptionletVolatilityStructure>( new ConstantOptionletVolatility( settlementDays, calendar, BusinessDayConvention.ModifiedFollowing, volatility, new Actual365Fixed())); pricer.setCapletVolatility(vol); Utils.setCouponPricer(floatingRateBond.cashflows(),pricer); // Yield curve bootstrapping forecastingTermStructure.linkTo(depoSwapTermStructure); discountingTermStructure.linkTo(bondDiscountingTermStructure); // We are using the depo & swap curve to estimate the future Libor rates liborTermStructure.linkTo(depoSwapTermStructure); /*************** * BOND PRICING * ****************/ // write column headings int[] widths = { 18, 10, 10, 10 }; Console.WriteLine("{0,18}{1,10}{2,10}{3,10}", "", "ZC", "Fixed", "Floating"); string separator = " | "; int width = widths[0] + widths[1] + widths[2] + widths[3]; string rule = "".PadLeft(width, '-'), dblrule = "".PadLeft(width, '='); string tab = "".PadLeft(8, ' '); Console.WriteLine(rule); Console.WriteLine("Net present value".PadLeft(widths[0]) + "{0,10:n2}{1,10:n2}{2,10:n2}", zeroCouponBond.NPV(), fixedRateBond.NPV(), floatingRateBond.NPV()); Console.WriteLine("Clean price".PadLeft(widths[0]) + "{0,10:n2}{1,10:n2}{2,10:n2}", zeroCouponBond.cleanPrice(), fixedRateBond.cleanPrice(), floatingRateBond.cleanPrice()); Console.WriteLine("Dirty price".PadLeft(widths[0]) + "{0,10:n2}{1,10:n2}{2,10:n2}", zeroCouponBond.dirtyPrice(), fixedRateBond.dirtyPrice(), floatingRateBond.dirtyPrice()); Console.WriteLine("Accrued coupon".PadLeft(widths[0]) + "{0,10:n2}{1,10:n2}{2,10:n2}", zeroCouponBond.accruedAmount(), fixedRateBond.accruedAmount(), floatingRateBond.accruedAmount()); Console.WriteLine("Previous coupon".PadLeft(widths[0]) + "{0,10:0.00%}{1,10:0.00%}{2,10:0.00%}", "N/A", fixedRateBond.previousCoupon(), floatingRateBond.previousCoupon()); Console.WriteLine("Next coupon".PadLeft(widths[0]) + "{0,10:0.00%}{1,10:0.00%}{2,10:0.00%}", "N/A", fixedRateBond.nextCoupon(), floatingRateBond.nextCoupon()); Console.WriteLine("Yield".PadLeft(widths[0]) + "{0,10:0.00%}{1,10:0.00%}{2,10:0.00%}", zeroCouponBond.yield(new Actual360(), Compounding.Compounded, Frequency.Annual), fixedRateBond.yield(new Actual360(), Compounding.Compounded, Frequency.Annual), floatingRateBond.yield(new Actual360(), Compounding.Compounded, Frequency.Annual)); Console.WriteLine(); // Other computations Console.WriteLine("Sample indirect computations (for the floating rate bond): "); Console.WriteLine(rule); Console.WriteLine("Yield to Clean Price: {0:n2}", floatingRateBond.cleanPrice(floatingRateBond.yield(new Actual360(), Compounding.Compounded, Frequency.Annual), new Actual360(), Compounding.Compounded, Frequency.Annual, settlementDate)); Console.WriteLine("Clean Price to Yield: {0:0.00%}", floatingRateBond.yield(floatingRateBond.cleanPrice(),new Actual360(), Compounding.Compounded, Frequency.Annual, settlementDate)); /* "Yield to Price" "Price to Yield" */ Console.WriteLine(" \nRun completed in {0}", DateTime.Now - timer); Console.WriteLine(); Console.Write("Press any key to continue ..."); Console.ReadKey(); }
public void testCached() { // ("Testing bond price/yield calculation against cached values..."); CommonVars vars = new CommonVars(); // with implicit settlement calculation: Date today = new Date(22, Month.November, 2004); Settings.setEvaluationDate(today); Calendar bondCalendar = new NullCalendar(); DayCounter bondDayCount = new ActualActual(ActualActual.Convention.ISMA); int settlementDays = 1; var discountCurve = new Handle<YieldTermStructure>(Utilities.flatRate(today, new SimpleQuote(0.03), new Actual360())); // actual market values from the evaluation date Frequency freq = Frequency.Semiannual; Schedule sch1 = new Schedule(new Date(31, Month.October, 2004), new Date(31, Month.October, 2006), new Period(freq), bondCalendar, BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); FixedRateBond bond1 = new FixedRateBond(settlementDays, vars.faceAmount, sch1, new List<double>() { 0.025 }, bondDayCount, BusinessDayConvention.ModifiedFollowing, 100.0, new Date(1, Month.November, 2004)); IPricingEngine bondEngine = new DiscountingBondEngine(discountCurve); bond1.setPricingEngine(bondEngine); double marketPrice1 = 99.203125; double marketYield1 = 0.02925; Schedule sch2 = new Schedule(new Date(15, Month.November, 2004), new Date(15, Month.November, 2009), new Period(freq), bondCalendar, BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); FixedRateBond bond2 = new FixedRateBond(settlementDays, vars.faceAmount, sch2, new List<double>() { 0.035 }, bondDayCount, BusinessDayConvention.ModifiedFollowing, 100.0, new Date(15, Month.November, 2004)); bond2.setPricingEngine(bondEngine); double marketPrice2 = 99.6875; double marketYield2 = 0.03569; // calculated values double cachedPrice1a = 99.204505, cachedPrice2a = 99.687192; double cachedPrice1b = 98.943393, cachedPrice2b = 101.986794; double cachedYield1a = 0.029257, cachedYield2a = 0.035689; double cachedYield1b = 0.029045, cachedYield2b = 0.035375; double cachedYield1c = 0.030423, cachedYield2c = 0.030432; // check double tolerance = 1.0e-6; double price, yield; price = bond1.cleanPrice(marketYield1, bondDayCount, Compounding.Compounded, freq); if (Math.Abs(price - cachedPrice1a) > tolerance) { Assert.Fail("failed to reproduce cached price:" + "\n calculated: " + price + "\n expected: " + cachedPrice1a + "\n tolerance: " + tolerance + "\n error: " + (price - cachedPrice1a)); } price = bond1.cleanPrice(); if (Math.Abs(price - cachedPrice1b) > tolerance) { Assert.Fail("failed to reproduce cached price:" + "\n calculated: " + price + "\n expected: " + cachedPrice1b + "\n tolerance: " + tolerance + "\n error: " + (price - cachedPrice1b)); } yield = bond1.yield(marketPrice1, bondDayCount, Compounding.Compounded, freq); if (Math.Abs(yield - cachedYield1a) > tolerance) { Assert.Fail("failed to reproduce cached compounded yield:" + "\n calculated: " + yield + "\n expected: " + cachedYield1a + "\n tolerance: " + tolerance + "\n error: " + (yield - cachedYield1a)); } yield = bond1.yield(marketPrice1, bondDayCount, Compounding.Continuous, freq); if (Math.Abs(yield - cachedYield1b) > tolerance) { Assert.Fail("failed to reproduce cached continuous yield:" + "\n calculated: " + yield + "\n expected: " + cachedYield1b + "\n tolerance: " + tolerance + "\n error: " + (yield - cachedYield1b)); } yield = bond1.yield(bondDayCount, Compounding.Continuous, freq); if (Math.Abs(yield - cachedYield1c) > tolerance) { Assert.Fail("failed to reproduce cached continuous yield:" + "\n calculated: " + yield + "\n expected: " + cachedYield1c + "\n tolerance: " + tolerance + "\n error: " + (yield - cachedYield1c)); } price = bond2.cleanPrice(marketYield2, bondDayCount, Compounding.Compounded, freq); if (Math.Abs(price - cachedPrice2a) > tolerance) { Assert.Fail("failed to reproduce cached price:" + "\n calculated: " + price + "\n expected: " + cachedPrice2a + "\n tolerance: " + tolerance + "\n error: " + (price - cachedPrice2a)); } price = bond2.cleanPrice(); if (Math.Abs(price - cachedPrice2b) > tolerance) { Assert.Fail("failed to reproduce cached price:" + "\n calculated: " + price + "\n expected: " + cachedPrice2b + "\n tolerance: " + tolerance + "\n error: " + (price - cachedPrice2b)); } yield = bond2.yield(marketPrice2, bondDayCount, Compounding.Compounded, freq); if (Math.Abs(yield - cachedYield2a) > tolerance) { Assert.Fail("failed to reproduce cached compounded yield:" + "\n calculated: " + yield + "\n expected: " + cachedYield2a + "\n tolerance: " + tolerance + "\n error: " + (yield - cachedYield2a)); } yield = bond2.yield(marketPrice2, bondDayCount, Compounding.Continuous, freq); if (Math.Abs(yield - cachedYield2b) > tolerance) { Assert.Fail("failed to reproduce cached continuous yield:" + "\n calculated: " + yield + "\n expected: " + cachedYield2b + "\n tolerance: " + tolerance + "\n error: " + (yield - cachedYield2b)); } yield = bond2.yield(bondDayCount, Compounding.Continuous, freq); if (Math.Abs(yield - cachedYield2c) > tolerance) { Assert.Fail("failed to reproduce cached continuous yield:" + "\n calculated: " + yield + "\n expected: " + cachedYield2c + "\n tolerance: " + tolerance + "\n error: " + (yield - cachedYield2c)); } // with explicit settlement date: Schedule sch3 = new Schedule(new Date(30, Month.November, 2004), new Date(30, Month.November, 2006), new Period(freq), new UnitedStates(UnitedStates.Market.GovernmentBond), BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); FixedRateBond bond3 = new FixedRateBond(settlementDays, vars.faceAmount, sch3, new List<double>() { 0.02875 }, new ActualActual(ActualActual.Convention.ISMA), BusinessDayConvention.ModifiedFollowing, 100.0, new Date(30, Month.November, 2004)); bond3.setPricingEngine(bondEngine); double marketYield3 = 0.02997; Date settlementDate = new Date(30, Month.November, 2004); double cachedPrice3 = 99.764759; price = bond3.cleanPrice(marketYield3, bondDayCount, Compounding.Compounded, freq, settlementDate); if (Math.Abs(price - cachedPrice3) > tolerance) { Assert.Fail("failed to reproduce cached price:" + "\n calculated: " + price + "" + "\n expected: " + cachedPrice3 + "" + "\n error: " + (price - cachedPrice3)); } // this should give the same result since the issue date is the // earliest possible settlement date Settings.setEvaluationDate(new Date(22, Month.November, 2004)); price = bond3.cleanPrice(marketYield3, bondDayCount, Compounding.Compounded, freq); if (Math.Abs(price - cachedPrice3) > tolerance) { Assert.Fail("failed to reproduce cached price:" + "\n calculated: " + price + "" + "\n expected: " + cachedPrice3 + "" + "\n error: " + (price - cachedPrice3)); } }
public void testTheoretical() { // "Testing theoretical bond price/yield calculation..."); CommonVars vars = new CommonVars(); double tolerance = 1.0e-7; int maxEvaluations = 100; int[] lengths = new int[] { 3, 5, 10, 15, 20 }; int settlementDays = 3; double[] coupons = new double[] { 0.02, 0.05, 0.08 }; Frequency[] frequencies = new Frequency[] { Frequency.Semiannual, Frequency.Annual }; DayCounter bondDayCount = new Actual360(); BusinessDayConvention accrualConvention = BusinessDayConvention.Unadjusted; BusinessDayConvention paymentConvention = BusinessDayConvention.ModifiedFollowing; double redemption = 100.0; double[] yields = new double[] { 0.03, 0.04, 0.05, 0.06, 0.07 }; for (int j = 0; j < lengths.Length; j++) { for (int k = 0; k < coupons.Length; k++) { for (int l = 0; l < frequencies.Length; l++) { Date dated = vars.today; Date issue = dated; Date maturity = vars.calendar.advance(issue, lengths[j], TimeUnit.Years); SimpleQuote rate = new SimpleQuote(0.0); var discountCurve = new Handle<YieldTermStructure>(Utilities.flatRate(vars.today, rate, bondDayCount)); Schedule sch = new Schedule(dated, maturity, new Period(frequencies[l]), vars.calendar, accrualConvention, accrualConvention, DateGeneration.Rule.Backward, false); FixedRateBond bond = new FixedRateBond(settlementDays, vars.faceAmount, sch, new List<double>() { coupons[k] }, bondDayCount, paymentConvention, redemption, issue); IPricingEngine bondEngine = new DiscountingBondEngine(discountCurve); bond.setPricingEngine(bondEngine); for (int m = 0; m < yields.Length; m++) { rate.setValue(yields[m]); double price = bond.cleanPrice(yields[m], bondDayCount, Compounding.Continuous, frequencies[l]); double calculatedPrice = bond.cleanPrice(); if (Math.Abs(price - calculatedPrice) > tolerance) { Assert.Fail("price calculation failed:" + "\n issue: " + issue + "\n maturity: " + maturity + "\n coupon: " + coupons[k] + "\n frequency: " + frequencies[l] + "\n" + "\n yield: " + yields[m] + "\n expected: " + price + "\n calculated': " + calculatedPrice + "\n error': " + (price - calculatedPrice)); } double calculatedYield = bond.yield(bondDayCount, Compounding.Continuous, frequencies[l], tolerance, maxEvaluations); if (Math.Abs(yields[m] - calculatedYield) > tolerance) { Assert.Fail("yield calculation failed:" + "\n issue: " + issue + "\n maturity: " + maturity + "\n coupon: " + coupons[k] + "\n frequency: " + frequencies[l] + "\n" + "\n yield: " + yields[m] + "\n price: " + price + "\n yield': " + calculatedYield); } } } } } }
public void testYield() { //"Testing consistency of bond price/yield calculation..."); CommonVars vars = new CommonVars(); double tolerance = 1.0e-7; int maxEvaluations = 100; int[] issueMonths = new int[] { -24, -18, -12, -6, 0, 6, 12, 18, 24 }; int[] lengths = new int[] { 3, 5, 10, 15, 20 }; int settlementDays = 3; double[] coupons = new double[] { 0.02, 0.05, 0.08 }; Frequency[] frequencies = new Frequency[] { Frequency.Semiannual, Frequency.Annual }; DayCounter bondDayCount = new Thirty360(); BusinessDayConvention accrualConvention = BusinessDayConvention.Unadjusted; BusinessDayConvention paymentConvention = BusinessDayConvention.ModifiedFollowing; double redemption = 100.0; double[] yields = new double[] { 0.03, 0.04, 0.05, 0.06, 0.07 }; Compounding[] compounding = new Compounding[] { Compounding.Compounded, Compounding.Continuous }; for (int i = 0; i < issueMonths.Length; i++) { for (int j = 0; j < lengths.Length; j++) { for (int k = 0; k < coupons.Length; k++) { for (int l = 0; l < frequencies.Length; l++) { for (int n = 0; n < compounding.Length; n++) { Date dated = vars.calendar.advance(vars.today, issueMonths[i], TimeUnit.Months); Date issue = dated; Date maturity = vars.calendar.advance(issue, lengths[j], TimeUnit.Years); Schedule sch = new Schedule(dated, maturity, new Period(frequencies[l]), vars.calendar, accrualConvention, accrualConvention, DateGeneration.Rule.Backward, false); FixedRateBond bond = new FixedRateBond(settlementDays, vars.faceAmount, sch, new List<double>() { coupons[k] }, bondDayCount, paymentConvention, redemption, issue); for (int m = 0; m < yields.Length; m++) { double price = bond.cleanPrice(yields[m], bondDayCount, compounding[n], frequencies[l]); double calculated = bond.yield(price, bondDayCount, compounding[n], frequencies[l], null, tolerance, maxEvaluations); if (Math.Abs(yields[m] - calculated) > tolerance) { // the difference might not matter double price2 = bond.cleanPrice(calculated, bondDayCount, compounding[n], frequencies[l]); if (Math.Abs(price - price2) / price > tolerance) { Assert.Fail("yield recalculation failed:\n" + " issue: " + issue + "\n" + " maturity: " + maturity + "\n" + " coupon: " + coupons[k] + "\n" + " frequency: " + frequencies[l] + "\n\n" + " yield: " + yields[m] + " " + (compounding[n] == Compounding.Compounded ? "compounded" : "continuous") + "\n" + " price: " + price + "\n" + " yield': " + calculated + "\n" + " price': " + price2); } } } } } } } } }
static void Main(string[] args) { DateTime timer = DateTime.Now; Date repoSettlementDate = new Date(14,Month.February,2000);; Date repoDeliveryDate = new Date(15,Month.August,2000); double repoRate = 0.05; DayCounter repoDayCountConvention = new Actual360(); int repoSettlementDays = 0; Compounding repoCompounding = Compounding.Simple; Frequency repoCompoundFreq = Frequency.Annual; // assume a ten year bond- this is irrelevant Date bondIssueDate = new Date(15,Month.September,1995); Date bondDatedDate = new Date(15,Month.September,1995); Date bondMaturityDate = new Date(15,Month.September,2005); double bondCoupon = 0.08; Frequency bondCouponFrequency = Frequency.Semiannual; // unknown what calendar fincad is using Calendar bondCalendar = new NullCalendar(); DayCounter bondDayCountConvention = new Thirty360(Thirty360.Thirty360Convention.BondBasis); // unknown what fincad is using. this may affect accrued calculation int bondSettlementDays = 0; BusinessDayConvention bondBusinessDayConvention = BusinessDayConvention.Unadjusted; double bondCleanPrice = 89.97693786; double bondRedemption = 100.0; double faceAmount = 100.0; Settings.setEvaluationDate(repoSettlementDate); RelinkableHandle<YieldTermStructure> bondCurve = new RelinkableHandle<YieldTermStructure>(); bondCurve.linkTo(new FlatForward(repoSettlementDate, .01, // dummy rate bondDayCountConvention, Compounding.Compounded, bondCouponFrequency)); /* boost::shared_ptr<FixedRateBond> bond( new FixedRateBond(faceAmount, bondIssueDate, bondDatedDate, bondMaturityDate, bondSettlementDays, std::vector<Rate>(1,bondCoupon), bondCouponFrequency, bondCalendar, bondDayCountConvention, bondBusinessDayConvention, bondBusinessDayConvention, bondRedemption, bondCurve)); */ Schedule bondSchedule = new Schedule(bondDatedDate, bondMaturityDate, new Period(bondCouponFrequency), bondCalendar,bondBusinessDayConvention, bondBusinessDayConvention, DateGeneration.Rule.Backward,false); FixedRateBond bond = new FixedRateBond(bondSettlementDays, faceAmount, bondSchedule, new List<double>() { bondCoupon }, bondDayCountConvention, bondBusinessDayConvention, bondRedemption, bondIssueDate); bond.setPricingEngine(new DiscountingBondEngine(bondCurve)); bondCurve.linkTo(new FlatForward(repoSettlementDate, bond.yield(bondCleanPrice, bondDayCountConvention, Compounding.Compounded, bondCouponFrequency), bondDayCountConvention, Compounding.Compounded, bondCouponFrequency)); Position.Type fwdType = Position.Type.Long; double dummyStrike = 91.5745; RelinkableHandle<YieldTermStructure> repoCurve = new RelinkableHandle<YieldTermStructure>(); repoCurve.linkTo(new FlatForward(repoSettlementDate, repoRate, repoDayCountConvention, repoCompounding, repoCompoundFreq)); FixedRateBondForward bondFwd = new FixedRateBondForward(repoSettlementDate, repoDeliveryDate, fwdType, dummyStrike, repoSettlementDays, repoDayCountConvention, bondCalendar, bondBusinessDayConvention, bond, repoCurve, repoCurve); Console.WriteLine("Underlying bond clean price: " + bond.cleanPrice()); Console.WriteLine("Underlying bond dirty price: " + bond.dirtyPrice()); Console.WriteLine("Underlying bond accrued at settlement: " + bond.accruedAmount(repoSettlementDate)); Console.WriteLine("Underlying bond accrued at delivery: " + bond.accruedAmount(repoDeliveryDate)); Console.WriteLine("Underlying bond spot income: " + bondFwd.spotIncome(repoCurve)); Console.WriteLine("Underlying bond fwd income: " + bondFwd.spotIncome(repoCurve)/ repoCurve.link.discount(repoDeliveryDate)); Console.WriteLine("Repo strike: " + dummyStrike); Console.WriteLine("Repo NPV: " + bondFwd.NPV()); Console.WriteLine("Repo clean forward price: " + bondFwd.cleanForwardPrice()); Console.WriteLine("Repo dirty forward price: " + bondFwd.forwardPrice()); Console.WriteLine("Repo implied yield: " + bondFwd.impliedYield(bond.dirtyPrice(), dummyStrike, repoSettlementDate, repoCompounding, repoDayCountConvention)); Console.WriteLine("Market repo rate: " + repoCurve.link.zeroRate(repoDeliveryDate, repoDayCountConvention, repoCompounding, repoCompoundFreq)); Console.WriteLine("\nCompare with example given at \n" + "http://www.fincad.com/support/developerFunc/mathref/BFWD.htm"); Console.WriteLine("Clean forward price = 88.2408"); Console.WriteLine("\nIn that example, it is unknown what bond calendar they are\n" + "using, as well as settlement Days. For that reason, I have\n" + "made the simplest possible assumptions here: NullCalendar\n" + "and 0 settlement days.\n"); Console.WriteLine("nRun completed in {0}", DateTime.Now - timer); Console.Write("Press any key to continue ..."); Console.ReadKey(); }