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 Schedule value() { // check for mandatory arguments Utils.QL_REQUIRE(effectiveDate_ != null, () => "effective date not provided"); Utils.QL_REQUIRE(terminationDate_ != null, () => "termination date not provided"); Utils.QL_REQUIRE((object)tenor_ != null, () => "tenor/frequency not provided"); // if no calendar was set... if (calendar_ == null) { // ...we use a null one. calendar_ = new NullCalendar(); } // set dynamic defaults: BusinessDayConvention convention; // if a convention was set, we use it. if (convention_ != null) { convention = convention_.Value; } else { if (!calendar_.empty()) { // ...if we set a calendar, we probably want it to be used; convention = BusinessDayConvention.Following; } else { // if not, we don't care. convention = BusinessDayConvention.Unadjusted; } } BusinessDayConvention terminationDateConvention; // if set explicitly, we use it; if (terminationDateConvention_ != null) { terminationDateConvention = terminationDateConvention_.Value; } else { // Unadjusted as per ISDA specification terminationDateConvention = convention; } return(new Schedule(effectiveDate_, terminationDate_, tenor_, calendar_, convention, terminationDateConvention, rule_, endOfMonth_, firstDate_, nextToLastDate_)); }
public double fixing(Date fixingDate, bool forecastTodaysFixing) { Date today = Settings.evaluationDate(); Date todayMinusLag = today - availabilityLag_; KeyValuePair <Date, Date> limm = Utils.inflationPeriod(todayMinusLag, frequency_); Date lastFix = limm.Key - 1; Date flatMustForecastOn = lastFix + 1; Date interpMustForecastOn = lastFix + 1 - new Period(frequency_); if (interpolated() && fixingDate >= interpMustForecastOn) { return(forecastFixing(fixingDate)); } if (!interpolated() && fixingDate >= flatMustForecastOn) { return(forecastFixing(fixingDate)); } // four cases with ratio() and interpolated() if (ratio()) { if (interpolated()) { // IS ratio, IS interpolated KeyValuePair <Date, Date> lim = Utils.inflationPeriod(fixingDate, frequency_); Date fixMinus1Y = new NullCalendar().advance(fixingDate, new Period(-1, TimeUnit.Years), BusinessDayConvention.ModifiedFollowing); KeyValuePair <Date, Date> limBef = Utils.inflationPeriod(fixMinus1Y, frequency_); double dp = lim.Value + 1 - lim.Key; double dpBef = limBef.Value + 1 - limBef.Key; double dl = fixingDate - lim.Key; // potentially does not work on 29th Feb double dlBef = fixMinus1Y - limBef.Key; // get the four relevant fixings // recall that they are stored flat for every day double limFirstFix = IndexManager.instance().getHistory(name()).value()[lim.Key]; if (limFirstFix == null) { throw new ApplicationException("Missing " + name() + " fixing for " + lim.Key); } double limSecondFix = IndexManager.instance().getHistory(name()).value()[lim.Value + 1]; if (limSecondFix == null) { throw new ApplicationException("Missing " + name() + " fixing for " + lim.Value + 1); } double limBefFirstFix = IndexManager.instance().getHistory(name()).value()[limBef.Key]; if (limBefFirstFix == null) { throw new ApplicationException("Missing " + name() + " fixing for " + limBef.Key); } double limBefSecondFix = IndexManager.instance().getHistory(name()).value()[limBef.Value + 1]; if (limBefSecondFix == null) { throw new ApplicationException("Missing " + name() + " fixing for " + limBef.Value + 1); } double linearNow = limFirstFix + (limSecondFix - limFirstFix) * dl / dp; double linearBef = limBefFirstFix + (limBefSecondFix - limBefFirstFix) * dlBef / dpBef; double wasYES = linearNow / linearBef - 1.0; return(wasYES); } else { // IS ratio, NOT interpolated double pastFixing = IndexManager.instance().getHistory(name()).value()[fixingDate]; if (pastFixing == null) { throw new ApplicationException("Missing " + name() + " fixing for " + fixingDate); } Date previousDate = fixingDate - new Period(1, TimeUnit.Years); double previousFixing = IndexManager.instance().getHistory(name()).value()[previousDate]; if (previousFixing == null) { throw new ApplicationException("Missing " + name() + " fixing for " + previousDate); } return(pastFixing / previousFixing - 1.0); } } else { // NOT ratio if (interpolated()) { // NOT ratio, IS interpolated KeyValuePair <Date, Date> lim = Utils.inflationPeriod(fixingDate, frequency_); double dp = lim.Value + 1 - lim.Key; double dl = fixingDate - lim.Key; double limFirstFix = IndexManager.instance().getHistory(name()).value()[lim.Key]; if (limFirstFix == null) { throw new ApplicationException("Missing " + name() + " fixing for " + lim.Key); } double limSecondFix = IndexManager.instance().getHistory(name()).value()[lim.Value + 1]; if (limSecondFix == null) { throw new ApplicationException("Missing " + name() + " fixing for " + lim.Value + 1); } double linearNow = limFirstFix + (limSecondFix - limFirstFix) * dl / dp; return(linearNow); } else { // NOT ratio, NOT interpolated // so just flat double pastFixing = IndexManager.instance().getHistory(name()).value()[fixingDate]; if (pastFixing == null) { throw new ApplicationException("Missing " + name() + " fixing for " + fixingDate); } return(pastFixing); } } // QL_FAIL("YoYInflationIndex::fixing, should never get here"); }
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 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)); } }
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); } }
/// <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__, 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 void testYYIndex() { // Testing year-on-year inflation indices SavedSettings backup = new SavedSettings(); //IndexHistoryCleaner cleaner = new IndexHistoryCleaner(); YYEUHICP yyeuhicp = new YYEUHICP( true ); if ( yyeuhicp.name() != "EU YY_HICP" || yyeuhicp.frequency() != Frequency.Monthly || yyeuhicp.revised() || !yyeuhicp.interpolated() || yyeuhicp.ratio() || yyeuhicp.availabilityLag() != new Period( 1, TimeUnit.Months ) ) { Assert.Fail( "wrong year-on-year EU HICP data (" + yyeuhicp.name() + ", " + yyeuhicp.frequency() + ", " + yyeuhicp.revised() + ", " + yyeuhicp.interpolated() + ", " + yyeuhicp.ratio() + ", " + yyeuhicp.availabilityLag() + ")" ); } YYEUHICPr yyeuhicpr = new YYEUHICPr( true ); if ( yyeuhicpr.name() != "EU YYR_HICP" || yyeuhicpr.frequency() != Frequency.Monthly || yyeuhicpr.revised() || !yyeuhicpr.interpolated() || !yyeuhicpr.ratio() || yyeuhicpr.availabilityLag() != new Period( 1, TimeUnit.Months ) ) { Assert.Fail( "wrong year-on-year EU HICPr data (" + yyeuhicpr.name() + ", " + yyeuhicpr.frequency() + ", " + yyeuhicpr.revised() + ", " + yyeuhicpr.interpolated() + ", " + yyeuhicpr.ratio() + ", " + yyeuhicpr.availabilityLag() + ")" ); } YYUKRPI yyukrpi = new YYUKRPI( false ); if ( yyukrpi.name() != "UK YY_RPI" || yyukrpi.frequency() != Frequency.Monthly || yyukrpi.revised() || yyukrpi.interpolated() || yyukrpi.ratio() || yyukrpi.availabilityLag() != new Period( 1, TimeUnit.Months ) ) { Assert.Fail( "wrong year-on-year UK RPI data (" + yyukrpi.name() + ", " + yyukrpi.frequency() + ", " + yyukrpi.revised() + ", " + yyukrpi.interpolated() + ", " + yyukrpi.ratio() + ", " + yyukrpi.availabilityLag() + ")" ); } YYUKRPIr yyukrpir = new YYUKRPIr( false ); if ( yyukrpir.name() != "UK YYR_RPI" || yyukrpir.frequency() != Frequency.Monthly || yyukrpir.revised() || yyukrpir.interpolated() || !yyukrpir.ratio() || yyukrpir.availabilityLag() != new Period( 1, TimeUnit.Months ) ) { Assert.Fail( "wrong year-on-year UK RPIr data (" + yyukrpir.name() + ", " + yyukrpir.frequency() + ", " + yyukrpir.revised() + ", " + yyukrpir.interpolated() + ", " + yyukrpir.ratio() + ", " + yyukrpir.availabilityLag() + ")" ); } // Retrieval test. //---------------- // make sure of the evaluation date Date evaluationDate = new Date( 13, Month.August, 2007 ); evaluationDate = new UnitedKingdom().adjust( evaluationDate ); Settings.setEvaluationDate( evaluationDate ); // fixing data Date from = new Date( 1, Month.January, 2005 ); Date to = new Date( 13, Month.August, 2007 ); Schedule rpiSchedule = new MakeSchedule().from( from ).to( to ) .withTenor( new Period( 1, TimeUnit.Months ) ) .withCalendar( new UnitedKingdom() ) .withConvention( BusinessDayConvention.ModifiedFollowing ).value(); double[] fixData = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0, 192.2, 192.2, 192.6, 193.1, 193.3, 193.6, 194.1, 193.4, 194.2, 195.0, 196.5, 197.7, 198.5, 198.5, 199.2, 200.1, 200.4, 201.1, 202.7, 201.6, 203.1, 204.4, 205.4, 206.2, 207.3 }; bool interp = false; YYUKRPIr iir = new YYUKRPIr( interp ); YYUKRPIr iirYES = new YYUKRPIr( true ); for ( int i = 0; i < fixData.Length; i++ ) { iir.addFixing( rpiSchedule[i], fixData[i] ); iirYES.addFixing( rpiSchedule[i], fixData[i] ); } Date todayMinusLag = evaluationDate - iir.availabilityLag(); KeyValuePair<Date, Date> lim0 = Utils.inflationPeriod( todayMinusLag, iir.frequency() ); todayMinusLag = lim0.Value + 1 - 2 * new Period( iir.frequency() ); double eps = 1.0e-8; // Interpolation tests //-------------------- // (no TS so can't forecast). for ( int i = 13; i < rpiSchedule.Count; i++ ) { KeyValuePair<Date, Date> lim = Utils.inflationPeriod( rpiSchedule[i], iir.frequency() ); KeyValuePair<Date, Date> limBef = Utils.inflationPeriod( rpiSchedule[i - 12], iir.frequency() ); for ( Date d = lim.Key; d <= lim.Value; d++ ) { if ( d < todayMinusLag ) { double expected = fixData[i] / fixData[i - 12] - 1.0; double calculated = iir.fixing( d ); Assert.IsTrue( Math.Abs( calculated - expected ) < eps, "Non-interpolated fixings not constant within a period: " + calculated + ", should be " + expected ); double dp = lim.Value + 1 - lim.Key; double dpBef = limBef.Value + 1 - limBef.Key; double dl = d - lim.Key; // potentially does not work on 29th Feb double dlBef = new NullCalendar().advance( d, -new Period( 1, TimeUnit.Years ), BusinessDayConvention.ModifiedFollowing ) - limBef.Key; double linearNow = fixData[i] + ( fixData[i + 1] - fixData[i] ) * dl / dp; double linearBef = fixData[i - 12] + ( fixData[i + 1 - 12] - fixData[i - 12] ) * dlBef / dpBef; double expectedYES = linearNow / linearBef - 1.0; double calculatedYES = iirYES.fixing( d ); Assert.IsTrue( Math.Abs( expectedYES - calculatedYES ) < eps, "Error in interpolated fixings: expect " + expectedYES + " see " + calculatedYES + " flat " + calculated + ", data: " + fixData[i - 12] + ", " + fixData[i + 1 - 12] + ", " + fixData[i] + ", " + fixData[i + 1] + ", fac: " + dp + ", " + dl + ", " + dpBef + ", " + dlBef + ", to: " + linearNow + ", " + linearBef ); } } } }
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"); }
public void testInArrears() { //("Testing in-arrears swap calculation..."); CommonVars vars = new CommonVars(); /* See Hull, 4th ed., page 550 Note: the calculation in the book is wrong (work out the adjustment and you'll get 0.05 + 0.000115 T1) */ Date maturity = vars.today + new Period(5, TimeUnit.Years); Calendar calendar = new NullCalendar(); Schedule schedule = new Schedule(vars.today, maturity, new Period(Frequency.Annual), calendar, BusinessDayConvention.Following, BusinessDayConvention.Following, DateGeneration.Rule.Forward, false); DayCounter dayCounter = new SimpleDayCounter(); List<double> nominals = new List<double>() { 100000000.0 }; IborIndex index = new IborIndex("dummy", new Period(1, TimeUnit.Years), 0, new EURCurrency(), calendar, BusinessDayConvention.Following, false, dayCounter, vars.termStructure); double oneYear = 0.05; double r = Math.Log(1.0 + oneYear); vars.termStructure.linkTo(Utilities.flatRate(vars.today, r, dayCounter)); List<double> coupons = new List<double>() { oneYear }; List<CashFlow> fixedLeg = new FixedRateLeg(schedule) .withCouponRates(coupons, dayCounter) .withNotionals(nominals); List<double> gearings = new List<double>(); List<double> spreads = new List<double>(); int fixingDays = 0; double capletVolatility = 0.22; var vol = new Handle<OptionletVolatilityStructure>( new ConstantOptionletVolatility(vars.today, new NullCalendar(), BusinessDayConvention.Following, capletVolatility, dayCounter)); IborCouponPricer pricer = new BlackIborCouponPricer(vol); List<CashFlow> floatingLeg = new IborLeg(schedule, index) .withPaymentDayCounter(dayCounter) .withFixingDays(fixingDays) .withGearings(gearings) .withSpreads(spreads) .inArrears() .withNotionals(nominals); Utils.setCouponPricer(floatingLeg, pricer); Swap swap = new Swap(floatingLeg, fixedLeg); swap.setPricingEngine(new DiscountingSwapEngine(vars.termStructure)); double storedValue = -144813.0; double tolerance = 1.0; if (Math.Abs(swap.NPV() - storedValue) > tolerance) Assert.Fail("Wrong NPV calculation:\n" + " expected: " + storedValue + "\n" + " calculated: " + swap.NPV()); }
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 MakeSchedule(Date effectiveDate, Date terminationDate, Period tenor, Calendar calendar, // BusinessDayConvention convention) { // calendar_ = calendar; // effectiveDate_ = effectiveDate; // terminationDate_ = terminationDate; // tenor_ = tenor; // convention_ = convention; // terminationDateConvention_ = convention; // rule_ = DateGeneration.Rule.Backward; // endOfMonth_ = false; // firstDate_ = nextToLastDate_ = null; //} public Schedule value() { // check for mandatory arguments if (effectiveDate_ == null) throw new ApplicationException("effective date not provided"); if (terminationDate_ == null) throw new ApplicationException("termination date not provided"); if ((object)tenor_ == null) throw new ApplicationException("tenor/frequency not provided"); // set dynamic defaults: BusinessDayConvention convention; // if a convention was set, we use it. if (convention_ != null ) { convention = convention_; } else { if (!calendar_.empty()) { // ...if we set a calendar, we probably want it to be used; convention = BusinessDayConvention.Following; } else { // if not, we don't care. convention = BusinessDayConvention.Unadjusted; } } BusinessDayConvention terminationDateConvention; // if set explicitly, we use it; if (terminationDateConvention_ != null ) { terminationDateConvention = terminationDateConvention_; } else { // Unadjusted as per ISDA specification terminationDateConvention = convention; } Calendar calendar = calendar_; // if no calendar was set... if (calendar.empty()) { // ...we use a null one. calendar = new NullCalendar(); } return new Schedule(effectiveDate_, terminationDate_, tenor_, calendar_, convention_, terminationDateConvention_, rule_, endOfMonth_, firstDate_, nextToLastDate_); }
public override void calculate(DateTime calcDate, FP_Parameter fp_parameter) { // master data load this.indexOptionDAO_.SelectOwn(); // market data load // index data clsHDAT_MARKETDATA_TB clstb = new clsHDAT_MARKETDATA_TB(); string calcDateStr = calcDate.ToString("yyyyMMdd"); QLNet.Settings.setEvaluationDate(calcDate); clstb.REF_DT = calcDateStr; clstb.INDEX_CD = this.indexOptionDAO_.UNDERLYING_INDEX_CD; int checkNum = clstb.SelectOwn(); if (checkNum == 0) { throw new Exception("market data does not exist : " + calcDateStr + " " + clstb.INDEX_CD); } double indexData = clstb.LAST; // curveData -------------------------------------------------- string curve_cd = "IRSKRW"; YieldCurve curveManager = new YieldCurve(); curveManager.loadCurveData(calcDate,curve_cd,clsHDAT_CURVEDATA_TB.RATE_TYP_Type.YTM); QLNet.YieldTermStructure yield_ts = curveManager.yieldCurve(); // calculate string maturityDateStr = this.indexOptionDAO_.MATURITY_DT; System.Globalization.CultureInfo us = new System.Globalization.CultureInfo("en-US"); DateTime maturityDate = DateTime.ParseExact(maturityDateStr, "yyyyMMdd", us); DayCounter dc = new Actual365Fixed(); Calendar cal = new NullCalendar(); double vol = 0.3; double strike = this.indexOptionDAO_.STRIKE; PlainVanillaPayoff strikePayoff = new PlainVanillaPayoff(Option.Type. Call, strike); Exercise exercise = new EuropeanExercise(maturityDate); VanillaOption q_option = new VanillaOption(strikePayoff,exercise); Handle<Quote> x0 = new Handle<Quote>(new SimpleQuote(indexData)); FlatForward flatForward = new FlatForward(calcDate,0.01,dc); Handle<YieldTermStructure> dividendTS = new Handle<YieldTermStructure>(flatForward); Handle<YieldTermStructure> riskFreeTS = new Handle<YieldTermStructure>(yield_ts); BlackConstantVol blackConstVol = new BlackConstantVol(calcDate,cal,vol,dc); Handle<BlackVolTermStructure> blackVolTS = new Handle<BlackVolTermStructure>(blackConstVol); GeneralizedBlackScholesProcess process =new GeneralizedBlackScholesProcess(x0 ,dividendTS,riskFreeTS,blackVolTS); AnalyticEuropeanEngine europeanEngine = new AnalyticEuropeanEngine(process); q_option.setPricingEngine(europeanEngine); double value = q_option.NPV(); double indexMultiplier = this.indexOptionDAO_.INDEX_MULTIPLIER; int quantity = this.indexOptionDAO_.QUANTITY; clsHITM_FP_GREEKRESULT_TB result_tb = new clsHITM_FP_GREEKRESULT_TB(); result_tb.FP_GREEKRESULT_ID = IDGenerator.getNewGreekResultID(this.indexOptionDAO_.INSTRUMENT_ID,calcDateStr); result_tb.CALC_DT = calcDateStr; result_tb.INSTRUMENT_ID = this.indexOptionDAO_.INSTRUMENT_ID; result_tb.INSTRUMENT_TYP = this.indexOptionDAO_.INSTRUMENT_TYP; result_tb.UNDERLYING_ID = "KOSPI200"; result_tb.UNDERLYING_VALUE = indexData; //result_tb.SEQ = 1; result_tb.DELTA = (q_option.delta() * indexData / 100) * indexMultiplier * quantity; // 1% Delta result_tb.GAMMA = 0.5 * (q_option.gamma() * indexData / 100) * indexMultiplier * quantity; // 1% Gamma result_tb.VEGA = q_option.vega() / 100 * indexMultiplier * quantity; // 1% point Vega result_tb.CALC_PRICE = value * indexMultiplier * quantity; result_tb.CALCULATED_FLAG = (int)clsHITM_FP_GREEKRESULT_TB.CALCULATED_FLAG_Type.CALCULATED; result_tb.CALCULATED_TIME = DateTime.Now.ToString("HHmmss"); ; result_tb.CALCULATE_TYP = (int)clsHITM_FP_GREEKRESULT_TB.CALCULATE_TYP_Type.ANALYTICS; // price if (result_tb.UpdateDateResult() == 0) { throw new Exception("update result fail. no exist , calcDate : " + calcDate.ToString("yyyyMMdd") + " , inst_id : " + result_tb.INSTRUMENT_ID); } // delta // gamma and others : no exist ? }
public double fixing(Date fixingDate, bool forecastTodaysFixing) { Date today = Settings.evaluationDate(); Date todayMinusLag = today - availabilityLag_; KeyValuePair<Date,Date> limm = Utils.inflationPeriod(todayMinusLag, frequency_); Date lastFix = limm.Key-1; Date flatMustForecastOn = lastFix+1; Date interpMustForecastOn = lastFix+1 - new Period(frequency_); if (interpolated() && fixingDate >= interpMustForecastOn) { return forecastFixing(fixingDate); } if (!interpolated() && fixingDate >= flatMustForecastOn) { return forecastFixing(fixingDate); } // four cases with ratio() and interpolated() if (ratio()) { if(interpolated()) { // IS ratio, IS interpolated KeyValuePair<Date,Date> lim = Utils.inflationPeriod(fixingDate, frequency_); Date fixMinus1Y= new NullCalendar().advance(fixingDate, new Period(-1,TimeUnit.Years),BusinessDayConvention.ModifiedFollowing); KeyValuePair<Date,Date> limBef = Utils.inflationPeriod(fixMinus1Y, frequency_); double dp= lim.Value + 1 - lim.Key; double dpBef=limBef.Value + 1 - limBef.Key; double dl = fixingDate-lim.Key; // potentially does not work on 29th Feb double dlBef = fixMinus1Y - limBef.Key; // get the four relevant fixings // recall that they are stored flat for every day double limFirstFix = IndexManager.instance().getHistory(name()).value()[lim.Key]; if( limFirstFix == null) throw new ApplicationException("Missing " + name() + " fixing for " + lim.Key ); double limSecondFix = IndexManager.instance().getHistory(name()).value()[lim.Value+1]; if ( limSecondFix == null ) throw new ApplicationException("Missing " + name() + " fixing for " + lim.Value+1 ); double limBefFirstFix = IndexManager.instance().getHistory(name()).value()[limBef.Key]; if ( limBefFirstFix == null ) throw new ApplicationException("Missing " + name() + " fixing for " + limBef.Key ); double limBefSecondFix = IndexManager.instance().getHistory(name()).value()[limBef.Value+1]; if ( limBefSecondFix == null ) throw new ApplicationException("Missing " + name() + " fixing for " + limBef.Value+1 ); double linearNow = limFirstFix + (limSecondFix-limFirstFix)*dl/dp; double linearBef = limBefFirstFix + (limBefSecondFix-limBefFirstFix)*dlBef/dpBef; double wasYES = linearNow / linearBef - 1.0; return wasYES; } else { // IS ratio, NOT interpolated double pastFixing = IndexManager.instance().getHistory(name()).value()[fixingDate]; if ( pastFixing == null ) throw new ApplicationException("Missing " + name() + " fixing for " + fixingDate); Date previousDate = fixingDate - new Period(1,TimeUnit.Years); double previousFixing = IndexManager.instance().getHistory(name()).value()[previousDate]; if( previousFixing == null ) throw new ApplicationException("Missing " + name() + " fixing for " + previousDate ); return pastFixing/previousFixing - 1.0; } } else { // NOT ratio if (interpolated()) { // NOT ratio, IS interpolated KeyValuePair<Date,Date> lim = Utils.inflationPeriod(fixingDate, frequency_); double dp= lim.Value + 1 - lim.Key; double dl = fixingDate-lim.Key; double limFirstFix = IndexManager.instance().getHistory(name()).value()[lim.Key]; if ( limFirstFix == null ) throw new ApplicationException("Missing " + name() + " fixing for " + lim.Key ); double limSecondFix = IndexManager.instance().getHistory(name()).value()[lim.Value+1]; if ( limSecondFix == null ) throw new ApplicationException("Missing " + name() + " fixing for " + lim.Value+1 ); double linearNow = limFirstFix + (limSecondFix-limFirstFix)*dl/dp; return linearNow; } else { // NOT ratio, NOT interpolated // so just flat double pastFixing = IndexManager.instance().getHistory(name()).value()[fixingDate]; if ( pastFixing == null ) throw new ApplicationException("Missing " + name() + " fixing for " + fixingDate); return pastFixing; } } // QL_FAIL("YoYInflationIndex::fixing, should never get here"); }
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(); }
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_); } }