void addEffectiveInterestRateAmortizing() { // Amortizing Schedule Schedule schedule = new Schedule(_tradeDate, _maturityDate, new Period(_payFrequency), _calendar, BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); double currentNominal = _marketValue; Date prevDate = _tradeDate; Date actualDate = _tradeDate; for (int i = 1; i < schedule.Count; ++i) { actualDate = schedule[i]; InterestRate rate = new InterestRate(_yield, _dCounter, Compounding.Simple, Frequency.Annual); InterestRate rate2 = new InterestRate(_couponRate, _dCounter, Compounding.Simple, Frequency.Annual); FixedRateCoupon r, r2; if (i > 1) { r = new FixedRateCoupon(actualDate, currentNominal, rate, prevDate, actualDate, prevDate, actualDate); r2 = new FixedRateCoupon(actualDate, currentNominal, rate2, prevDate, actualDate, prevDate, actualDate, null, _originalPayment); } else { Calendar nullCalendar = new NullCalendar(); Period p1 = new Period(_payFrequency); Date testDate = nullCalendar.advance(actualDate, -1 * p1); r = new FixedRateCoupon(actualDate, currentNominal, rate, testDate, actualDate, prevDate, actualDate); r2 = new FixedRateCoupon(actualDate, currentNominal, rate2, testDate, actualDate, prevDate, actualDate, null, _originalPayment); } double amort = Math.Round(Math.Abs(_originalPayment - r.amount()), 2); AmortizingPayment p = new AmortizingPayment(amort, actualDate); if (_isPremium) { currentNominal -= Math.Abs(amort); } else { currentNominal += Math.Abs(amort); } cashflows_.Add(r2); cashflows_.Add(p); prevDate = actualDate; } // Add single redemption for yield calculation setSingleRedemption(_faceValue, 100, _maturityDate); }
public void testBondFromScheduleWithDateVector() { // Testing South African R2048 bond price using Schedule constructor with Date vector SavedSettings backup = new SavedSettings(); //When pricing bond from Yield To Maturity, use NullCalendar() Calendar calendar = new NullCalendar(); int settlementDays = 3; Date issueDate = new Date(29, Month.June, 2012); Date today = new Date(7, Month.September, 2015); Date evaluationDate = calendar.adjust(today); Date settlementDate = calendar.advance(evaluationDate, new Period(settlementDays, TimeUnit.Days)); Settings.setEvaluationDate(evaluationDate); // For the schedule to generate correctly for Feb-28's, make maturity date on Feb 29 Date maturityDate = new Date(29, Month.February, 2048); double coupon = 0.0875; Compounding comp = Compounding.Compounded; Frequency freq = Frequency.Semiannual; DayCounter dc = new ActualActual(ActualActual.Convention.Bond); // Yield as quoted in market InterestRate yield = new InterestRate(0.09185, dc, comp, freq); Period tenor = new Period(6, TimeUnit.Months); Period exCouponPeriod = new Period(10, TimeUnit.Days); // Generate coupon dates for 31 Aug and end of Feb each year // For leap years, this will generate 29 Feb, but the bond // actually pays coupons on 28 Feb, regardsless of whether // it is a leap year or not. Schedule schedule = new Schedule(issueDate, maturityDate, tenor, new NullCalendar(), BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, true); // Adjust the 29 Feb's to 28 Feb List<Date> dates = new List<Date>(); for (int i = 0; i < schedule.Count; ++i) { Date d = schedule.date(i); if (d.Month == 2 && d.Day == 29) dates.Add(new Date(28, Month.February, d.Year)); else dates.Add(d); } schedule = new Schedule(dates, schedule.calendar(), schedule.businessDayConvention(), schedule.terminationDateBusinessDayConvention(), schedule.tenor(), schedule.rule(), schedule.endOfMonth(), schedule.isRegular()); FixedRateBond bond = new FixedRateBond( 0, 100.0, schedule, new List<double>() { coupon }, dc, BusinessDayConvention.Following, 100.0, issueDate, calendar, exCouponPeriod, calendar, BusinessDayConvention.Unadjusted, false); double calculatedPrice = BondFunctions.dirtyPrice(bond, yield, settlementDate); double expectedPrice = 95.75706; double tolerance = 1e-5; if (Math.Abs(calculatedPrice - expectedPrice) > tolerance) { Assert.Fail(string.Format("failed to reproduce R2048 dirty price\nexpected: {0}\ncalculated: {1}", expectedPrice, calculatedPrice)); } }
/// <summary> /// rule based constructor /// </summary> /// <param name="effectiveDate"></param> /// <param name="terminationDate"></param> /// <param name="tenor"></param> /// <param name="calendar"></param> /// <param name="convention"></param> /// <param name="terminationDateConvention"></param> /// <param name="rule"></param> /// <param name="endOfMonth"></param> /// <param name="firstDate"></param> /// <param name="nextToLastDate"></param> public Schedule(Date effectiveDate, Date terminationDate, Period tenor, Calendar calendar, BusinessDayConvention convention, BusinessDayConvention terminationDateConvention, DateGeneration.Rule rule, bool endOfMonth, Date firstDate = null, Date nextToLastDate = null) { calendar_ = calendar ?? new NullCalendar(); firstDate_ = firstDate == effectiveDate ? null : firstDate; nextToLastDate_ = nextToLastDate == terminationDate ? null : nextToLastDate; tenor_ = tenor; convention_ = convention; terminationDateConvention_ = terminationDateConvention; rule_ = rule; endOfMonth_ = allowsEndOfMonth(tenor) && endOfMonth; // sanity checks Utils.QL_REQUIRE(terminationDate != null, () => "null termination date"); // in many cases (e.g. non-expired bonds) the effective date is not // really necessary. In these cases a decent placeholder is enough if (effectiveDate == null && firstDate == null && rule == DateGeneration.Rule.Backward) { Date evalDate = Settings.evaluationDate(); Utils.QL_REQUIRE(evalDate < terminationDate, () => "null effective date", QLNetExceptionEnum.NullEffectiveDate); int y; if (nextToLastDate != null) { y = (nextToLastDate - evalDate) / 366 + 1; effectiveDate = nextToLastDate - new Period(y, TimeUnit.Years); } else { y = (terminationDate - evalDate) / 366 + 1; effectiveDate = terminationDate - new Period(y, TimeUnit.Years); } // More accurate , is the previous coupon date if (effectiveDate > evalDate) { effectiveDate = effectiveDate - new Period(tenor_.length(), TimeUnit.Months); } else if (effectiveDate + new Period(tenor_.length(), TimeUnit.Months) < evalDate) { effectiveDate = effectiveDate + new Period(tenor_.length(), TimeUnit.Months); } } else { Utils.QL_REQUIRE(effectiveDate != null, () => "null effective date", QLNetExceptionEnum.NullEffectiveDate); } Utils.QL_REQUIRE(effectiveDate < terminationDate, () => "effective date (" + effectiveDate + ") later than or equal to termination date (" + terminationDate + ")" ); if (tenor_.length() == 0) { rule_ = DateGeneration.Rule.Zero; } else { Utils.QL_REQUIRE(tenor.length() > 0, () => "non positive tenor (" + tenor + ") not allowed"); } if (firstDate_ != null) { switch (rule_.Value) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: Utils.QL_REQUIRE(firstDate_ > effectiveDate && firstDate_ < terminationDate, () => "first date (" + firstDate_ + ") out of effective-termination date range [" + effectiveDate + ", " + terminationDate + ")"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: Utils.QL_REQUIRE(IMM.isIMMdate(firstDate_, false), () => "first date (" + firstDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: case DateGeneration.Rule.CDS2015: Utils.QL_FAIL("first date incompatible with " + rule_.Value + " date generation rule"); break; default: Utils.QL_FAIL("unknown rule (" + rule_.Value + ")"); break; } } if (nextToLastDate_ != null) { switch (rule_.Value) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: Utils.QL_REQUIRE(nextToLastDate_ > effectiveDate && nextToLastDate_ < terminationDate, () => "next to last date (" + nextToLastDate_ + ") out of effective-termination date range (" + effectiveDate + ", " + terminationDate + "]"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: Utils.QL_REQUIRE(IMM.isIMMdate(nextToLastDate_, false), () => "next-to-last date (" + nextToLastDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: case DateGeneration.Rule.CDS2015: Utils.QL_FAIL("next to last date incompatible with " + rule_.Value + " date generation rule"); break; default: Utils.QL_FAIL("unknown rule (" + rule_.Value + ")"); break; } } // calendar needed for endOfMonth adjustment Calendar nullCalendar = new NullCalendar(); int periods = 1; Date seed = new Date(), exitDate = new Date(); switch (rule_.Value) { case DateGeneration.Rule.Zero: tenor_ = new Period(0, TimeUnit.Years); dates_.Add(effectiveDate); dates_.Add(terminationDate); isRegular_.Add(true); break; case DateGeneration.Rule.Backward: dates_.Add(terminationDate); seed = terminationDate; if (nextToLastDate_ != null) { dates_.Insert(0, nextToLastDate_); Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_.Value); if (temp != nextToLastDate_) { isRegular_.Insert(0, false); } else { isRegular_.Insert(0, true); } seed = nextToLastDate_; } exitDate = effectiveDate; if (firstDate_ != null) { exitDate = firstDate_; } while (true) { Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_.Value); if (temp < exitDate) { if (firstDate_ != null && (calendar_.adjust(dates_.First(), convention_) != calendar_.adjust(firstDate_, convention_))) { dates_.Insert(0, firstDate_); isRegular_.Insert(0, false); } break; } else { // skip dates that would result in duplicates // after adjustment if (calendar_.adjust(dates_.First(), convention_) != calendar_.adjust(temp, convention_)) { dates_.Insert(0, temp); isRegular_.Insert(0, true); } ++periods; } } if (calendar_.adjust(dates_.First(), convention) != calendar_.adjust(effectiveDate, convention)) { dates_.Insert(0, effectiveDate); isRegular_.Insert(0, false); } break; case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.ThirdWednesday: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: case DateGeneration.Rule.CDS2015: Utils.QL_REQUIRE(!endOfMonth, () => "endOfMonth convention incompatible with " + rule_.Value + " date generation rule"); goto case DateGeneration.Rule.Forward; // fall through case DateGeneration.Rule.Forward: if (rule_.Value == DateGeneration.Rule.CDS || rule_.Value == DateGeneration.Rule.CDS2015) { dates_.Add(previousTwentieth(effectiveDate, rule_.Value)); } else { dates_.Add(effectiveDate); } seed = dates_.Last(); if (firstDate_ != null) { dates_.Add(firstDate_); Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_.Value); if (temp != firstDate_) { isRegular_.Add(false); } else { isRegular_.Add(true); } seed = firstDate_; } else if (rule_.Value == DateGeneration.Rule.Twentieth || rule_.Value == DateGeneration.Rule.TwentiethIMM || rule_.Value == DateGeneration.Rule.OldCDS || rule_.Value == DateGeneration.Rule.CDS || rule_.Value == DateGeneration.Rule.CDS2015) { Date next20th = nextTwentieth(effectiveDate, rule_.Value); if (rule_ == DateGeneration.Rule.OldCDS) { // distance rule inforced in natural days long stubDays = 30; if (next20th - effectiveDate < stubDays) { // +1 will skip this one and get the next next20th = nextTwentieth(next20th + 1, rule_.Value); } } if (next20th != effectiveDate) { dates_.Add(next20th); isRegular_.Add(false); seed = next20th; } } exitDate = terminationDate; if (nextToLastDate_ != null) { exitDate = nextToLastDate_; } if (rule_ == DateGeneration.Rule.CDS2015 && nextTwentieth(terminationDate, rule_.Value) == terminationDate && terminationDate.month() % 2 == 1) { exitDate = nextTwentieth(terminationDate + 1, rule_.Value); } while (true) { Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_.Value); if (temp > exitDate) { if (nextToLastDate_ != null && (calendar_.adjust(dates_.Last(), convention_) != calendar_.adjust(nextToLastDate_, convention_))) { dates_.Add(nextToLastDate_); isRegular_.Add(false); } break; } else { // skip dates that would result in duplicates // after adjustment if (calendar_.adjust(dates_.Last(), convention_) != calendar_.adjust(temp, convention_)) { dates_.Add(temp); isRegular_.Add(true); } ++periods; } } if (calendar_.adjust(dates_.Last(), terminationDateConvention_.Value) != calendar_.adjust(terminationDate, terminationDateConvention_.Value)) { if (rule_.Value == DateGeneration.Rule.Twentieth || rule_.Value == DateGeneration.Rule.TwentiethIMM || rule_.Value == DateGeneration.Rule.OldCDS || rule_.Value == DateGeneration.Rule.CDS) { dates_.Add(nextTwentieth(terminationDate, rule_.Value)); isRegular_.Add(true); } else if (rule_ == DateGeneration.Rule.CDS2015) { Date tentativeTerminationDate = nextTwentieth(terminationDate, rule_.Value); if (tentativeTerminationDate.month() % 2 == 0) { dates_.Add(tentativeTerminationDate); isRegular_.Add(true); } } else { dates_.Add(terminationDate); isRegular_.Add(false); } } break; default: Utils.QL_FAIL("unknown rule (" + rule_.Value + ")"); break; } // adjustments if (rule_ == DateGeneration.Rule.ThirdWednesday) { for (int i = 1; i < dates_.Count - 1; ++i) { dates_[i] = Date.nthWeekday(3, DayOfWeek.Wednesday, dates_[i].Month, dates_[i].Year); } } if (endOfMonth && calendar_.isEndOfMonth(seed)) { // adjust to end of month if (convention_ == BusinessDayConvention.Unadjusted) { for (int i = 1; i < dates_.Count - 1; ++i) { dates_[i] = Date.endOfMonth(dates_[i]); } } else { for (int i = 1; i < dates_.Count - 1; ++i) { dates_[i] = calendar_.endOfMonth(dates_[i]); } } if (terminationDateConvention_ != BusinessDayConvention.Unadjusted) { dates_[0] = calendar_.endOfMonth(dates_.First()); dates_[dates_.Count - 1] = calendar_.endOfMonth(dates_.Last()); } else { // the termination date is the first if going backwards, // the last otherwise. if (rule_ == DateGeneration.Rule.Backward) { dates_[dates_.Count - 1] = Date.endOfMonth(dates_.Last()); } else { dates_[0] = Date.endOfMonth(dates_.First()); } } } else { // first date not adjusted for CDS schedules if (rule_ != DateGeneration.Rule.OldCDS) { dates_[0] = calendar_.adjust(dates_[0], convention_); } for (int i = 1; i < dates_.Count - 1; ++i) { dates_[i] = calendar_.adjust(dates_[i], convention_); } // termination date is NOT adjusted as per ISDA specifications, unless otherwise specified in the // confirmation of the deal or unless we're creating a CDS schedule if (terminationDateConvention_.Value != BusinessDayConvention.Unadjusted && rule_.Value != DateGeneration.Rule.CDS && rule_.Value != DateGeneration.Rule.CDS2015) { dates_[dates_.Count - 1] = calendar_.adjust(dates_.Last(), terminationDateConvention_.Value); } } // Final safety checks to remove extra next-to-last date, if // necessary. It can happen to be equal or later than the end // date due to EOM adjustments (see the Schedule test suite // for an example). if (dates_.Count >= 2 && dates_[dates_.Count - 2] >= dates_.Last()) { isRegular_[isRegular_.Count - 2] = (dates_[dates_.Count - 2] == dates_.Last()); dates_[dates_.Count - 2] = dates_.Last(); dates_.RemoveAt(dates_.Count - 1); isRegular_.RemoveAt(isRegular_.Count - 1); } if (dates_.Count >= 2 && dates_[1] <= dates_.First()) { isRegular_[1] = (dates_[1] == dates_.First()); dates_[1] = dates_.First(); dates_.RemoveAt(0); isRegular_.RemoveAt(0); } Utils.QL_REQUIRE(dates_.Count > 1, () => "degenerate single date (" + dates_[0] + ") schedule" + "\n seed date: " + seed + "\n exit date: " + exitDate + "\n effective date: " + effectiveDate + "\n first date: " + firstDate + "\n next to last date: " + nextToLastDate + "\n termination date: " + terminationDate + "\n generation rule: " + rule_.Value + "\n end of month: " + endOfMonth_.Value); }
public Schedule( Date effectiveDate, Date terminationDate, Period tenor, Calendar calendar, BusinessDayConvention convention, BusinessDayConvention terminationDateConvention, DateGeneration.Rule rule, bool endOfMonth, Date firstDate = null, Date nextToLastDate = null) { // first save the properties fullInterface_ = true; tenor_ = tenor; if ( calendar == null ) calendar_ = new NullCalendar(); else calendar_ = calendar; convention_ = convention; terminationDateConvention_ = terminationDateConvention; rule_ = rule; endOfMonth_ = endOfMonth; if ( firstDate == effectiveDate ) firstDate_ = null; else firstDate_ = firstDate; if ( nextToLastDate == terminationDate ) nextToLastDate_ = null; else nextToLastDate_ = nextToLastDate; // sanity checks Utils.QL_REQUIRE( terminationDate != null, () => "null termination date" ); // in many cases (e.g. non-expired bonds) the effective date is not // really necessary. In these cases a decent placeholder is enough if ( effectiveDate == null && firstDate == null && rule== DateGeneration.Rule.Backward) { Date evalDate = Settings.evaluationDate(); Utils.QL_REQUIRE( evalDate < terminationDate, () => "null effective date" ); int y; if (nextToLastDate != null) { y = (nextToLastDate - evalDate)/366 + 1; effectiveDate = nextToLastDate - new Period(y,TimeUnit.Years); } else { y = (terminationDate - evalDate)/366 + 1; effectiveDate = terminationDate - new Period(y,TimeUnit.Years); } } else Utils.QL_REQUIRE( effectiveDate != null, () => "null effective date" ); if (tenor_.length() == 0) rule_ = DateGeneration.Rule.Zero; else Utils.QL_REQUIRE( tenor.length() > 0, () => "non positive tenor (" + tenor + ") not allowed" ); if (firstDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: Utils.QL_REQUIRE(firstDate_ > effectiveDate && firstDate_ < terminationDate, () => "first date (" + firstDate_ + ") out of effective-termination date range [" + effectiveDate + ", " + terminationDate + ")"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: Utils.QL_REQUIRE( IMM.isIMMdate( firstDate_, false ), () => "first date (" + firstDate_ + ") is not an IMM date" ); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: Utils.QL_FAIL("first date incompatible with " + rule_ + " date generation rule"); break; default: Utils.QL_FAIL("unknown rule (" + rule_ + ")"); break; } } if (nextToLastDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: Utils.QL_REQUIRE( nextToLastDate_ > effectiveDate && nextToLastDate_ < terminationDate, () => "next to last date (" + nextToLastDate_ + ") out of effective-termination date range (" + effectiveDate + ", " + terminationDate + "]"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: Utils.QL_REQUIRE( IMM.isIMMdate( nextToLastDate_, false ), () => "next-to-last date (" + nextToLastDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: Utils.QL_FAIL("next to last date incompatible with " + rule_ + " date generation rule"); break; default: Utils.QL_FAIL("unknown rule (" + rule_ + ")"); break; } } // calendar needed for endOfMonth adjustment Calendar nullCalendar = new NullCalendar(); int periods = 1; Date seed = new Date() , exitDate; switch (rule_) { case DateGeneration.Rule.Zero: tenor_ = new Period(0, TimeUnit.Years); dates_.Add(effectiveDate); dates_.Add(terminationDate); isRegular_.Add(true); break; case DateGeneration.Rule.Backward: dates_.Add(terminationDate); seed = terminationDate; if (nextToLastDate_ != null) { dates_.Insert(0, nextToLastDate_); Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); if (temp != nextToLastDate_) isRegular_.Insert(0, false); else isRegular_.Insert(0, true); seed = nextToLastDate_; } exitDate = effectiveDate; if (firstDate_ != null) exitDate = firstDate_; while (true) { Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); if (temp < exitDate) { if (firstDate_ != null && (calendar_.adjust(dates_.First(), convention_) != calendar_.adjust(firstDate_, convention_))) { dates_.Insert(0, firstDate_); isRegular_.Insert(0, false); } break; } else { // skip dates that would result in duplicates // after adjustment if (calendar_.adjust(dates_.First(), convention_) != calendar_.adjust(temp, convention_)) { dates_.Insert(0, temp); isRegular_.Insert(0, true); } ++periods; } } if (calendar_.adjust(dates_.First(), convention) != calendar_.adjust(effectiveDate, convention)) { dates_.Insert(0, effectiveDate); isRegular_.Insert(0, false); } break; case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.ThirdWednesday: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: Utils.QL_REQUIRE( !endOfMonth, () => "endOfMonth convention incompatible with " + rule_ + " date generation rule" ); goto case DateGeneration.Rule.Forward; // fall through case DateGeneration.Rule.Forward: if (rule_ == DateGeneration.Rule.CDS) { dates_.Add(previousTwentieth(effectiveDate, DateGeneration.Rule.CDS)); } else { dates_.Add(effectiveDate); } seed = dates_.Last(); if (firstDate_ != null) { dates_.Add(firstDate_); Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); if (temp != firstDate_) isRegular_.Add(false); else isRegular_.Add(true); seed = firstDate_; } else if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { Date next20th = nextTwentieth(effectiveDate, rule_); if (rule_ == DateGeneration.Rule.OldCDS) { // distance rule inforced in natural days long stubDays = 30; if (next20th - effectiveDate < stubDays) { // +1 will skip this one and get the next next20th = nextTwentieth(next20th + 1, rule_); } } if (next20th != effectiveDate) { dates_.Add(next20th); isRegular_.Add(false); seed = next20th; } } exitDate = terminationDate; if (nextToLastDate_ != null) exitDate = nextToLastDate_; while (true) { Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); if (temp > exitDate) { if (nextToLastDate_ != null && (calendar_.adjust(dates_.Last(), convention_) != calendar_.adjust(nextToLastDate_, convention_))) { dates_.Add(nextToLastDate_); isRegular_.Add(false); } break; } else { // skip dates that would result in duplicates // after adjustment if (calendar_.adjust(dates_.Last(), convention_) != calendar_.adjust(temp, convention_)) { dates_.Add(temp); isRegular_.Add(true); } ++periods; } } if (calendar_.adjust(dates_.Last(), terminationDateConvention_) != calendar_.adjust(terminationDate, terminationDateConvention_)) { if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { dates_.Add(nextTwentieth(terminationDate, rule_)); isRegular_.Add(true); } else { dates_.Add(terminationDate); isRegular_.Add(false); } } break; default: Utils.QL_FAIL("unknown rule (" + rule_ + ")"); break; } // adjustments if (rule_ == DateGeneration.Rule.ThirdWednesday) for (int i = 1; i < dates_.Count-1; ++i) dates_[i] = Date.nthWeekday(3, DayOfWeek.Wednesday, dates_[i].Month, dates_[i].Year); if (endOfMonth && calendar_.isEndOfMonth(seed)) { // adjust to end of month if (convention_ == BusinessDayConvention.Unadjusted) { for (int i = 0; i < dates_.Count-1; ++i) dates_[i] = Date.endOfMonth(dates_[i]); } else { for (int i = 0; i < dates_.Count-1; ++i) dates_[i] = calendar_.endOfMonth(dates_[i]); } if (terminationDateConvention_ != BusinessDayConvention.Unadjusted) dates_[dates_.Count - 1] = calendar_.endOfMonth(dates_.Last()); } else { // first date not adjusted for CDS schedules if (rule_ != DateGeneration.Rule.OldCDS) dates_[0] = calendar_.adjust(dates_[0], convention_); for (int i = 1; i < dates_.Count-1; ++i) dates_[i] = calendar_.adjust(dates_[i], convention_); // termination date is NOT adjusted as per ISDA specifications, unless otherwise specified in the // confirmation of the deal or unless we're creating a CDS schedule if (terminationDateConvention_ != BusinessDayConvention.Unadjusted || rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) dates_[dates_.Count - 1] = calendar_.adjust(dates_.Last(), terminationDateConvention_); } // final safety check to remove duplicated last dates, if any // it can happen if EOM is applied to two near dates if (dates_.Count >= 2 && dates_[dates_.Count - 2] >= dates_.Last()) { isRegular_[dates_.Count() - 2] = (dates_[dates_.Count() - 2] == dates_.Last()); dates_[dates_.Count() - 2] = dates_.Last(); dates_.RemoveAt(dates_.Count - 1); isRegular_.RemoveAt(isRegular_.Count - 1); } }
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, "); Console.WriteLine("quarterly coupon and call dates"); Console.WriteLine("reference date is : " + today ); /* 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("QuantLib price/yld (%) "); Console.WriteLine( "{0:0.00} / {1:0.00} ", callableBond.cleanPrice() , 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) "); Console.WriteLine("96.50 / 5.47"); // 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("QuantLib price/yld (%) "); Console.WriteLine( "{0:0.00} / {1:0.00} ", callableBond.cleanPrice() , 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) "); Console.WriteLine("95.68 / 5.66"); // 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("QuantLib price/yld (%) "); Console.WriteLine("{0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) "); Console.WriteLine("92.34 / 6.49"); // 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("QuantLib price/yld (%) "); Console.WriteLine("{0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) "); Console.WriteLine("87.16 / 7.83"); // 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("QuantLib price/yld (%) "); Console.WriteLine("{0:0.00} / {1:0.00} ", callableBond.cleanPrice(), 100.0 * callableBond.yield(bondDayCounter, Compounding.Compounded, frequency, accuracy, maxIterations)); Console.WriteLine("Bloomberg price/yld (%) "); Console.WriteLine("77.31 / 10.65"); }
void addEffectiveInterestRateAmortizing() { // Amortizing Schedule Schedule schedule = new Schedule(_tradeDate, _maturityDate, new Period(_payFrequency), _calendar, BusinessDayConvention.Unadjusted, BusinessDayConvention.Unadjusted, DateGeneration.Rule.Backward, false); double currentNominal = _marketValue; Date prevDate = _tradeDate; Date actualDate = _tradeDate; for (int i = 1; i < schedule.Count; ++i) { actualDate = schedule[i]; InterestRate rate = new InterestRate(_yield, _dCounter, Compounding.Simple, Frequency.Annual); InterestRate rate2 = new InterestRate(_couponRate, _dCounter, Compounding.Simple, Frequency.Annual); FixedRateCoupon r,r2; if (i > 1) { r = new FixedRateCoupon(currentNominal, actualDate, rate, prevDate, actualDate, prevDate, actualDate); r2 = new FixedRateCoupon(currentNominal, actualDate, rate2, prevDate, actualDate, prevDate, actualDate, null,_originalPayment); } else { Calendar nullCalendar = new NullCalendar(); Period p1 = new Period(_payFrequency); Date testDate = nullCalendar.advance(actualDate, -1 * p1); r = new FixedRateCoupon(currentNominal, actualDate, rate, testDate, actualDate, prevDate, actualDate); r2 = new FixedRateCoupon(currentNominal, actualDate, rate2, testDate, actualDate, prevDate, actualDate, null,_originalPayment); } double amort = Math.Round(Math.Abs(_originalPayment - r.amount()),2); AmortizingPayment p = new AmortizingPayment(amort, actualDate); if (_isPremium) currentNominal -= Math.Abs(amort); else currentNominal += Math.Abs(amort); cashflows_.Add(r2); cashflows_.Add(p); prevDate = actualDate; } // Add single redemption for yield calculation setSingleRedemption(_faceValue, 100, _maturityDate); }
public Schedule(Date effectiveDate, Date terminationDate, Period tenor, Calendar calendar, BusinessDayConvention convention, BusinessDayConvention terminationDateConvention, DateGeneration.Rule rule, bool endOfMonth, Date firstDate, Date nextToLastDate) { // first save the properties fullInterface_ = true; tenor_ = tenor; calendar_ = calendar; convention_ = convention; terminationDateConvention_ = terminationDateConvention; rule_ = rule; endOfMonth_ = endOfMonth; if (firstDate == effectiveDate) { firstDate_ = null; } else { firstDate_ = firstDate; } if (nextToLastDate == terminationDate) { nextToLastDate_ = null; } else { nextToLastDate_ = nextToLastDate; } // sanity checks Utils.QL_REQUIRE(terminationDate != null, "null termination date"); // in many cases (e.g. non-expired bonds) the effective date is not // really necessary. In these cases a decent placeholder is enough if (effectiveDate == null && firstDate == null && rule == DateGeneration.Rule.Backward) { Date evalDate = Settings.evaluationDate(); Utils.QL_REQUIRE(evalDate < terminationDate, "null effective date"); int y; if (nextToLastDate != null) { y = (nextToLastDate - evalDate) / 366 + 1; effectiveDate = nextToLastDate - new Period(y, TimeUnit.Years); } else { y = (terminationDate - evalDate) / 366 + 1; effectiveDate = terminationDate - new Period(y, TimeUnit.Years); } } else { Utils.QL_REQUIRE(effectiveDate != null, "null effective date"); } if (tenor_.length() == 0) { rule_ = DateGeneration.Rule.Zero; } else { Utils.QL_REQUIRE(tenor.length() > 0, "non positive tenor (" + tenor + ") not allowed"); } if (firstDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: Utils.QL_REQUIRE(firstDate_ > effectiveDate && firstDate_ < terminationDate, "first date (" + firstDate_ + ") out of effective-termination date range [" + effectiveDate + ", " + terminationDate + ")"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: Utils.QL_REQUIRE(IMM.isIMMdate(firstDate_, false), "first date (" + firstDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: Utils.QL_FAIL("first date incompatible with " + rule_ + " date generation rule"); break; default: Utils.QL_FAIL("unknown rule (" + rule_ + ")"); break; } } if (nextToLastDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: Utils.QL_REQUIRE(nextToLastDate_ > effectiveDate && nextToLastDate_ < terminationDate, "next to last date (" + nextToLastDate_ + ") out of effective-termination date range (" + effectiveDate + ", " + terminationDate + "]"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: Utils.QL_REQUIRE(IMM.isIMMdate(nextToLastDate_, false), "next-to-last date (" + nextToLastDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: Utils.QL_FAIL("next to last date incompatible with " + rule_ + " date generation rule"); break; default: Utils.QL_FAIL("unknown rule (" + rule_ + ")"); break; } } // calendar needed for endOfMonth adjustment Calendar nullCalendar = new NullCalendar(); int periods = 1; Date seed = new Date(), exitDate; switch (rule_) { case DateGeneration.Rule.Zero: tenor_ = new Period(0, TimeUnit.Years); dates_.Add(effectiveDate); dates_.Add(terminationDate); isRegular_.Add(true); break; case DateGeneration.Rule.Backward: dates_.Add(terminationDate); seed = terminationDate; if (nextToLastDate_ != null) { dates_.Insert(0, nextToLastDate_); Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); if (temp != nextToLastDate_) { isRegular_.Insert(0, false); } else { isRegular_.Insert(0, true); } seed = nextToLastDate_; } exitDate = effectiveDate; if (firstDate_ != null) { exitDate = firstDate_; } while (true) { Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); if (temp < exitDate) { if (firstDate_ != null && (calendar_.adjust(dates_.First(), convention_) != calendar_.adjust(firstDate_, convention_))) { dates_.Insert(0, firstDate_); isRegular_.Insert(0, false); } break; } else { // skip dates that would result in duplicates // after adjustment if (calendar_.adjust(dates_.First(), convention_) != calendar_.adjust(temp, convention_)) { dates_.Insert(0, temp); isRegular_.Insert(0, true); } ++periods; } } if (calendar_.adjust(dates_.First(), convention) != calendar_.adjust(effectiveDate, convention)) { dates_.Insert(0, effectiveDate); isRegular_.Insert(0, false); } break; case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.ThirdWednesday: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: Utils.QL_REQUIRE(!endOfMonth, "endOfMonth convention incompatible with " + rule_ + " date generation rule"); goto case DateGeneration.Rule.Forward; // fall through case DateGeneration.Rule.Forward: if (rule_ == DateGeneration.Rule.CDS) { dates_.Add(previousTwentieth(effectiveDate, DateGeneration.Rule.CDS)); } else { dates_.Add(effectiveDate); } seed = dates_.Last(); if (firstDate_ != null) { dates_.Add(firstDate_); Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); if (temp != firstDate_) { isRegular_.Add(false); } else { isRegular_.Add(true); } seed = firstDate_; } else if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { Date next20th = nextTwentieth(effectiveDate, rule_); if (rule_ == DateGeneration.Rule.OldCDS) { // distance rule inforced in natural days long stubDays = 30; if (next20th - effectiveDate < stubDays) { // +1 will skip this one and get the next next20th = nextTwentieth(next20th + 1, rule_); } } if (next20th != effectiveDate) { dates_.Add(next20th); isRegular_.Add(false); seed = next20th; } } exitDate = terminationDate; if (nextToLastDate_ != null) { exitDate = nextToLastDate_; } while (true) { Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); if (temp > exitDate) { if (nextToLastDate_ != null && (calendar_.adjust(dates_.Last(), convention_) != calendar_.adjust(nextToLastDate_, convention_))) { dates_.Add(nextToLastDate_); isRegular_.Add(false); } break; } else { // skip dates that would result in duplicates // after adjustment if (calendar_.adjust(dates_.Last(), convention_) != calendar_.adjust(temp, convention_)) { dates_.Add(temp); isRegular_.Add(true); } ++periods; } } if (calendar_.adjust(dates_.Last(), terminationDateConvention_) != calendar_.adjust(terminationDate, terminationDateConvention_)) { if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { dates_.Add(nextTwentieth(terminationDate, rule_)); isRegular_.Add(true); } else { dates_.Add(terminationDate); isRegular_.Add(false); } } break; default: Utils.QL_FAIL("unknown rule (" + rule_ + ")"); break; } // adjustments if (rule_ == DateGeneration.Rule.ThirdWednesday) { for (int i = 1; i < dates_.Count - 1; ++i) { dates_[i] = Date.nthWeekday(3, DayOfWeek.Wednesday, dates_[i].Month, dates_[i].Year); } } if (endOfMonth && calendar_.isEndOfMonth(seed)) { // adjust to end of month if (convention_ == BusinessDayConvention.Unadjusted) { for (int i = 0; i < dates_.Count - 1; ++i) { dates_[i] = Date.endOfMonth(dates_[i]); } } else { for (int i = 0; i < dates_.Count - 1; ++i) { dates_[i] = calendar_.endOfMonth(dates_[i]); } } if (terminationDateConvention_ != BusinessDayConvention.Unadjusted) { dates_[dates_.Count - 1] = calendar_.endOfMonth(dates_.Last()); } } else { // first date not adjusted for CDS schedules if (rule_ != DateGeneration.Rule.OldCDS) { dates_[0] = calendar_.adjust(dates_[0], convention_); } for (int i = 1; i < dates_.Count - 1; ++i) { dates_[i] = calendar_.adjust(dates_[i], convention_); } // termination date is NOT adjusted as per ISDA specifications, unless otherwise specified in the // confirmation of the deal or unless we're creating a CDS schedule if (terminationDateConvention_ != BusinessDayConvention.Unadjusted || rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { dates_[dates_.Count - 1] = calendar_.adjust(dates_.Last(), terminationDateConvention_); } } // final safety check to remove duplicated last dates, if any // it can happen if EOM is applied to two near dates if (dates_.Count >= 2 && dates_[dates_.Count - 2] >= dates_.Last()) { isRegular_[dates_.Count() - 2] = (dates_[dates_.Count() - 2] == dates_.Last()); dates_[dates_.Count() - 2] = dates_.Last(); dates_.RemoveAt(dates_.Count - 1); isRegular_.RemoveAt(isRegular_.Count - 1); } }
public Schedule(Date effectiveDate__, Date terminationDate__, Period tenor__, Calendar calendar__, BusinessDayConvention convention__, BusinessDayConvention terminationDateConvention__, DateGeneration.Rule rule__, bool endOfMonth__, Date firstDate__, Date nextToLastDate__) { // first save the properties fullInterface_ = true; tenor_ = tenor__; calendar_ = calendar__; convention_ = convention__; terminationDateConvention_ = terminationDateConvention__; rule_ = rule__; endOfMonth_ = endOfMonth__; firstDate_ = firstDate__; nextToLastDate_ = nextToLastDate__; // sanity checks if (effectiveDate__ == null) { throw new ArgumentException("Null effective date"); } if (terminationDate__ == null) { throw new ArgumentException("Null termination date"); } if (effectiveDate__ >= terminationDate__) { throw new ArgumentException("Effective date (" + effectiveDate__ + ") is later than or equal to termination date (" + terminationDate__ + ")"); } if (tenor_.length() == 0) { rule_ = DateGeneration.Rule.Zero; } else if (tenor_.length() < 0) { throw new ArgumentException("Non positive tenor (" + tenor_ + ") is not allowed"); } if (firstDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: if (!(firstDate_ > effectiveDate__ && firstDate_ < terminationDate__)) { throw new ArgumentException("First date (" + firstDate_ + ") is out of range [effective date (" + effectiveDate__ + "), termination date (" + terminationDate__ + ")]"); } // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: if (!IMM.isIMMdate(firstDate_, false)) { throw new ArgumentException("first date (" + firstDate_ + ") is not an IMM date"); } break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: throw new ArgumentException("First date is incompatible with " + rule_ + " date generation rule"); default: throw new ArgumentException("Unknown DateGeneration rule: " + rule_); } } if (nextToLastDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: if (!(nextToLastDate_ > effectiveDate__ && nextToLastDate_ < terminationDate__)) { throw new ArgumentException("Next to last date (" + nextToLastDate_ + ") out of range [effective date (" + effectiveDate__ + "), termination date (" + terminationDate__ + ")]"); } // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: if (!IMM.isIMMdate(firstDate_, false)) { throw new ArgumentException("first date (" + firstDate_ + ") is not an IMM date"); } break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: throw new ArgumentException("next to last is incompatible with " + rule_ + " date generation rule"); default: throw new ArgumentException("Unknown DateGeneration rule: " + rule_); } } // calendar needed for endOfMonth adjustment Calendar nullCalendar = new NullCalendar(); int periods = 1; Date seed = new Date(), exitDate; switch (rule_) { case DateGeneration.Rule.Zero: tenor_ = new Period(0, TimeUnit.Years); originalDates_.Add(effectiveDate__); originalDates_.Add(terminationDate__); isRegular_.Add(true); break; case DateGeneration.Rule.Backward: originalDates_.Add(terminationDate__); seed = terminationDate__; if (nextToLastDate_ != null) { originalDates_.Insert(0, nextToLastDate_); Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); isRegular_.Insert(0, temp == nextToLastDate_); seed = nextToLastDate_; } exitDate = effectiveDate__; if (firstDate_ != null) { exitDate = firstDate_; } while (true) { Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); if (temp < exitDate) { if (firstDate_ != null && (calendar_.adjust(originalDates_.First(), convention_) != calendar_.adjust(firstDate_, convention_))) { originalDates_.Insert(0, firstDate_); isRegular_.Insert(0, false); } break; } else { originalDates_.Insert(0, temp); isRegular_.Insert(0, true); ++periods; } } if (endOfMonth_ && calendar_.isEndOfMonth(seed)) { convention_ = BusinessDayConvention.Preceding; } if (calendar_.adjust(originalDates_[0], convention_) != calendar_.adjust(effectiveDate__, convention_)) { originalDates_.Insert(0, effectiveDate__); isRegular_.Insert(0, false); } break; case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.ThirdWednesday: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: if (endOfMonth_) { throw new ArgumentException("endOfMonth convention is incompatible with " + rule_ + " date generation rule"); } goto case DateGeneration.Rule.Forward; // fall through case DateGeneration.Rule.Forward: if (rule_ == DateGeneration.Rule.CDS) { originalDates_.Add(previousTwentieth(effectiveDate__, DateGeneration.Rule.CDS)); } else { originalDates_.Add(effectiveDate__); } seed = effectiveDate__; if (firstDate_ != null) { originalDates_.Add(firstDate_); Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); isRegular_.Add(temp == firstDate_); seed = firstDate_; } else if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { Date next20th = nextTwentieth(effectiveDate__, rule_); if (rule_ == DateGeneration.Rule.OldCDS) { // distance rule inforced in natural days long stubDays = 30; if (next20th - effectiveDate__ < stubDays) { // +1 will skip this one and get the next next20th = nextTwentieth(next20th + 1, rule_); } } if (next20th != effectiveDate__) { originalDates_.Add(next20th); isRegular_.Add(false); seed = next20th; } } exitDate = terminationDate__; if (nextToLastDate_ != null) { exitDate = nextToLastDate_; } while (true) { Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); if (temp > exitDate) { if (nextToLastDate_ != null && (calendar_.adjust(originalDates_.Last(), convention_) != calendar_.adjust(nextToLastDate_, convention_))) { originalDates_.Add(nextToLastDate_); isRegular_.Add(false); } break; } else { originalDates_.Add(temp); isRegular_.Add(true); ++periods; } } if (endOfMonth_ && calendar_.isEndOfMonth(seed)) { convention_ = BusinessDayConvention.Preceding; } if (calendar_.adjust(originalDates_.Last(), terminationDateConvention_) != calendar_.adjust(terminationDate__, terminationDateConvention_)) { if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { originalDates_.Add(nextTwentieth(terminationDate__, rule_)); isRegular_.Add(true); } else { originalDates_.Add(terminationDate__); isRegular_.Add(false); } } break; default: throw new ArgumentException("Unknown DateGeneration rule: " + rule_); } // adjustments to holidays, etc. if (rule_ == DateGeneration.Rule.ThirdWednesday) { for (int i = 1; i < originalDates_.Count; ++i) { originalDates_[i] = Date.nthWeekday(3, DayOfWeek.Wednesday, originalDates_[i].Month, originalDates_[i].Year); } } if (endOfMonth && calendar_.isEndOfMonth(seed)) { // adjust to end of month if (convention_ == BusinessDayConvention.Unadjusted) { for (int i = 0; i < originalDates_.Count; ++i) { originalDates_[i] = Date.endOfMonth(originalDates_[i]); } } else { for (int i = 0; i < originalDates_.Count; ++i) { originalDates_[i] = calendar_.endOfMonth(originalDates_[i]); } } if (terminationDateConvention_ == BusinessDayConvention.Unadjusted) { originalDates_[originalDates_.Count - 1] = Date.endOfMonth(originalDates_.Last()); } else { originalDates_[originalDates_.Count - 1] = calendar_.endOfMonth(originalDates_.Last()); } } else { // first date not adjusted for CDS schedules if (rule_ != DateGeneration.Rule.OldCDS) { originalDates_[0] = calendar_.adjust(originalDates_[0], convention_); } for (int i = 1; i < originalDates_.Count; ++i) { originalDates_[i] = calendar_.adjust(originalDates_[i], convention_); } foreach (Date d in originalDates_) { adjustedDates_.Add(d); } // termination date is NOT adjusted as per ISDA specifications, unless otherwise specified in the // confirmation of the deal or unless we're creating a CDS schedule if (terminationDateConvention_ != BusinessDayConvention.Unadjusted || rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { adjustedDates_[adjustedDates_.Count - 1] = calendar_.adjust(originalDates_.Last(), terminationDateConvention_); } } }
public Schedule(Date effectiveDate__, Date terminationDate__, Period tenor__, Calendar calendar__, BusinessDayConvention convention__, BusinessDayConvention terminationDateConvention__, DateGeneration.Rule rule__, bool endOfMonth__, Date firstDate__, Date nextToLastDate__) { // first save the properties fullInterface_ = true; tenor_ = tenor__; calendar_ = calendar__; convention_ = convention__; terminationDateConvention_ = terminationDateConvention__; rule_ = rule__; endOfMonth_ = endOfMonth__; firstDate_ = firstDate__; nextToLastDate_ = nextToLastDate__; // sanity checks if (effectiveDate__ == null) throw new ArgumentException("Null effective date"); if (terminationDate__ == null) throw new ArgumentException("Null termination date"); if (effectiveDate__ >= terminationDate__) throw new ArgumentException("Effective date (" + effectiveDate__ + ") is later than or equal to termination date (" + terminationDate__ + ")"); if (tenor_.length() == 0) rule_ = DateGeneration.Rule.Zero; else if (tenor_.length() < 0) throw new ArgumentException("Non positive tenor (" + tenor_ + ") is not allowed"); if (firstDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: if (!(firstDate_ > effectiveDate__ && firstDate_ < terminationDate__)) throw new ArgumentException("First date (" + firstDate_ + ") is out of range [effective date (" + effectiveDate__ + "), termination date (" + terminationDate__ + ")]"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: if (!IMM.isIMMdate(firstDate_, false)) throw new ArgumentException("first date (" + firstDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: throw new ArgumentException("First date is incompatible with " + rule_ + " date generation rule"); default: throw new ArgumentException("Unknown DateGeneration rule: " + rule_); } } if (nextToLastDate_ != null) { switch (rule_) { case DateGeneration.Rule.Backward: case DateGeneration.Rule.Forward: if (!(nextToLastDate_ > effectiveDate__ && nextToLastDate_ < terminationDate__)) throw new ArgumentException("Next to last date (" + nextToLastDate_ + ") out of range [effective date (" + effectiveDate__ + "), termination date (" + terminationDate__ + ")]"); // we should ensure that the above condition is still verified after adjustment break; case DateGeneration.Rule.ThirdWednesday: if (!IMM.isIMMdate(firstDate_, false)) throw new ArgumentException("first date (" + firstDate_ + ") is not an IMM date"); break; case DateGeneration.Rule.Zero: case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: throw new ArgumentException("next to last is incompatible with " + rule_ + " date generation rule"); default: throw new ArgumentException("Unknown DateGeneration rule: " + rule_); } } // calendar needed for endOfMonth adjustment Calendar nullCalendar = new NullCalendar(); int periods = 1; Date seed = new Date(), exitDate; switch (rule_) { case DateGeneration.Rule.Zero: tenor_ = new Period(0, TimeUnit.Years); originalDates_.Add(effectiveDate__); originalDates_.Add(terminationDate__); isRegular_.Add(true); break; case DateGeneration.Rule.Backward: originalDates_.Add(terminationDate__); seed = terminationDate__; if (nextToLastDate_ != null) { originalDates_.Insert(0, nextToLastDate_); Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); isRegular_.Insert(0, temp == nextToLastDate_); seed = nextToLastDate_; } exitDate = effectiveDate__; if (firstDate_ != null) exitDate = firstDate_; while (true) { Date temp = nullCalendar.advance(seed, -periods * tenor_, convention_, endOfMonth_); if (temp < exitDate) { if (firstDate_ != null && (calendar_.adjust(originalDates_.First(), convention_) != calendar_.adjust(firstDate_, convention_))) { originalDates_.Insert(0, firstDate_); isRegular_.Insert(0, false); } break; } else { originalDates_.Insert(0, temp); isRegular_.Insert(0, true); ++periods; } } if (endOfMonth_ && calendar_.isEndOfMonth(seed)) convention_ = BusinessDayConvention.Preceding; if (calendar_.adjust(originalDates_[0], convention_) != calendar_.adjust(effectiveDate__, convention_)) { originalDates_.Insert(0, effectiveDate__); isRegular_.Insert(0, false); } break; case DateGeneration.Rule.Twentieth: case DateGeneration.Rule.TwentiethIMM: case DateGeneration.Rule.ThirdWednesday: case DateGeneration.Rule.OldCDS: case DateGeneration.Rule.CDS: if (endOfMonth_) throw new ArgumentException("endOfMonth convention is incompatible with " + rule_ + " date generation rule"); goto case DateGeneration.Rule.Forward; // fall through case DateGeneration.Rule.Forward: if (rule_ == DateGeneration.Rule.CDS) { originalDates_.Add(previousTwentieth(effectiveDate__,DateGeneration.Rule.CDS)); } else { originalDates_.Add(effectiveDate__); } seed = effectiveDate__; if (firstDate_ != null) { originalDates_.Add(firstDate_); Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); isRegular_.Add(temp == firstDate_); seed = firstDate_; } else if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) { Date next20th = nextTwentieth(effectiveDate__, rule_); if (rule_ == DateGeneration.Rule.OldCDS) { // distance rule inforced in natural days long stubDays = 30; if (next20th - effectiveDate__ < stubDays) { // +1 will skip this one and get the next next20th = nextTwentieth(next20th + 1, rule_); } } if (next20th != effectiveDate__) { originalDates_.Add(next20th); isRegular_.Add(false); seed = next20th; } } exitDate = terminationDate__; if (nextToLastDate_ != null) exitDate = nextToLastDate_; while (true) { Date temp = nullCalendar.advance(seed, periods * tenor_, convention_, endOfMonth_); if (temp > exitDate) { if (nextToLastDate_ != null && (calendar_.adjust(originalDates_.Last(), convention_) != calendar_.adjust(nextToLastDate_, convention_))) { originalDates_.Add(nextToLastDate_); isRegular_.Add(false); } break; } else { originalDates_.Add(temp); isRegular_.Add(true); ++periods; } } if (endOfMonth_ && calendar_.isEndOfMonth(seed)) convention_ = BusinessDayConvention.Preceding; if (calendar_.adjust(originalDates_.Last(), terminationDateConvention_) != calendar_.adjust(terminationDate__, terminationDateConvention_)) { if (rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS ) { originalDates_.Add(nextTwentieth(terminationDate__, rule_)); isRegular_.Add(true); } else { originalDates_.Add(terminationDate__); isRegular_.Add(false); } } break; default: throw new ArgumentException("Unknown DateGeneration rule: " + rule_); } // adjustments to holidays, etc. if (rule_ == DateGeneration.Rule.ThirdWednesday) for (int i = 1; i < originalDates_.Count; ++i) originalDates_[i] = Date.nthWeekday(3, DayOfWeek.Wednesday, originalDates_[i].Month, originalDates_[i].Year); if (endOfMonth && calendar_.isEndOfMonth(seed)) { // adjust to end of month if (convention_ == BusinessDayConvention.Unadjusted) { for (int i = 0; i < originalDates_.Count; ++i) originalDates_[i] = Date.endOfMonth(originalDates_[i]); } else { for (int i = 0; i < originalDates_.Count; ++i) originalDates_[i] = calendar_.endOfMonth(originalDates_[i]); } if (terminationDateConvention_ == BusinessDayConvention.Unadjusted) originalDates_[originalDates_.Count - 1] = Date.endOfMonth(originalDates_.Last()); else originalDates_[originalDates_.Count - 1] = calendar_.endOfMonth(originalDates_.Last()); } else { // first date not adjusted for CDS schedules if (rule_ != DateGeneration.Rule.OldCDS) originalDates_[0] = calendar_.adjust(originalDates_[0], convention_); for (int i = 1; i < originalDates_.Count; ++i) originalDates_[i] = calendar_.adjust(originalDates_[i], convention_); foreach (Date d in originalDates_) adjustedDates_.Add(d); // termination date is NOT adjusted as per ISDA specifications, unless otherwise specified in the // confirmation of the deal or unless we're creating a CDS schedule if (terminationDateConvention_ != BusinessDayConvention.Unadjusted || rule_ == DateGeneration.Rule.Twentieth || rule_ == DateGeneration.Rule.TwentiethIMM || rule_ == DateGeneration.Rule.OldCDS || rule_ == DateGeneration.Rule.CDS) adjustedDates_[adjustedDates_.Count - 1] = calendar_.adjust(originalDates_.Last(), terminationDateConvention_); } }