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); }
/// <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.Instance.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); }