Example #1
0
        public void testLambdaBootstrapping()
        {
            //"Testing caplet LMM lambda bootstrapping..."

            //SavedSettings backup;

            double tolerance = 1e-10;

            double[] lambdaExpected = { 14.3010297550, 19.3821411939, 15.9816590141,
                                        15.9953118303, 14.0570815635, 13.5687599894,
                                        12.7477197786, 13.7056638165, 11.6191989567 };

            LiborForwardModelProcess process = makeProcess();
            Matrix covar = process.covariance(0.0, null, 1.0);

            for (int i = 0; i < 9; ++i)
            {
                double calculated = Math.Sqrt(covar[i + 1, i + 1]);
                double expected   = lambdaExpected[i] / 100;

                if (Math.Abs(calculated - expected) > tolerance)
                {
                    Assert.Fail("Failed to reproduce expected lambda values"
                                + "\n    calculated: " + calculated
                                + "\n    expected:   " + expected);
                }
            }

            LfmCovarianceParameterization param = process.covarParam();

            List <double> tmp  = process.fixingTimes();
            TimeGrid      grid = new TimeGrid(tmp.Last(), 14);

            for (int t = 0; t < grid.size(); ++t)
            {
                //verifier la presence du null
                Matrix diff = param.integratedCovariance(grid[t], null)
                              - param.integratedCovariance(grid[t], null);

                for (int i = 0; i < diff.rows(); ++i)
                {
                    for (int j = 0; j < diff.columns(); ++j)
                    {
                        if (Math.Abs(diff[i, j]) > tolerance)
                        {
                            Assert.Fail("Failed to reproduce integrated covariance"
                                        + "\n    calculated: " + diff[i, j]
                                        + "\n    expected:   " + 0);
                        }
                    }
                }
            }
        }
Example #2
0
        LiborForwardModelProcess makeProcess(Matrix volaComp)
        {
            int factors = (volaComp.empty() ? 1 : volaComp.columns());

            IborIndex index = makeIndex();
            LiborForwardModelProcess process = new LiborForwardModelProcess(len, index, null);

            LfmCovarianceParameterization fct = new LfmHullWhiteParameterization(
                process,
                makeCapVolCurve(Settings.evaluationDate()),
                volaComp * Matrix.transpose(volaComp), factors);

            process.setCovarParam(fct);

            return(process);
        }
Example #3
0
        public void testInitialisation()
        {
            //"Testing caplet LMM process initialisation..."

            //SavedSettings backup;

            DayCounter dayCounter = new Actual360();
            RelinkableHandle <YieldTermStructure> termStructure = new RelinkableHandle <YieldTermStructure>();;

            termStructure.linkTo(Utilities.flatRate(Date.Today, 0.04, dayCounter));

            IborIndex index = new Euribor6M(termStructure);
            OptionletVolatilityStructure capletVol = new ConstantOptionletVolatility(
                termStructure.currentLink().referenceDate(),
                termStructure.currentLink().calendar(),
                BusinessDayConvention.Following,
                0.2,
                termStructure.currentLink().dayCounter());

            Calendar calendar = index.fixingCalendar();

            for (int daysOffset = 0; daysOffset < 1825 /* 5 year*/; daysOffset += 8)
            {
                Date todaysDate = calendar.adjust(Date.Today + daysOffset);
                Settings.setEvaluationDate(todaysDate);
                Date settlementDate =
                    calendar.advance(todaysDate, index.fixingDays(), TimeUnit.Days);

                termStructure.linkTo(Utilities.flatRate(settlementDate, 0.04, dayCounter));

                LiborForwardModelProcess process = new LiborForwardModelProcess(60, index);

                List <double> fixings = process.fixingTimes();
                for (int i = 1; i < fixings.Count - 1; ++i)
                {
                    int ileft  = process.nextIndexReset(fixings[i] - 0.000001);
                    int iright = process.nextIndexReset(fixings[i] + 0.000001);
                    int ii     = process.nextIndexReset(fixings[i]);

                    if ((ileft != i) || (iright != i + 1) || (ii != i + 1))
                    {
                        Assert.Fail("Failed to next index resets");
                    }
                }
            }
        }
        public void testCapletPricing()
        {
            //"Testing caplet pricing...";

            //SavedSettings backup;

            const int size = 10;

            #if QL_USE_INDEXED_COUPON
            const double tolerance = 1e-5;
            #else
            const double tolerance = 1e-12;
            #endif

            IborIndex index = makeIndex();
            LiborForwardModelProcess process = new LiborForwardModelProcess(size, index);

            // set-up pricing engine
            OptionletVolatilityStructure capVolCurve = makeCapVolCurve(Settings.evaluationDate());

            Vector variances = new LfmHullWhiteParameterization(process, capVolCurve).covariance(0.0, null).diagonal();

            LmVolatilityModel volaModel = new LmFixedVolatilityModel(Vector.Sqrt(variances), process.fixingTimes());

            LmCorrelationModel corrModel = new LmExponentialCorrelationModel(size, 0.3);

            IAffineModel model = (IAffineModel)(new LiborForwardModel(process, volaModel, corrModel));

            Handle <YieldTermStructure> termStructure = process.index().forwardingTermStructure();

            AnalyticCapFloorEngine engine1 = new AnalyticCapFloorEngine(model, termStructure);

            Cap cap1 = new Cap(process.cashFlows(),
                               new InitializedList <double>(size, 0.04));
            cap1.setPricingEngine(engine1);

            const double expected   = 0.015853935178;
            double       calculated = cap1.NPV();

            if (Math.Abs(expected - calculated) > tolerance)
            {
                Assert.Fail("Failed to reproduce npv"
                            + "\n    calculated: " + calculated
                            + "\n    expected:   " + expected);
            }
        }
Example #5
0
        CapletVarianceCurve makeCapVolCurve(Date todaysDate)
        {
            double[] vols = { 14.40, 17.15, 16.81, 16.64, 16.17,
                              15.78, 15.40, 15.21, 14.86, 14.54 };

            List <Date>              dates      = new List <Date>();
            List <double>            capletVols = new List <double>();
            LiborForwardModelProcess process    = new LiborForwardModelProcess(len + 1, makeIndex(), null);

            for (int i = 0; i < len; ++i)
            {
                capletVols.Add(vols[i] / 100);
                dates.Add(process.fixingDates()[i + 1]);
            }

            return(new CapletVarianceCurve(todaysDate, dates,
                                           capletVols, new ActualActual()));
        }
Example #6
0
        OptionletVolatilityStructure makeCapVolCurve(Date todaysDate)
        {
            double[] vols = { 14.40, 17.15, 16.81, 16.64, 16.17,
                              15.78, 15.40, 15.21, 14.86 };

            List <Date>              dates      = new List <Date>();
            List <double>            capletVols = new List <double>();
            LiborForwardModelProcess process    =
                new LiborForwardModelProcess(10, makeIndex());

            for (int i = 0; i < 9; ++i)
            {
                capletVols.Add(vols[i] / 100);
                dates.Add(process.fixingDates()[i + 1]);
            }

            return(new CapletVarianceCurve(todaysDate, dates,
                                           capletVols, new Actual360()));
        }
Example #7
0
        public void testSwaptionPricing()
        {
            // Testing forward swap and swaption pricing
            const int size  = 10;
            const int steps = 8 * size;

#if QL_USE_INDEXED_COUPON
            const double tolerance = 1e-6;
#else
            const double tolerance = 1e-12;
#endif

            List <Date>   dates = new List <Date>();
            List <double> rates = new List <double>();
            dates.Add(new Date(4, 9, 2005));
            dates.Add(new Date(4, 9, 2011));
            rates.Add(0.04);
            rates.Add(0.08);

            IborIndex index = makeIndex(dates, rates);

            LiborForwardModelProcess process = new LiborForwardModelProcess(size, index);

            LmCorrelationModel corrModel = new LmExponentialCorrelationModel(size, 0.5);

            LmVolatilityModel volaModel = new LmLinearExponentialVolatilityModel(process.fixingTimes(),
                                                                                 0.291, 1.483, 0.116, 0.00001);

            // set-up pricing engine
            process.setCovarParam((LfmCovarianceParameterization)
                                  new LfmCovarianceProxy(volaModel, corrModel));

            // set-up a small Monte-Carlo simulation to price swations
            List <double> tmp = process.fixingTimes();

            TimeGrid grid = new TimeGrid(tmp, tmp.Count, steps);

            List <int> location = new List <int>();
            for (int i = 0; i < tmp.Count; ++i)
            {
                location.Add(grid.index(tmp[i]));
            }

            ulong     seed     = 42;
            const int nrTrails = 5000;
            LowDiscrepancy.icInstance = new InverseCumulativeNormal();

            IRNG rsg = (InverseCumulativeRsg <RandomSequenceGenerator <MersenneTwisterUniformRng>
                                              , InverseCumulativeNormal>)
                       new PseudoRandom().make_sequence_generator(process.factors() * (grid.size() - 1), seed);



            MultiPathGenerator <IRNG> generator = new MultiPathGenerator <IRNG>(process,
                                                                                grid,
                                                                                rsg, false);

            LiborForwardModel liborModel = new LiborForwardModel(process, volaModel, corrModel);

            Calendar              calendar   = index.fixingCalendar();
            DayCounter            dayCounter = index.forwardingTermStructure().link.dayCounter();
            BusinessDayConvention convention = index.businessDayConvention();

            Date settlement = index.forwardingTermStructure().link.referenceDate();

            SwaptionVolatilityMatrix m = liborModel.getSwaptionVolatilityMatrix();

            for (int i = 1; i < size; ++i)
            {
                for (int j = 1; j <= size - i; ++j)
                {
                    Date fwdStart    = settlement + new Period(6 * i, TimeUnit.Months);
                    Date fwdMaturity = fwdStart + new Period(6 * j, TimeUnit.Months);

                    Schedule schedule = new Schedule(fwdStart, fwdMaturity, index.tenor(), calendar,
                                                     convention, convention, DateGeneration.Rule.Forward, false);

                    double      swapRate    = 0.0404;
                    VanillaSwap forwardSwap = new VanillaSwap(VanillaSwap.Type.Receiver, 1.0,
                                                              schedule, swapRate, dayCounter,
                                                              schedule, index, 0.0, index.dayCounter());
                    forwardSwap.setPricingEngine(new DiscountingSwapEngine(index.forwardingTermStructure()));

                    // check forward pricing first
                    double expected   = forwardSwap.fairRate();
                    double calculated = liborModel.S_0(i - 1, i + j - 1);

                    if (Math.Abs(expected - calculated) > tolerance)
                    {
                        QAssert.Fail("Failed to reproduce fair forward swap rate"
                                     + "\n    calculated: " + calculated
                                     + "\n    expected:   " + expected);
                    }

                    swapRate    = forwardSwap.fairRate();
                    forwardSwap =
                        new VanillaSwap(VanillaSwap.Type.Receiver, 1.0,
                                        schedule, swapRate, dayCounter,
                                        schedule, index, 0.0, index.dayCounter());
                    forwardSwap.setPricingEngine(new DiscountingSwapEngine(index.forwardingTermStructure()));

                    if (i == j && i <= size / 2)
                    {
                        IPricingEngine engine =
                            new LfmSwaptionEngine(liborModel, index.forwardingTermStructure());
                        Exercise exercise =
                            new EuropeanExercise(process.fixingDates()[i]);

                        Swaption swaption =
                            new Swaption(forwardSwap, exercise);
                        swaption.setPricingEngine(engine);

                        GeneralStatistics stat = new GeneralStatistics();

                        for (int n = 0; n < nrTrails; ++n)
                        {
                            Sample <IPath> path = (n % 2 != 0) ? generator.antithetic()
                                          : generator.next();
                            MultiPath value = path.value as MultiPath;
                            Utils.QL_REQUIRE(value != null, () => "Invalid Path");
                            //Sample<MultiPath> path = generator.next();
                            List <double> rates_ = new InitializedList <double>(size);
                            for (int k = 0; k < process.size(); ++k)
                            {
                                rates_[k] = value[k][location[i]];
                            }
                            List <double> dis = process.discountBond(rates_);

                            double npv = 0.0;
                            for (int k = i; k < i + j; ++k)
                            {
                                npv += (swapRate - rates_[k])
                                       * (process.accrualEndTimes()[k]
                                          - process.accrualStartTimes()[k]) * dis[k];
                            }
                            stat.add(Math.Max(npv, 0.0));
                        }

                        if (Math.Abs(swaption.NPV() - stat.mean())
                            > stat.errorEstimate() * 2.35)
                        {
                            QAssert.Fail("Failed to reproduce swaption npv"
                                         + "\n    calculated: " + stat.mean()
                                         + "\n    expected:   " + swaption.NPV());
                        }
                    }
                }
            }
        }
Example #8
0
        public void testCalibration()
        {
            // Testing calibration of a Libor forward model
            const int    size      = 14;
            const double tolerance = 8e-3;

            double[] capVols = { 0.145708, 0.158465, 0.166248, 0.168672,
                                 0.169007, 0.167956, 0.166261, 0.164239,
                                 0.162082, 0.159923, 0.157781, 0.155745,
                                 0.153776, 0.151950, 0.150189, 0.148582,
                                 0.147034, 0.145598, 0.144248 };

            double[] swaptionVols = { 0.170595, 0.166844, 0.158306, 0.147444,
                                      0.136930, 0.126833, 0.118135, 0.175963,
                                      0.166359, 0.155203, 0.143712, 0.132769,
                                      0.122947, 0.114310, 0.174455, 0.162265,
                                      0.150539, 0.138734, 0.128215, 0.118470,
                                      0.110540, 0.169780, 0.156860, 0.144821,
                                      0.133537, 0.123167, 0.114363, 0.106500,
                                      0.164521, 0.151223, 0.139670, 0.128632,
                                      0.119123, 0.110330, 0.103114, 0.158956,
                                      0.146036, 0.134555, 0.124393, 0.115038,
                                      0.106996, 0.100064 };

            IborIndex index = makeIndex();
            LiborForwardModelProcess    process       = new LiborForwardModelProcess(size, index);
            Handle <YieldTermStructure> termStructure = index.forwardingTermStructure();

            // set-up the model
            LmVolatilityModel volaModel = new LmExtLinearExponentialVolModel(process.fixingTimes(),
                                                                             0.5, 0.6, 0.1, 0.1);

            LmCorrelationModel corrModel = new LmLinearExponentialCorrelationModel(size, 0.5, 0.8);

            LiborForwardModel model = new LiborForwardModel(process, volaModel, corrModel);

            int        swapVolIndex = 0;
            DayCounter dayCounter   = index.forwardingTermStructure().link.dayCounter();

            // set-up calibration helper
            List <CalibrationHelper> calibrationHelper = new List <CalibrationHelper>();

            int i;

            for (i = 2; i < size; ++i)
            {
                Period         maturity = i * index.tenor();
                Handle <Quote> capVol   = new Handle <Quote>(new SimpleQuote(capVols[i - 2]));

                CalibrationHelper caphelper = new CapHelper(maturity, capVol, index, Frequency.Annual,
                                                            index.dayCounter(), true, termStructure, CalibrationHelper.CalibrationErrorType.ImpliedVolError);

                caphelper.setPricingEngine(new AnalyticCapFloorEngine(model, termStructure));

                calibrationHelper.Add(caphelper);

                if (i <= size / 2)
                {
                    // add a few swaptions to test swaption calibration as well
                    for (int j = 1; j <= size / 2; ++j)
                    {
                        Period         len         = j * index.tenor();
                        Handle <Quote> swaptionVol = new Handle <Quote>(
                            new SimpleQuote(swaptionVols[swapVolIndex++]));

                        CalibrationHelper swaptionHelper =
                            new SwaptionHelper(maturity, len, swaptionVol, index,
                                               index.tenor(), dayCounter,
                                               index.dayCounter(),
                                               termStructure, CalibrationHelper.CalibrationErrorType.ImpliedVolError);

                        swaptionHelper.setPricingEngine(new LfmSwaptionEngine(model, termStructure));

                        calibrationHelper.Add(swaptionHelper);
                    }
                }
            }

            LevenbergMarquardt om = new LevenbergMarquardt(1e-6, 1e-6, 1e-6);

            //ConjugateGradient gc = new ConjugateGradient();

            model.calibrate(calibrationHelper,
                            om,
                            new EndCriteria(2000, 100, 1e-6, 1e-6, 1e-6),
                            new Constraint(),
                            new List <double>());

            // measure the calibration error
            double calculated = 0.0;

            for (i = 0; i < calibrationHelper.Count; ++i)
            {
                double diff = calibrationHelper[i].calibrationError();
                calculated += diff * diff;
            }

            if (Math.Sqrt(calculated) > tolerance)
            {
                QAssert.Fail("Failed to calibrate libor forward model"
                             + "\n    calculated diff: " + Math.Sqrt(calculated)
                             + "\n    expected : smaller than  " + tolerance);
            }
        }
Example #9
0
        public void testSimpleCovarianceModels()
        {
            // Testing simple covariance models
            const int    size      = 10;
            const double tolerance = 1e-14;
            int          i;

            LmCorrelationModel corrModel = new LmExponentialCorrelationModel(size, 0.1);

            Matrix recon = corrModel.correlation(0.0, null)
                           - corrModel.pseudoSqrt(0.0, null) * Matrix.transpose(corrModel.pseudoSqrt(0.0, null));

            for (i = 0; i < size; ++i)
            {
                for (int j = 0; j < size; ++j)
                {
                    if (Math.Abs(recon[i, j]) > tolerance)
                    {
                        QAssert.Fail("Failed to reproduce correlation matrix"
                                     + "\n    calculated: " + recon[i, j]
                                     + "\n    expected:   " + 0);
                    }
                }
            }

            List <double> fixingTimes = new InitializedList <double>(size);

            for (i = 0; i < size; ++i)
            {
                fixingTimes[i] = 0.5 * i;
            }

            const double a = 0.2;
            const double b = 0.1;
            const double c = 2.1;
            const double d = 0.3;

            LmVolatilityModel volaModel = new LmLinearExponentialVolatilityModel(fixingTimes, a, b, c, d);

            LfmCovarianceProxy covarProxy = new LfmCovarianceProxy(volaModel, corrModel);

            LiborForwardModelProcess process = new LiborForwardModelProcess(size, makeIndex());

            LiborForwardModel liborModel = new LiborForwardModel(process, volaModel, corrModel);

            for (double t = 0; t < 4.6; t += 0.31)
            {
                recon = covarProxy.covariance(t, null)
                        - covarProxy.diffusion(t, null) * Matrix.transpose(covarProxy.diffusion(t, null));

                for (int k = 0; k < size; ++k)
                {
                    for (int j = 0; j < size; ++j)
                    {
                        if (Math.Abs(recon[k, j]) > tolerance)
                        {
                            QAssert.Fail("Failed to reproduce correlation matrix"
                                         + "\n    calculated: " + recon[k, j]
                                         + "\n    expected:   " + 0);
                        }
                    }
                }

                Vector volatility = volaModel.volatility(t, null);

                for (int k = 0; k < size; ++k)
                {
                    double expected = 0;
                    if (k > 2 * t)
                    {
                        double T = fixingTimes[k];
                        expected = (a * (T - t) + d) * Math.Exp(-b * (T - t)) + c;
                    }

                    if (Math.Abs(expected - volatility[k]) > tolerance)
                    {
                        QAssert.Fail("Failed to reproduce volatities"
                                     + "\n    calculated: " + volatility[k]
                                     + "\n    expected:   " + expected);
                    }
                }
            }
        }
Example #10
0
        public void testMonteCarloCapletPricing()
        {
            //"Testing caplet LMM Monte-Carlo caplet pricing..."

            //SavedSettings backup;

            /* factor loadings are taken from Hull & White article
             * plus extra normalisation to get orthogonal eigenvectors
             * http://www.rotman.utoronto.ca/~amackay/fin/libormktmodel2.pdf */
            double[] compValues = { 0.85549771,  0.46707264,  0.22353259,
                                    0.91915359,  0.37716089,  0.11360610,
                                    0.96438280,  0.26413316, -0.01412414,
                                    0.97939148,  0.13492952, -0.15028753,
                                    0.95970595, -0.00000000, -0.28100621,
                                    0.97939148, -0.13492952, -0.15028753,
                                    0.96438280, -0.26413316, -0.01412414,
                                    0.91915359, -0.37716089,  0.11360610,
                                    0.85549771, -0.46707264, 0.22353259 };

            Matrix        volaComp    = new Matrix(9, 3);
            List <double> lcompValues = new InitializedList <double>(27, 0);
            List <double> ltemp       = new InitializedList <double>(3, 0);

            lcompValues = compValues.ToList();
            //std::copy(compValues, compValues+9*3, volaComp.begin());
            for (int i = 0; i < 9; i++)
            {
                ltemp = lcompValues.GetRange(3 * i, 3);
                for (int j = 0; j < 3; j++)
                {
                    volaComp[i, j] = ltemp[j];
                }
            }
            LiborForwardModelProcess process1 = makeProcess();
            LiborForwardModelProcess process2 = makeProcess(volaComp);

            List <double> tmp  = process1.fixingTimes();
            TimeGrid      grid = new TimeGrid(tmp, 12);

            List <int> location = new List <int>();

            for (int i = 0; i < tmp.Count; ++i)
            {
                location.Add(grid.index(tmp[i]));
            }

            // set-up a small Monte-Carlo simulation to price caplets
            // and ratchet caps using a one- and a three factor libor market model

            ulong seed = 42;

            LowDiscrepancy.icInstance = new InverseCumulativeNormal();
            IRNG rsg1 = (IRNG) new LowDiscrepancy().make_sequence_generator(
                process1.factors() * (grid.size() - 1), seed);
            IRNG rsg2 = (IRNG) new LowDiscrepancy().make_sequence_generator(
                process2.factors() * (grid.size() - 1), seed);

            MultiPathGenerator <IRNG> generator1 = new MultiPathGenerator <IRNG> (process1, grid, rsg1, false);
            MultiPathGenerator <IRNG> generator2 = new MultiPathGenerator <IRNG> (process2, grid, rsg2, false);

            const int nrTrails             = 250000;
            List <GeneralStatistics> stat1 = new InitializedList <GeneralStatistics>(process1.size());
            List <GeneralStatistics> stat2 = new InitializedList <GeneralStatistics>(process2.size());
            List <GeneralStatistics> stat3 = new InitializedList <GeneralStatistics>(process2.size() - 1);

            for (int i = 0; i < nrTrails; ++i)
            {
                Sample <MultiPath> path1 = generator1.next();
                Sample <MultiPath> path2 = generator2.next();

                List <double> rates1 = new InitializedList <double>(len);
                List <double> rates2 = new InitializedList <double>(len);
                for (int j = 0; j < process1.size(); ++j)
                {
                    rates1[j] = path1.value[j][location[j]];
                    rates2[j] = path2.value[j][location[j]];
                }

                List <double> dis1 = process1.discountBond(rates1);
                List <double> dis2 = process2.discountBond(rates2);

                for (int k = 0; k < process1.size(); ++k)
                {
                    double accrualPeriod = process1.accrualEndTimes()[k]
                                           - process1.accrualStartTimes()[k];
                    // caplet payoff function, cap rate at 4%
                    double payoff1 = Math.Max(rates1[k] - 0.04, 0.0) * accrualPeriod;

                    double payoff2 = Math.Max(rates2[k] - 0.04, 0.0) * accrualPeriod;
                    stat1[k].add(dis1[k] * payoff1);
                    stat2[k].add(dis2[k] * payoff2);

                    if (k != 0)
                    {
                        // ratchet cap payoff function
                        double payoff3 = Math.Max(rates2[k] - (rates2[k - 1] + 0.0025), 0.0)
                                         * accrualPeriod;
                        stat3[k - 1].add(dis2[k] * payoff3);
                    }
                }
            }

            double[] capletNpv = { 0.000000000000, 0.000002841629, 0.002533279333,
                                   0.009577143571, 0.017746502618, 0.025216116835,
                                   0.031608230268, 0.036645683881, 0.039792254012,
                                   0.041829864365 };

            double[] ratchetNpv = { 0.0082644895, 0.0082754754, 0.0082159966,
                                    0.0082982822, 0.0083803357, 0.0084366961,
                                    0.0084173270, 0.0081803406, 0.0079533814 };

            for (int k = 0; k < process1.size(); ++k)
            {
                double calculated1 = stat1[k].mean();
                double tolerance1  = stat1[k].errorEstimate();
                double expected    = capletNpv[k];

                if (Math.Abs(calculated1 - expected) > tolerance1)
                {
                    Assert.Fail("Failed to reproduce expected caplet NPV"
                                + "\n    calculated: " + calculated1
                                + "\n    error int:  " + tolerance1
                                + "\n    expected:   " + expected);
                }

                double calculated2 = stat2[k].mean();
                double tolerance2  = stat2[k].errorEstimate();

                if (Math.Abs(calculated2 - expected) > tolerance2)
                {
                    Assert.Fail("Failed to reproduce expected caplet NPV"
                                + "\n    calculated: " + calculated2
                                + "\n    error int:  " + tolerance2
                                + "\n    expected:   " + expected);
                }

                if (k != 0)
                {
                    double calculated3 = stat3[k - 1].mean();
                    double tolerance3  = stat3[k - 1].errorEstimate();
                    expected = ratchetNpv[k - 1];

                    double refError = 1e-5; // 1e-5. error bars of the reference values

                    if (Math.Abs(calculated3 - expected) > tolerance3 + refError)
                    {
                        Assert.Fail("Failed to reproduce expected caplet NPV"
                                    + "\n    calculated: " + calculated3
                                    + "\n    error int:  " + tolerance3 + refError
                                    + "\n    expected:   " + expected);
                    }
                }
            }
        }