/**
         * The value of the annuity (or RPV01 - the premium leg per unit of coupon) at a specified valuation time.
         * The actual value of the leg is this multiplied by the notional and the fractional coupon (i.e. coupon
         * in basis points divided by 10,000).
         * <p>
         * If this is a spot starting CDS (effective protection start = 0) then cash flows from premium payments
         * and accrual-on-default are risky discounted to t=0 ('today'), then rolled forward (risk-free) to the
         * valuation time; if the annuity is requested clean, the accrued premium (paid at the cash-settle time) is
         * rolled (again risk-free) to the valuation time; the absolute value of this amount is subtracted from the
         * other cash flows to give the clean annuity.
         * <p>
         * If this is a forward starting CDS (effective protection start > 0), then the premium payments are again
         * risky discounted to t=0; if the annuity is requested clean, the accrued premium is risk-free discounted
         * to the effective protection start, then risky discounted to t=0 - this gives the t=0 value of the annuity
         * including the chance that a default occurs before protection starts.
         * <p>
         * If valuationTime > 0, the value of the annuity is rolled forward (risk-free) to that time.
         * To compute the Expected value of the annuity conditional on no default before the valuationTime,
         * one must divide this number by the survival probability to the valuationTime (for unit coupon).
         *
         * @param cds  the analytic description of a CDS traded at a certain time
         * @param yieldCurve  the yield (or discount) curve
         * @param creditCurve  the credit (or survival) curve
         * @param cleanOrDirty  the clean or dirty price
         * @param valuationTime  the valuation time
         * @return 10,000 times the RPV01 (on a notional of 1)
         */
        public double Annuity(CDS cds, PiecewiseconstantHazardRate hazard,
                              YieldTermStructure yt, CdsPriceType cleanOrDirt)
        {
            List <CashFlow> cf             = cds.FixLeg;
            DateTime        tradedate      = cds.tradedate;
            DateTime        settlementDate = tradedate.AddDays(cds.Cashsettlement);
            double          recoveryrate   = cds.Recovery;
            DateTime        Stepindate     = tradedate.AddDays(1);

            OMLib.Conventions.DayCount.Actual360 dc = new OMLib.Conventions.DayCount.Actual360();
            double   notional    = cds.Notional;
            double   coupon      = cds.PremiumRate;
            DateTime lastpayment = cds.formerpaymentdate;

            if (cf.Count() == 0)
            {
                return(0.0);
            }
            double ita      = (double)365 / 360;
            double totalNPV = 0.0;

            for (int i = 0; i < cf.Count; ++i)
            {
                totalNPV += cf[i].Amount * cf[i].DiscountFactor * cf[i].Survivalprobability;
            }
            double accrualpaidondefault = calculateSinglePeriodAccrualOnDefault(cds, yt, hazard);

            totalNPV += ita * coupon * accrualpaidondefault * notional / yt.discount(tradedate.AddDays(3));
            Calendar calendar = new UnitedStates();


            return(totalNPV / yt.discount(settlementDate));
        }
示例#2
0
        // par-rate approximation
        public static double parRate(YieldTermStructure yts, List <Date> dates, DayCounter resultDayCounter)
        {
            Utils.QL_REQUIRE(dates.Count >= 2, () => "at least two dates are required");
            double sum = 0.0;
            double dt;

            for (int i = 1; i < dates.Count; ++i)
            {
                dt = resultDayCounter.yearFraction(dates[i - 1], dates[i]);
                Utils.QL_REQUIRE(dt >= 0.0, () => "unsorted dates");
                sum += yts.discount(dates[i]) * dt;
            }
            double result = yts.discount(dates.First()) - yts.discount(dates.Last());

            return(result / sum);
        }
        public void calculate()
        {
            FxTermStructure    fxForwardTS = under_.fxForwardTS_;
            YieldTermStructure discountTS  = engine_.disCountTS_;

            double tradedForward = instInfo_.fxForwardRate_;
            double marketForward = fxForwardTS.forwardRate(instInfo_.maturityDate_);

            unitValue_ = (tradedForward - marketForward) * discountTS.discount(instInfo_.payDate_);

            this.Value_ = Convert.ToInt64(unitValue_) * instInfo_.Notional_;
        }
示例#4
0
        public void testVannaVolgaDoubleBarrierValues()
        {
            // Testing double-barrier FX options against Vanna/Volga values
            SavedSettings backup = new SavedSettings();

            DoubleBarrierFxOptionData[] values =
            {
                //                             BarrierType,                    barr.1, barr.2, rebate,         type,    strike,          s,         q,         r,  t, vol25Put,    volAtm,vol25Call,      vol,    result,   tol
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.13321, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.11638, 0.14413, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.22687, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.10088, 0.07456, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.31179, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08925, 0.02710, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.38843, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08463, 0.00569, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.46047, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08412, 0.00013, 1.0e-4),

                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put,  1.13321, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.11638, 0.00017, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put,  1.22687, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.10088, 0.00353, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put,  1.31179, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08925, 0.02221, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put,  1.38843, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08463, 0.06049, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put,  1.46047, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08412, 0.11103, 1.0e-4),

                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.06145, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.12511, 0.19981, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.19545, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.10890, 0.10389, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.32238, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09444, 0.03555, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.44298, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09197, 0.00634, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.56345, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09261, 0.00000, 1.0e-4),

                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put,  1.06145, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.12511, 0.00000, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put,  1.19545, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.10890, 0.00436, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put,  1.32238, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09444, 0.03173, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put,  1.44298, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09197, 0.09346, 1.0e-4),
                new DoubleBarrierFxOptionData(DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put,  1.56345, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09261, 0.17704, 1.0e-4),
            };

            DayCounter dc    = new Actual360();
            Date       today = new Date(05, Month.Mar, 2013);

            Settings.setEvaluationDate(today);

            SimpleQuote        spot      = new SimpleQuote(0.0);
            SimpleQuote        qRate     = new SimpleQuote(0.0);
            YieldTermStructure qTS       = Utilities.flatRate(today, qRate, dc);
            SimpleQuote        rRate     = new SimpleQuote(0.0);
            YieldTermStructure rTS       = Utilities.flatRate(today, rRate, dc);
            SimpleQuote        vol25Put  = new SimpleQuote(0.0);
            SimpleQuote        volAtm    = new SimpleQuote(0.0);
            SimpleQuote        vol25Call = new SimpleQuote(0.0);

            for (int i = 0; i < values.Length; i++)
            {
                for (int j = 0; j <= 1; j++)
                {
                    DoubleBarrier.Type barrierType = (DoubleBarrier.Type)j;
                    spot.setValue(values[i].s);
                    qRate.setValue(values[i].q);
                    rRate.setValue(values[i].r);
                    vol25Put.setValue(values[i].vol25Put);
                    volAtm.setValue(values[i].volAtm);
                    vol25Call.setValue(values[i].vol25Call);

                    StrikedTypePayoff payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);

                    Date     exDate   = today + (int)(values[i].t * 365 + 0.5);
                    Exercise exercise = new EuropeanExercise(exDate);

                    Handle <DeltaVolQuote> volAtmQuote = new Handle <DeltaVolQuote>(
                        new DeltaVolQuote(new Handle <Quote>(volAtm), DeltaVolQuote.DeltaType.Fwd, values[i].t,
                                          DeltaVolQuote.AtmType.AtmDeltaNeutral));

                    //always delta neutral atm
                    Handle <DeltaVolQuote> vol25PutQuote = new Handle <DeltaVolQuote>(new DeltaVolQuote(-0.25,
                                                                                                        new Handle <Quote>(vol25Put), values[i].t, DeltaVolQuote.DeltaType.Fwd));

                    Handle <DeltaVolQuote> vol25CallQuote = new Handle <DeltaVolQuote>(new DeltaVolQuote(0.25,
                                                                                                         new Handle <Quote>(vol25Call), values[i].t, DeltaVolQuote.DeltaType.Fwd));

                    DoubleBarrierOption doubleBarrierOption = new DoubleBarrierOption(barrierType,
                                                                                      values[i].barrier1, values[i].barrier2, values[i].rebate, payoff, exercise);

                    double bsVanillaPrice = Utils.blackFormula(values[i].type, values[i].strike,
                                                               spot.value() * qTS.discount(values[i].t) / rTS.discount(values[i].t),
                                                               values[i].v * Math.Sqrt(values[i].t), rTS.discount(values[i].t));

                    IPricingEngine vannaVolgaEngine;

                    vannaVolgaEngine = new VannaVolgaDoubleBarrierEngine(volAtmQuote, vol25PutQuote, vol25CallQuote,
                                                                         new Handle <Quote>(spot),
                                                                         new Handle <YieldTermStructure>(rTS),
                                                                         new Handle <YieldTermStructure>(qTS),
                                                                         (process, series) => new WulinYongDoubleBarrierEngine(process, series),
                                                                         true,
                                                                         bsVanillaPrice);
                    doubleBarrierOption.setPricingEngine(vannaVolgaEngine);

                    double expected = 0;
                    if (barrierType == DoubleBarrier.Type.KnockOut)
                    {
                        expected = values[i].result;
                    }
                    else if (barrierType == DoubleBarrier.Type.KnockIn)
                    {
                        expected = (bsVanillaPrice - values[i].result);
                    }

                    double calculated = doubleBarrierOption.NPV();
                    double error      = Math.Abs(calculated - expected);
                    if (error > values[i].tol)
                    {
                        REPORT_FAILURE_VANNAVOLGA("value", values[i].barrierType,
                                                  values[i].barrier1, values[i].barrier2,
                                                  values[i].rebate, payoff, exercise, values[i].s,
                                                  values[i].q, values[i].r, today, values[i].vol25Put,
                                                  values[i].volAtm, values[i].vol25Call, values[i].v,
                                                  expected, calculated, error, values[i].tol);
                    }

                    vannaVolgaEngine = new VannaVolgaDoubleBarrierEngine(volAtmQuote, vol25PutQuote, vol25CallQuote,
                                                                         new Handle <Quote>(spot),
                                                                         new Handle <YieldTermStructure>(rTS),
                                                                         new Handle <YieldTermStructure>(qTS),
                                                                         (process, series) => new AnalyticDoubleBarrierEngine(process, series),
                                                                         true,
                                                                         bsVanillaPrice);
                    doubleBarrierOption.setPricingEngine(vannaVolgaEngine);

                    calculated = doubleBarrierOption.NPV();
                    error      = Math.Abs(calculated - expected);
                    double maxtol = 5.0e-3; // different engines have somewhat different results
                    if (error > maxtol)
                    {
                        REPORT_FAILURE_VANNAVOLGA("value", values[i].barrierType,
                                                  values[i].barrier1, values[i].barrier2,
                                                  values[i].rebate, payoff, exercise, values[i].s,
                                                  values[i].q, values[i].r, today, values[i].vol25Put,
                                                  values[i].volAtm, values[i].vol25Call, values[i].v,
                                                  expected, calculated, error, values[i].tol);
                    }
                }
            }
        }
示例#5
0
        public void testZeroTermStructure()
        {
            // Testing zero inflation term structure...

            SavedSettings backup = new SavedSettings();

            // try the Zero UK
            Calendar calendar         = new UnitedKingdom();
            BusinessDayConvention bdc = BusinessDayConvention.ModifiedFollowing;
            Date evaluationDate       = new Date(13, Month.August, 2007);

            evaluationDate = calendar.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, 206.1, -999.0 };

            RelinkableHandle <ZeroInflationTermStructure> hz = new RelinkableHandle <ZeroInflationTermStructure>();
            bool  interp  = false;
            UKRPI iiUKRPI = new UKRPI(interp, hz);

            for (int i = 0; i < rpiSchedule.Count; i++)
            {
                iiUKRPI.addFixing(rpiSchedule[i], fixData[i]);
            }


            ZeroInflationIndex ii        = iiUKRPI as ZeroInflationIndex;
            YieldTermStructure nominalTS = nominalTermStructure();


            // now build the zero inflation curve

            Datum[] zcData =
            {
                new Datum(new Date(13, Month.August, 2008),  2.93),
                new Datum(new Date(13, Month.August, 2009),  2.95),
                new Datum(new Date(13, Month.August, 2010), 2.965),
                new Datum(new Date(15, Month.August, 2011),  2.98),
                new Datum(new Date(13, Month.August, 2012),   3.0),
                new Datum(new Date(13, Month.August, 2014),  3.06),
                new Datum(new Date(13, Month.August, 2017), 3.175),
                new Datum(new Date(13, Month.August, 2019), 3.243),
                new Datum(new Date(15, Month.August, 2022), 3.293),
                new Datum(new Date(14, Month.August, 2027), 3.338),
                new Datum(new Date(13, Month.August, 2032), 3.348),
                new Datum(new Date(15, Month.August, 2037), 3.348),
                new Datum(new Date(13, Month.August, 2047), 3.308),
                new Datum(new Date(13, Month.August, 2057), 3.228)
            };


            Period     observationLag = new Period(2, TimeUnit.Months);
            DayCounter dc             = new Thirty360();
            Frequency  frequency      = Frequency.Monthly;
            List <BootstrapHelper <ZeroInflationTermStructure> > helpers =
                makeHelpers(zcData, zcData.Length, ii,
                            observationLag,
                            calendar, bdc, dc);

            double baseZeroRate = zcData[0].rate / 100.0;
            PiecewiseZeroInflationCurve <Linear> pZITS = new PiecewiseZeroInflationCurve <Linear>(
                evaluationDate, calendar, dc, observationLag,
                frequency, ii.interpolated(), baseZeroRate,
                new Handle <YieldTermStructure>(nominalTS), helpers);

            pZITS.recalculate();

            // first check that the zero rates on the curve match the data
            // and that the helpers give the correct impled rates
            const double eps = 0.00000001;
            bool         forceLinearInterpolation = false;

            for (int i = 0; i < zcData.Length; i++)
            {
                Assert.IsTrue(Math.Abs(zcData[i].rate / 100.0
                                       - pZITS.zeroRate(zcData[i].date, observationLag, forceLinearInterpolation)) < eps,
                              "ZITS zeroRate != instrument "
                              + pZITS.zeroRate(zcData[i].date, observationLag, forceLinearInterpolation)
                              + " vs " + zcData[i].rate / 100.0
                              + " interpolation: " + ii.interpolated()
                              + " forceLinearInterpolation " + forceLinearInterpolation);

                Assert.IsTrue(Math.Abs(helpers[i].impliedQuote()
                                       - zcData[i].rate / 100.0) < eps,
                              "ZITS implied quote != instrument "
                              + helpers[i].impliedQuote()
                              + " vs " + zcData[i].rate / 100.0);
            }

            // now test the forecasting capability of the index.
            hz.linkTo(pZITS);
            from = hz.link.baseDate();
            to   = hz.link.maxDate() - new Period(1, TimeUnit.Months);             // a bit of margin for adjustments
            Schedule testIndex = new MakeSchedule().from(from).to(to)
                                 .withTenor(new Period(1, TimeUnit.Months))
                                 .withCalendar(new UnitedKingdom())
                                 .withConvention(BusinessDayConvention.ModifiedFollowing).value();

            // we are testing UKRPI which is not interpolated
            Date   bd = hz.link.baseDate();
            double bf = ii.fixing(bd);

            for (int i = 0; i < testIndex.Count; i++)
            {
                Date   d = testIndex[i];
                double z = hz.link.zeroRate(d, new Period(0, TimeUnit.Days));
                double t = hz.link.dayCounter().yearFraction(bd, d);
                if (!ii.interpolated())                   // because fixing constant over period
                {
                    t = hz.link.dayCounter().yearFraction(bd,
                                                          Utils.inflationPeriod(d, ii.frequency()).Key);
                }
                double calc = bf * Math.Pow(1 + z, t);
                if (t <= 0)
                {
                    calc = ii.fixing(d, false);                       // still historical
                }
                if (Math.Abs(calc - ii.fixing(d, true)) / 10000.0 > eps)
                {
                    Assert.Fail("ZC index does not forecast correctly for date " + d
                                + " from base date " + bd
                                + " with fixing " + bf
                                + ", correct:  " + calc
                                + ", fix: " + ii.fixing(d, true)
                                + ", t " + t);
                }
            }

            //===========================================================================================
            // Test zero-inflation-indexed (i.e. cpi ratio) cashflow
            // just ordinary indexed cashflow with a zero inflation index

            Date  baseDate = new Date(1, Month.January, 2006);
            Date  fixDate  = new Date(1, Month.August, 2014);
            Date  payDate  = new UnitedKingdom().adjust(fixDate + new Period(3, TimeUnit.Months), BusinessDayConvention.ModifiedFollowing);
            Index ind      = ii as Index;

            Utils.QL_REQUIRE(ind != null, () => "dynamic_pointer_cast to Index from InflationIndex failed");

            double          notional          = 1000000.0;//1m
            IndexedCashFlow iicf              = new IndexedCashFlow(notional, ind, baseDate, fixDate, payDate);
            double          correctIndexed    = ii.fixing(iicf.fixingDate()) / ii.fixing(iicf.baseDate());
            double          calculatedIndexed = iicf.amount() / iicf.notional();

            Assert.IsTrue(Math.Abs(correctIndexed - calculatedIndexed) < eps,
                          "IndexedCashFlow indexing wrong: " + calculatedIndexed + " vs correct = "
                          + correctIndexed);

            //===========================================================================================
            // Test zero coupon swap

            // first make one ...

            ZeroInflationIndex zii = ii as ZeroInflationIndex;

            Utils.QL_REQUIRE(zii != null, () => "dynamic_pointer_cast to ZeroInflationIndex from UKRPI failed");
            ZeroCouponInflationSwap nzcis =
                new ZeroCouponInflationSwap(ZeroCouponInflationSwap.Type.Payer,
                                            1000000.0,
                                            evaluationDate,
                                            zcData[6].date,                                                                 // end date = maturity
                                            calendar, bdc, dc, zcData[6].rate / 100.0,                                      // fixed rate
                                            zii, observationLag);

            // N.B. no coupon pricer because it is not a coupon, effect of inflation curve via
            //      inflation curve attached to the inflation index.
            Handle <YieldTermStructure> hTS = new Handle <YieldTermStructure>(nominalTS);
            IPricingEngine sppe             = new DiscountingSwapEngine(hTS);

            nzcis.setPricingEngine(sppe);

            // ... and price it, should be zero
            Assert.IsTrue(Math.Abs(nzcis.NPV()) < 0.00001, "ZCIS does not reprice to zero "
                          + nzcis.NPV()
                          + evaluationDate + " to " + zcData[6].date + " becoming " + nzcis.maturityDate()
                          + " rate " + zcData[6].rate
                          + " fixed leg " + nzcis.legNPV(0)
                          + " indexed-predicted inflated leg " + nzcis.legNPV(1)
                          + " discount " + nominalTS.discount(nzcis.maturityDate()));


            //===========================================================================================
            // Test multiplicative seasonality in price
            //

            //Seasonality factors NOT normalized
            //and UKRPI is not interpolated
            Date          trueBaseDate         = Utils.inflationPeriod(hz.link.baseDate(), ii.frequency()).Value;
            Date          seasonallityBaseDate = new Date(31, Month.January, trueBaseDate.year());
            List <double> seasonalityFactors   = new List <double>(12);

            seasonalityFactors.Add(1.003245);
            seasonalityFactors.Add(1.000000);
            seasonalityFactors.Add(0.999715);
            seasonalityFactors.Add(1.000495);
            seasonalityFactors.Add(1.000929);
            seasonalityFactors.Add(0.998687);
            seasonalityFactors.Add(0.995949);
            seasonalityFactors.Add(0.994682);
            seasonalityFactors.Add(0.995949);
            seasonalityFactors.Add(1.000519);
            seasonalityFactors.Add(1.003705);
            seasonalityFactors.Add(1.004186);

            //Creating two different seasonality objects
            //
            MultiplicativePriceSeasonality seasonality_1        = new MultiplicativePriceSeasonality();
            InitializedList <double>       seasonalityFactors_1 = new InitializedList <double>(12, 1.0);

            seasonality_1.set(seasonallityBaseDate, Frequency.Monthly, seasonalityFactors_1);

            MultiplicativePriceSeasonality seasonality_real =
                new MultiplicativePriceSeasonality(seasonallityBaseDate, Frequency.Monthly, seasonalityFactors);

            //Testing seasonality correction when seasonality factors are = 1
            //
            double[] fixing =
            {
                ii.fixing(new Date(14, Month.January,   2013), true),
                ii.fixing(new Date(14, Month.February,  2013), true),
                ii.fixing(new Date(14, Month.March,     2013), true),
                ii.fixing(new Date(14, Month.April,     2013), true),
                ii.fixing(new Date(14, Month.May,       2013), true),
                ii.fixing(new Date(14, Month.June,      2013), true),
                ii.fixing(new Date(14, Month.July,      2013), true),
                ii.fixing(new Date(14, Month.August,    2013), true),
                ii.fixing(new Date(14, Month.September, 2013), true),
                ii.fixing(new Date(14, Month.October,   2013), true),
                ii.fixing(new Date(14, Month.November,  2013), true),
                ii.fixing(new Date(14, Month.December,  2013), true)
            };

            hz.link.setSeasonality(seasonality_1);
            Utils.QL_REQUIRE(hz.link.hasSeasonality(), () => "[44] incorrectly believes NO seasonality correction");

            double[] seasonalityFixing_1 =
            {
                ii.fixing(new Date(14, Month.January,   2013), true),
                ii.fixing(new Date(14, Month.February,  2013), true),
                ii.fixing(new Date(14, Month.March,     2013), true),
                ii.fixing(new Date(14, Month.April,     2013), true),
                ii.fixing(new Date(14, Month.May,       2013), true),
                ii.fixing(new Date(14, Month.June,      2013), true),
                ii.fixing(new Date(14, Month.July,      2013), true),
                ii.fixing(new Date(14, Month.August,    2013), true),
                ii.fixing(new Date(14, Month.September, 2013), true),
                ii.fixing(new Date(14, Month.October,   2013), true),
                ii.fixing(new Date(14, Month.November,  2013), true),
                ii.fixing(new Date(14, Month.December,  2013), true)
            };

            for (int i = 0; i < 12; i++)
            {
                if (Math.Abs(fixing[i] - seasonalityFixing_1[i]) > eps)
                {
                    Assert.Fail("Seasonality doesn't work correctly when seasonality factors are set = 1");
                }
            }

            //Testing seasonality correction when seasonality factors are different from 1
            //
            //0.998687 is the seasonality factor corresponding to June (the base CPI curve month)
            //
            double[] expectedFixing =
            {
                ii.fixing(new Date(14, Month.January,   2013), true) * 1.003245 / 0.998687,
                ii.fixing(new Date(14, Month.February,  2013), true) * 1.000000 / 0.998687,
                ii.fixing(new Date(14, Month.March,     2013), true) * 0.999715 / 0.998687,
                ii.fixing(new Date(14, Month.April,     2013), true) * 1.000495 / 0.998687,
                ii.fixing(new Date(14, Month.May,       2013), true) * 1.000929 / 0.998687,
                ii.fixing(new Date(14, Month.June,      2013), true) * 0.998687 / 0.998687,
                ii.fixing(new Date(14, Month.July,      2013), true) * 0.995949 / 0.998687,
                ii.fixing(new Date(14, Month.August,    2013), true) * 0.994682 / 0.998687,
                ii.fixing(new Date(14, Month.September, 2013), true) * 0.995949 / 0.998687,
                ii.fixing(new Date(14, Month.October,   2013), true) * 1.000519 / 0.998687,
                ii.fixing(new Date(14, Month.November,  2013), true) * 1.003705 / 0.998687,
                ii.fixing(new Date(14, Month.December,  2013), true) * 1.004186 / 0.998687
            };

            hz.link.setSeasonality(seasonality_real);

            double[] seasonalityFixing_real =
            {
                ii.fixing(new Date(14, Month.January,   2013), true),
                ii.fixing(new Date(14, Month.February,  2013), true),
                ii.fixing(new Date(14, Month.March,     2013), true),
                ii.fixing(new Date(14, Month.April,     2013), true),
                ii.fixing(new Date(14, Month.May,       2013), true),
                ii.fixing(new Date(14, Month.June,      2013), true),
                ii.fixing(new Date(14, Month.July,      2013), true),
                ii.fixing(new Date(14, Month.August,    2013), true),
                ii.fixing(new Date(14, Month.September, 2013), true),
                ii.fixing(new Date(14, Month.October,   2013), true),
                ii.fixing(new Date(14, Month.November,  2013), true),
                ii.fixing(new Date(14, Month.December,  2013), true)
            };

            for (int i = 0; i < 12; i++)
            {
                if (Math.Abs(expectedFixing[i] - seasonalityFixing_real[i]) > 0.01)
                {
                    Assert.Fail("Seasonality doesn't work correctly when considering seasonality factors != 1 "
                                + expectedFixing[i] + " vs " + seasonalityFixing_real[i]);
                }
            }


            //Testing Unset function
            //
            Utils.QL_REQUIRE(hz.link.hasSeasonality(), () => "[4] incorrectly believes NO seasonality correction");
            hz.link.setSeasonality();
            Utils.QL_REQUIRE(!hz.link.hasSeasonality(), () => "[5] incorrectly believes HAS seasonality correction");

            double[] seasonalityFixing_unset =
            {
                ii.fixing(new Date(14, Month.January,   2013), true),
                ii.fixing(new Date(14, Month.February,  2013), true),
                ii.fixing(new Date(14, Month.March,     2013), true),
                ii.fixing(new Date(14, Month.April,     2013), true),
                ii.fixing(new Date(14, Month.May,       2013), true),
                ii.fixing(new Date(14, Month.June,      2013), true),
                ii.fixing(new Date(14, Month.July,      2013), true),
                ii.fixing(new Date(14, Month.August,    2013), true),
                ii.fixing(new Date(14, Month.September, 2013), true),
                ii.fixing(new Date(14, Month.October,   2013), true),
                ii.fixing(new Date(14, Month.November,  2013), true),
                ii.fixing(new Date(14, Month.December,  2013), true)
            };

            for (int i = 0; i < 12; i++)
            {
                if (Math.Abs(seasonalityFixing_unset[i] - seasonalityFixing_1[i]) > eps)
                {
                    Assert.Fail("UnsetSeasonality doesn't work correctly "
                                + seasonalityFixing_unset[i] + " vs " + seasonalityFixing_1[i]);
                }
            }


            //==============================================================================
            // now do an INTERPOLATED index, i.e. repeat everything on a fake version of
            // UKRPI (to save making another term structure)

            bool  interpYES  = true;
            UKRPI iiUKRPIyes = new UKRPI(interpYES, hz);

            for (int i = 0; i < fixData.Length; i++)
            {
                iiUKRPIyes.addFixing(rpiSchedule[i], fixData[i]);
            }

            ZeroInflationIndex iiyes = iiUKRPIyes as ZeroInflationIndex;

            // now build the zero inflation curve
            // same data, bigger lag or it will be a self-contradiction
            Period observationLagyes = new Period(3, TimeUnit.Months);
            List <BootstrapHelper <ZeroInflationTermStructure> > helpersyes =
                makeHelpers(zcData, zcData.Length,
                            iiyes, observationLagyes, calendar, bdc, dc);

            PiecewiseZeroInflationCurve <Linear> pZITSyes =
                new PiecewiseZeroInflationCurve <Linear>(
                    evaluationDate, calendar, dc, observationLagyes,
                    frequency, iiyes.interpolated(), baseZeroRate,
                    new Handle <YieldTermStructure>(nominalTS), helpersyes);

            pZITSyes.recalculate();

            // first check that the zero rates on the curve match the data
            // and that the helpers give the correct impled rates
            forceLinearInterpolation = false;               // still
            for (int i = 0; i < zcData.Length; i++)
            {
                Assert.IsTrue(Math.Abs(zcData[i].rate / 100.0
                                       - pZITSyes.zeroRate(zcData[i].date, observationLagyes, forceLinearInterpolation)) < eps,
                              "ZITS INTERPOLATED zeroRate != instrument "
                              + pZITSyes.zeroRate(zcData[i].date, observationLagyes, forceLinearInterpolation)
                              + " date " + zcData[i].date + " observationLagyes " + observationLagyes
                              + " vs " + zcData[i].rate / 100.0
                              + " interpolation: " + iiyes.interpolated()
                              + " forceLinearInterpolation " + forceLinearInterpolation);
                Assert.IsTrue(Math.Abs(helpersyes[i].impliedQuote()
                                       - zcData[i].rate / 100.0) < eps,
                              "ZITS INTERPOLATED implied quote != instrument "
                              + helpersyes[i].impliedQuote()
                              + " vs " + zcData[i].rate / 100.0);
            }


            //======================================================================================
            // now test the forecasting capability of the index.
            hz.linkTo(pZITSyes);
            from      = hz.link.baseDate() + new Period(1, TimeUnit.Months);       // to avoid historical linear bit for rest of base month
            to        = hz.link.maxDate() - new Period(1, TimeUnit.Months);        // a bit of margin for adjustments
            testIndex = new MakeSchedule().from(from).to(to)
                        .withTenor(new Period(1, TimeUnit.Months))
                        .withCalendar(new UnitedKingdom())
                        .withConvention(BusinessDayConvention.ModifiedFollowing).value();

            // we are testing UKRPI which is FAKE interpolated for testing here
            bd = hz.link.baseDate();
            bf = iiyes.fixing(bd);
            for (int i = 0; i < testIndex.Count; i++)
            {
                Date   d    = testIndex[i];
                double z    = hz.link.zeroRate(d, new Period(0, TimeUnit.Days));
                double t    = hz.link.dayCounter().yearFraction(bd, d);
                double calc = bf * Math.Pow(1 + z, t);
                if (t <= 0)
                {
                    calc = iiyes.fixing(d);                             // still historical
                }
                if (Math.Abs(calc - iiyes.fixing(d)) > eps)
                {
                    Assert.Fail("ZC INTERPOLATED index does not forecast correctly for date " + d
                                + " from base date " + bd
                                + " with fixing " + bf
                                + ", correct:  " + calc
                                + ", fix: " + iiyes.fixing(d)
                                + ", t " + t
                                + ", zero " + z);
                }
            }


            //===========================================================================================
            // Test zero coupon swap

            ZeroInflationIndex ziiyes = iiyes as ZeroInflationIndex;

            Utils.QL_REQUIRE(ziiyes != null, () => "dynamic_pointer_cast to ZeroInflationIndex from UKRPI-I failed");
            ZeroCouponInflationSwap nzcisyes = new ZeroCouponInflationSwap(ZeroCouponInflationSwap.Type.Payer,
                                                                           1000000.0,
                                                                           evaluationDate,
                                                                           zcData[6].date,                                                   // end date = maturity
                                                                           calendar, bdc, dc, zcData[6].rate / 100.0,                        // fixed rate
                                                                           ziiyes, observationLagyes);

            // N.B. no coupon pricer because it is not a coupon, effect of inflation curve via
            //      inflation curve attached to the inflation index.
            nzcisyes.setPricingEngine(sppe);

            // ... and price it, should be zero
            Assert.IsTrue(Math.Abs(nzcisyes.NPV()) < 0.00001, "ZCIS-I does not reprice to zero "
                          + nzcisyes.NPV()
                          + evaluationDate + " to " + zcData[6].date + " becoming " + nzcisyes.maturityDate()
                          + " rate " + zcData[6].rate
                          + " fixed leg " + nzcisyes.legNPV(0)
                          + " indexed-predicted inflated leg " + nzcisyes.legNPV(1)
                          + " discount " + nominalTS.discount(nzcisyes.maturityDate())
                          );

            // remove circular refernce
            hz.linkTo(new ZeroInflationTermStructure());
        }
        public double protectionLeg(CDS cds, YieldTermStructure yt, PiecewiseconstantHazardRate hazard,
                                    double valuationTime)
        {
            List <double> Jumps = yt.t;
            List <double> tenor = hazard.t;
            List <double> result = new List <double>();
            int           index = 0, indexj = 0, lastIndex = 0;

            while (index < Jumps.Count || indexj < tenor.Count)
            {
                if (lastIndex > 0)
                {
                    if (index >= Jumps.Count)
                    {
                        if (!DateTime.Equals(result.Last(), tenor[indexj]))
                        {
                            result.Add(tenor[indexj]);
                            lastIndex++;
                        }
                        indexj++;
                        continue;
                    }
                    if (indexj >= tenor.Count)
                    {
                        if (!DateTime.Equals(result.Last(), Jumps[index]))
                        {
                            result.Add(Jumps[index]);
                            lastIndex++;
                        }
                        index++;
                        continue;
                    }
                }
                double smallestVal = tenor.Last();

                // Choose the smaller of a or b
                if (Jumps[index] < tenor[indexj])
                {
                    smallestVal = Jumps[index++];
                }
                else
                {
                    smallestVal = tenor[indexj++];
                }

                // Don't insert duplicates
                if (lastIndex > 0)
                {
                    if (result.Last() != smallestVal)
                    {
                        result.Add(smallestVal);
                        lastIndex++;
                    }
                }
                else
                {
                    result.Add(smallestVal);
                    lastIndex++;
                }
            }
            DateTime tradedate      = cds.tradedate;
            DateTime settlementDate = tradedate.AddDays((int)valuationTime * 365);
            double   recoveryrate   = cds.Recovery;
            DateTime Stepindate     = tradedate.AddDays(1);

            OMLib.Conventions.DayCount.Actual360 dc = new OMLib.Conventions.DayCount.Actual360();
            CdsCoupon[] cf       = cds.getCoupons();
            double      notional = cds.Notional;

            DateTime      t0        = tradedate;
            double        T         = cf.Last().getEffEnd();
            List <double> JumpNodes = new List <double>();

            JumpNodes.Add(0);
            for (int j = 0; j < result.Count; j++)
            {
                if (result[j] < T)
                {
                    JumpNodes.Add(result[j]);
                }
            }
            JumpNodes.Add(T);
            double ht0 = hazard.getRT_(JumpNodes[0]);
            double rt0 = yt.getRT_(JumpNodes[0]);
            double b0  = Math.Exp(-ht0 - rt0); // risky discount factor

            double pv  = 0.0;
            double dPV = 0.0;

            for (int i = 1; i < JumpNodes.Count; ++i)
            {
                double ht1 = hazard.getRT_(JumpNodes[i]);
                double rt1 = yt.getRT_(JumpNodes[i]);
                double b1  = Math.Exp(-ht1 - rt1);

                double dht  = ht1 - ht0;
                double drt  = rt1 - rt0;
                double dhrt = dht + drt;

                // The formula has been modified from ISDA (but is equivalent) to avoid log(exp(x)) and explicitly
                // calculating the time step - it also handles the limit
                if (Math.Abs(dhrt) < 1e-5)
                {
                    dPV = dht * b0 * Maths.Epsilon.epsilon(-dhrt) / (-dhrt);
                }
                else
                {
                    dPV = (b0 - b1) * dht / dhrt;
                }
                pv += dPV;
                ht0 = ht1;
                rt0 = rt1;
                b0  = b1;
            }
            pv = pv * notional * (1 - recoveryrate);
            return(pv / yt.discount(settlementDate));
        }
        public static object eqInstDisplayIRSwap(
            [ExcelArgument(Description = "id of IR Swap ")] string tradeid,
            [ExcelArgument(Description = "id of discount curve ")] string discountId,
            [ExcelArgument(Description = "trigger ")] object trigger)
        {
            if (ExcelUtil.CallFromWizard())
            {
                return("");
            }

            string callerAddress = "";

            callerAddress = ExcelUtil.getActiveCellAddress();

            object[,] ret;
            try
            {
                Xl.Range rng = ExcelUtil.getActiveCellRange();


                if (!tradeid.Contains('@'))
                {
                    tradeid = "SWP@" + tradeid;
                }

                if (!discountId.Contains('@'))
                {
                    discountId = "CRV@" + discountId;
                }
                YieldTermStructure discountcurve = OHRepository.Instance.getObject <YieldTermStructure>(discountId);
                Date asofdate = Settings.instance().getEvaluationDate();

                GenericSwap inst = OHRepository.Instance.getObject <GenericSwap>(tradeid);

                int rows = Math.Max(inst.firstLegInfo().Count, inst.secondLegInfo().Count);
                ret = new object[rows, 20];     // 10 cols each leg
                for (int i = 0; i < rows; i++)
                {
                    for (int j = 0; j < 20; j++)
                    {
                        ret[i, j] = "";         // initialization. null will be posted as 0; so explicitly set it to ""
                    }
                }

                // first leg
                string[] s;
                DateTime startdate, enddate, paymentdate, resetdate;
                double   balance = 0, rate = 0, spread = 0, payment = 0, discount = 0, pv = 0;
                for (int i = 0; i < inst.firstLegInfo().Count; i++)
                {
                    s           = inst.firstLegInfo()[i].Split(',');
                    startdate   = EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[0])));
                    enddate     = EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[1])));
                    paymentdate = EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[2])));
                    resetdate   = (s[3] == "") ? DateTime.MinValue : EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[3])));
                    balance     = Convert.ToDouble(s[4]);
                    rate        = Convert.ToDouble(s[5]);
                    spread      = Convert.ToDouble(s[6]);
                    payment     = Convert.ToDouble(s[7]);

                    // today's cashflow is not included
                    if (EliteQuant.EQConverter.DateTimeToDate(paymentdate).serialNumber() <= asofdate.serialNumber())
                    {
                        discount = 0.0;
                    }
                    else
                    {
                        discount = discountcurve.discount(EliteQuant.EQConverter.DateTimeToDate(paymentdate));
                    }

                    pv = payment * discount;

                    // and return the matrix to vba
                    ret[i, 0] = (object)startdate;
                    ret[i, 1] = (object)enddate;
                    ret[i, 2] = (object)paymentdate;
                    ret[i, 3] = (s[3] == "") ? "":(object)resetdate;
                    ret[i, 4] = (object)(balance == 0 ? "" : (object)balance);
                    ret[i, 5] = (object)(rate == 0 ? "" : (object)rate);
                    ret[i, 6] = (object)(spread == 0 ? "" : (object)spread);
                    ret[i, 7] = (object)(payment == 0 ? "" : (object)payment);
                    ret[i, 8] = (object)(discount == 0 ? "" : (object)discount);
                    ret[i, 9] = (object)(pv == 0 ? "" : (object)pv);
                }
                for (int i = 0; i < inst.secondLegInfo().Count; i++)
                {
                    s           = inst.secondLegInfo()[i].Split(',');
                    startdate   = EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[0])));
                    enddate     = EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[1])));
                    paymentdate = EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[2])));
                    resetdate   = (s[3] == "") ? DateTime.MinValue : EliteQuant.EQConverter.DateToDateTime(new Date(Convert.ToInt32(s[3])));
                    balance     = Convert.ToDouble(s[4]);
                    rate        = Convert.ToDouble(s[5]);
                    spread      = Convert.ToDouble(s[6]);
                    payment     = Convert.ToDouble(s[7]);

                    // today's cashflow is not included
                    if (EliteQuant.EQConverter.DateTimeToDate(paymentdate).serialNumber() <= asofdate.serialNumber())
                    {
                        discount = 0.0;
                    }
                    else
                    {
                        discount = discountcurve.discount(EliteQuant.EQConverter.DateTimeToDate(paymentdate));
                    }

                    pv = payment * discount;

                    // and return the matrix to vba
                    ret[i, 10] = (object)startdate;
                    ret[i, 11] = (object)enddate;
                    ret[i, 12] = (object)paymentdate;
                    ret[i, 13] = (s[3] == "") ? "" : (object)resetdate;
                    ret[i, 14] = (object)(balance == 0 ? "" : (object)balance);
                    ret[i, 15] = (object)(rate == 0 ? "" : (object)rate);
                    ret[i, 16] = (object)(spread == 0 ? "" : (object)spread);
                    ret[i, 17] = (object)(payment == 0 ? "" : (object)payment);
                    ret[i, 18] = (object)(discount == 0 ? "" : (object)discount);
                    ret[i, 19] = (object)(pv == 0 ? "" : (object)pv);
                }

                return(ret);
            }
            catch (Exception e)
            {
                ExcelUtil.logError(callerAddress, System.Reflection.MethodInfo.GetCurrentMethod().Name.ToString(), e.Message);
                return("#EQ_ERR!");
            }
        }
示例#8
0
        private void BuildDiscountCurve()
        {
            Utils.QL_REQUIRE(_curveSegments.Count <= 1, () => "More than one zero curve segment not supported yet.");
            Utils.QL_REQUIRE(_curveSegments[0].CurveSegmentType() == YieldCurveSegment.Type.Zero, () => "The curve segment is not of type Zero.");

            // Fill a vector of zero quotes.
            List <ZeroQuote>        zeroQuotes       = new List <ZeroQuote>();
            DirectYieldCurveSegment zeroCurveSegment = _curveSegments[0] as DirectYieldCurveSegment;

            List <string> zeroQuoteIDs = zeroCurveSegment.Quotes();

            for (int i = 0; i < zeroQuoteIDs.Count; ++i)
            {
                MarketDatum marketQuote = _loader.Get(zeroQuoteIDs[i], _asofDate);
                if (marketQuote != null)
                {
                    Utils.QL_REQUIRE(marketQuote.GetInstrumentType() == MarketDatum.InstrumentType.ZERO, () => "Market quote not of type zero.");
                    ZeroQuote zeroQuote = marketQuote as ZeroQuote;
                    zeroQuotes.Add(zeroQuote);
                }
                else
                {
                    Utils.QL_FAIL("Could not find quote for ID " + zeroQuoteIDs[i] + " with as of date " + _asofDate + ".");
                }
            }

            // Create the (date, zero) pairs.
            Dictionary <Date, double> data = new Dictionary <Date, double>();
            Convention convention          = _conventions.Get(_curveSegments[0].ConventionsID());

            Utils.QL_REQUIRE(convention != null, () => "No conventions found with ID: " + _curveSegments[0].ConventionsID());
            Utils.QL_REQUIRE(convention.ConventionType() == Convention.Type.Zero, () => "Conventions ID does not give zero rate conventions.");
            ZeroRateConvention zeroConvention  = convention as ZeroRateConvention;
            DayCounter         quoteDayCounter = zeroConvention.DayCounter();

            for (int i = 0; i < zeroQuotes.Count; ++i)
            {
                Utils.QL_REQUIRE(quoteDayCounter == zeroQuotes[i].DayCounter(), () => "The day counter should be the same between the conventions and the quote.");

                if (!zeroQuotes[i].TenorBased())
                {
                    data[zeroQuotes[i].Date()] = zeroQuotes[i].Quote().link.value();
                }
                else
                {
                    Utils.QL_REQUIRE(zeroConvention.TenorBased(), () => "Using tenor based zero rates without tenor based zero rate conventions.");


                    Date zeroDate = _asofDate;
                    if (zeroConvention.SpotLag() > 0)
                    {
                    }

                    zeroDate       = zeroConvention.SpotCalendar().advance(zeroDate, new Period(zeroConvention.SpotLag(), TimeUnit.Days));
                    zeroDate       = zeroConvention.TenorCalendar().advance(zeroDate, zeroQuotes[i].Tenor(), zeroConvention.RollConvention(), zeroConvention.Eom());
                    data[zeroDate] = zeroQuotes[i].Quote().link.value();
                }
            }

            Utils.QL_REQUIRE(data.Count > 0, () => "No market data found for curve spec " + _curveSpec.Name() + " with as of date " + _asofDate);


            // \todo review - more flexible (flat vs. linear extrap)?
            if (data.Keys.First() > _asofDate)
            {
                double rate = data.Values.First();
                data[_asofDate] = rate;
                //LOG("Insert zero curve point at time zero for " + curveSpec_.name() + ": "+ "date " + _asofDate + ", "+"zero " + data[_asofDate]);
            }

            Utils.QL_REQUIRE(data.Count > 1, () => "The single zero rate quote provided should be associated with a date greater than as of date.");

            // First build temporary curves
            List <Date>   dates     = new List <Date>();
            List <double> zeroes    = new List <double>();
            List <double> discounts = new List <double>();

            dates.Add(data.Keys.First());
            zeroes.Add(data.Values.First());
            discounts.Add(1.0);

            Compounding zeroCompounding     = zeroConvention.Compounding();
            Frequency   zeroCompoundingFreq = zeroConvention.CompoundingFrequency();
            Dictionary <Date, double> it;

            foreach (KeyValuePair <Date, double> kvp in data)
            {
                Date   d = kvp.Key;
                double r = kvp.Value;

                dates.Add(d);
                InterestRate tempRate = new InterestRate(r, quoteDayCounter, zeroCompounding, zeroCompoundingFreq);
                double       t        = quoteDayCounter.yearFraction(_asofDate, d);
                /* Convert zero rate to continuously compounded if necessary */
                if (zeroCompounding == Compounding.Continuous)
                {
                    zeroes.Add(r);
                }
                else
                {
                    zeroes.Add(tempRate.equivalentRate(Compounding.Continuous, Frequency.Annual, t).value());
                }
                discounts.Add(tempRate.discountFactor(t));
                //LOG("Add zero curve point for " + curveSpec_.name() + ": " + dates.Last() + " " + zeroes.Last() + " / " + discounts.Last());
            }

            Utils.QL_REQUIRE(dates.Count == zeroes.Count, () => "Date and zero vectors differ in size.");
            Utils.QL_REQUIRE(dates.Count == discounts.Count, () => "Date and discount vectors differ in size.");

            // Now build curve with requested conventions
            if (_interpolationVariable == YieldCurve.InterpolationVariable.Zero)
            {
                YieldTermStructure tempCurve = Zerocurve(dates, zeroes, quoteDayCounter);
                zeroes.Clear();
                for (int i = 0; i < dates.Count; ++i)
                {
                    double zero = tempCurve.zeroRate(dates[i], _zeroDayCounter, Compounding.Continuous).value();
                    zeroes.Add(zero);
                }

                _p = Zerocurve(dates, zeroes, _zeroDayCounter);
            }
            else if (_interpolationVariable == YieldCurve.InterpolationVariable.Discount)
            {
                YieldTermStructure tempCurve = Discountcurve(dates, discounts, quoteDayCounter);
                discounts.Clear();
                for (int i = 0; i < dates.Count; ++i)
                {
                    double discount = tempCurve.discount(dates[i]);
                    discounts.Add(discount);
                }
                _p = Discountcurve(dates, discounts, _zeroDayCounter);
            }
            else
            {
                Utils.QL_FAIL("Unknown yield curve interpolation variable.");
            }
        }