public override void calculate()
        {
            double sigmaShift_vega  = 0.001;
            double sigmaShift_volga = 0.0001;
            double spotShift_delta  = 0.0001 * spotFX_.link.value();
            double sigmaShift_vanna = 0.0001;

            Utils.QL_REQUIRE(arguments_.barrierType == DoubleBarrier.Type.KnockIn ||
                             arguments_.barrierType == DoubleBarrier.Type.KnockOut, () =>
                             "Only same type barrier supported");

            Handle <Quote> x0Quote     = new Handle <Quote>(new SimpleQuote(spotFX_.link.value()));
            Handle <Quote> atmVolQuote = new Handle <Quote>(new SimpleQuote(atmVol_.link.value()));

            BlackVolTermStructure blackVolTS = new BlackConstantVol(Settings.evaluationDate(),
                                                                    new NullCalendar(), atmVolQuote, new Actual365Fixed());

            BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(x0Quote, foreignTS_, domesticTS_,
                                                                                   new Handle <BlackVolTermStructure>(blackVolTS));

            IPricingEngine engineBS = getOriginalEngine_(stochProcess, series_);

            BlackDeltaCalculator blackDeltaCalculatorAtm = new BlackDeltaCalculator(
                Option.Type.Call, atmVol_.link.deltaType(), x0Quote.link.value(),
                domesticTS_.link.discount(T_), foreignTS_.link.discount(T_),
                atmVol_.link.value() * Math.Sqrt(T_));

            double atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_.link.atmType());

            double call25Vol = vol25Call_.link.value();
            double put25Vol  = vol25Put_.link.value();
            BlackDeltaCalculator blackDeltaCalculatorPut25 = new BlackDeltaCalculator(
                Option.Type.Put, vol25Put_.link.deltaType(), x0Quote.link.value(),
                domesticTS_.link.discount(T_), foreignTS_.link.discount(T_),
                put25Vol * Math.Sqrt(T_));
            double put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25);
            BlackDeltaCalculator blackDeltaCalculatorCall25 = new BlackDeltaCalculator(
                Option.Type.Call, vol25Call_.link.deltaType(), x0Quote.link.value(),
                domesticTS_.link.discount(T_), foreignTS_.link.discount(T_),
                call25Vol * Math.Sqrt(T_));
            double call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25);

            //here use vanna volga interpolated smile to price vanilla
            List <double> strikes = new List <double>();
            List <double> vols    = new List <double>();

            strikes.Add(put25Strike);
            vols.Add(put25Vol);
            strikes.Add(atmStrike);
            vols.Add(atmVol_.link.value());
            strikes.Add(call25Strike);
            vols.Add(call25Vol);
            VannaVolga vannaVolga = new VannaVolga(x0Quote.link.value(), foreignTS_.link.discount(T_),
                                                   foreignTS_.link.discount(T_), T_);
            Interpolation interpolation = vannaVolga.interpolate(strikes, strikes.Count, vols);

            interpolation.enableExtrapolation();
            StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff;

            Utils.QL_REQUIRE(payoff != null, () => "invalid payoff");
            double strikeVol = interpolation.value(payoff.strike());
            //vannila option price
            double vanillaOption = Utils.blackFormula(payoff.optionType(), payoff.strike(),
                                                      x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                      strikeVol * Math.Sqrt(T_),
                                                      domesticTS_.link.discount(T_));

            //already out
            if ((x0Quote.link.value() > arguments_.barrier_hi || x0Quote.link.value() < arguments_.barrier_lo) &&
                arguments_.barrierType == DoubleBarrier.Type.KnockOut)
            {
                results_.value = 0.0;
                results_.additionalResults["VanillaPrice"]    = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierOutPrice"] = 0.0;
            }
            //already in
            else if ((x0Quote.link.value() > arguments_.barrier_hi || x0Quote.link.value() < arguments_.barrier_lo) &&
                     arguments_.barrierType == DoubleBarrier.Type.KnockIn)
            {
                results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["VanillaPrice"]    = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierOutPrice"] = 0.0;
            }
            else
            {
                //set up BS barrier option pricing
                //only calculate out barrier option price
                // in barrier price = vanilla - out barrier
                //StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff;
                DoubleBarrierOption doubleBarrierOption = new DoubleBarrierOption(
                    arguments_.barrierType,
                    arguments_.barrier_lo.GetValueOrDefault(),
                    arguments_.barrier_hi.GetValueOrDefault(),
                    arguments_.rebate.GetValueOrDefault(),
                    payoff,
                    arguments_.exercise);

                doubleBarrierOption.setPricingEngine(engineBS);

                //BS price
                double priceBS = doubleBarrierOption.NPV();

                double priceAtmCallBS = Utils.blackFormula(Option.Type.Call, atmStrike,
                                                           x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                           atmVol_.link.value() * Math.Sqrt(T_),
                                                           domesticTS_.link.discount(T_));
                double price25CallBS = Utils.blackFormula(Option.Type.Call, call25Strike,
                                                          x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                          atmVol_.link.value() * Math.Sqrt(T_),
                                                          domesticTS_.link.discount(T_));
                double price25PutBS = Utils.blackFormula(Option.Type.Put, put25Strike,
                                                         x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                         atmVol_.link.value() * Math.Sqrt(T_),
                                                         domesticTS_.link.discount(T_));

                //market price
                double priceAtmCallMkt = Utils.blackFormula(Option.Type.Call, atmStrike,
                                                            x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                            atmVol_.link.value() * Math.Sqrt(T_),
                                                            domesticTS_.link.discount(T_));
                double price25CallMkt = Utils.blackFormula(Option.Type.Call, call25Strike,
                                                           x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                           call25Vol * Math.Sqrt(T_),
                                                           domesticTS_.link.discount(T_));
                double price25PutMkt = Utils.blackFormula(Option.Type.Put, put25Strike,
                                                          x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                          put25Vol * Math.Sqrt(T_),
                                                          domesticTS_.link.discount(T_));

                //Analytical Black Scholes formula
                NormalDistribution norm  = new NormalDistribution();
                double             d1atm = (Math.Log(x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_) / atmStrike)
                                            + 0.5 * Math.Pow(atmVolQuote.link.value(), 2.0) * T_) / (atmVolQuote.link.value() * Math.Sqrt(T_));
                double vegaAtm_Analytical  = x0Quote.link.value() * norm.value(d1atm) * Math.Sqrt(T_) * foreignTS_.link.discount(T_);
                double vannaAtm_Analytical = vegaAtm_Analytical / x0Quote.link.value() * (1.0 - d1atm / (atmVolQuote.link.value() * Math.Sqrt(T_)));
                double volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote.link.value() * Math.Sqrt(T_)) / atmVolQuote.link.value();

                double d125call = (Math.Log(x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_) / call25Strike)
                                   + 0.5 * Math.Pow(atmVolQuote.link.value(), 2.0) * T_) / (atmVolQuote.link.value() * Math.Sqrt(T_));
                double vega25Call_Analytical  = x0Quote.link.value() * norm.value(d125call) * Math.Sqrt(T_) * foreignTS_.link.discount(T_);
                double vanna25Call_Analytical = vega25Call_Analytical / x0Quote.link.value() * (1.0 - d125call / (atmVolQuote.link.value() * Math.Sqrt(T_)));
                double volga25Call_Analytical = vega25Call_Analytical * d125call * (d125call - atmVolQuote.link.value() * Math.Sqrt(T_)) / atmVolQuote.link.value();

                double d125Put = (Math.Log(x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_) / put25Strike)
                                  + 0.5 * Math.Pow(atmVolQuote.link.value(), 2.0) * T_) / (atmVolQuote.link.value() * Math.Sqrt(T_));
                double vega25Put_Analytical  = x0Quote.link.value() * norm.value(d125Put) * Math.Sqrt(T_) * foreignTS_.link.discount(T_);
                double vanna25Put_Analytical = vega25Put_Analytical / x0Quote.link.value() * (1.0 - d125Put / (atmVolQuote.link.value() * Math.Sqrt(T_)));
                double volga25Put_Analytical = vega25Put_Analytical * d125Put * (d125Put - atmVolQuote.link.value() * Math.Sqrt(T_)) / atmVolQuote.link.value();


                //BS vega
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega);
                doubleBarrierOption.recalculate();
                double vegaBarBS = (doubleBarrierOption.NPV() - priceBS) / sigmaShift_vega;
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vega);//setback

                //BS volga

                //vegaBar2
                //base NPV
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_volga);
                doubleBarrierOption.recalculate();
                double priceBS2 = doubleBarrierOption.NPV();

                //shifted npv
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega);
                doubleBarrierOption.recalculate();
                double vegaBarBS2 = (doubleBarrierOption.NPV() - priceBS2) / sigmaShift_vega;
                double volgaBarBS = (vegaBarBS2 - vegaBarBS) / sigmaShift_volga;
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value()
                                                                  - sigmaShift_volga
                                                                  - sigmaShift_vega);                        //setback

                //BS Delta
                //base delta
                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//shift forth
                doubleBarrierOption.recalculate();
                double priceBS_delta1 = doubleBarrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back
                doubleBarrierOption.recalculate();
                double priceBS_delta2 = doubleBarrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back
                double deltaBar1 = (priceBS_delta1 - priceBS_delta2) / (2.0 * spotShift_delta);

                //shifted vanna
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vanna); //shift sigma
                //shifted delta
                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);          //shift forth
                doubleBarrierOption.recalculate();
                priceBS_delta1 = doubleBarrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back
                doubleBarrierOption.recalculate();
                priceBS_delta2 = doubleBarrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back
                double deltaBar2 = (priceBS_delta1 - priceBS_delta2) / (2.0 * spotShift_delta);

                double vannaBarBS = (deltaBar2 - deltaBar1) / sigmaShift_vanna;

                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vanna);//set back

                //Matrix
                Matrix A = new Matrix(3, 3, 0.0);

                //analytical
                A[0, 0] = vegaAtm_Analytical;
                A[0, 1] = vega25Call_Analytical;
                A[0, 2] = vega25Put_Analytical;
                A[1, 0] = vannaAtm_Analytical;
                A[1, 1] = vanna25Call_Analytical;
                A[1, 2] = vanna25Put_Analytical;
                A[2, 0] = volgaAtm_Analytical;
                A[2, 1] = volga25Call_Analytical;
                A[2, 2] = volga25Put_Analytical;

                Vector b = new Vector(3, 0.0);
                b[0] = vegaBarBS;
                b[1] = vannaBarBS;
                b[2] = volgaBarBS;
                Vector q = Matrix.inverse(A) * b;

                double H = arguments_.barrier_hi.GetValueOrDefault();
                double L = arguments_.barrier_lo.GetValueOrDefault();
                double theta_tilt_minus = ((domesticTS_.link.zeroRate(T_, Compounding.Continuous).value() -
                                            foreignTS_.link.zeroRate(T_, Compounding.Continuous).value()) /
                                           atmVol_.link.value() - atmVol_.link.value() / 2.0) * Math.Sqrt(T_);
                double h = 1.0 / atmVol_.link.value() * Math.Log(H / x0Quote.link.value()) / Math.Sqrt(T_);
                double l = 1.0 / atmVol_.link.value() * Math.Log(L / x0Quote.link.value()) / Math.Sqrt(T_);
                CumulativeNormalDistribution cnd = new CumulativeNormalDistribution();

                double doubleNoTouch = 0.0;
                for (int j = -series_; j < series_; j++)
                {
                    double e_minus = 2 * j * (h - l) - theta_tilt_minus;
                    doubleNoTouch += Math.Exp(-2.0 * j * theta_tilt_minus * (h - l)) * (cnd.value(h + e_minus) - cnd.value(l + e_minus))
                                     - Math.Exp(-2.0 * j * theta_tilt_minus * (h - l) + 2.0 * theta_tilt_minus * h) *
                                     (cnd.value(h - 2.0 * h + e_minus) - cnd.value(l - 2.0 * h + e_minus));
                }

                double p_survival = doubleNoTouch;

                double lambda = p_survival;
                double adjust = q[0] * (priceAtmCallMkt - priceAtmCallBS)
                                + q[1] * (price25CallMkt - price25CallBS)
                                + q[2] * (price25PutMkt - price25PutBS);
                double outPrice = priceBS + lambda * adjust;//
                double inPrice;

                //adapt Vanilla delta
                if (adaptVanDelta_ == true)
                {
                    outPrice += lambda * (bsPriceWithSmile_ - vanillaOption);
                    //capfloored by (0, vanilla)
                    outPrice = Math.Max(0.0, Math.Min(bsPriceWithSmile_, outPrice));
                    inPrice  = bsPriceWithSmile_ - outPrice;
                }
                else
                {
                    //capfloored by (0, vanilla)
                    outPrice = Math.Max(0.0, Math.Min(vanillaOption, outPrice));
                    inPrice  = vanillaOption - outPrice;
                }

                if (arguments_.barrierType == DoubleBarrier.Type.KnockOut)
                {
                    results_.value = outPrice;
                }
                else
                {
                    results_.value = inPrice;
                }

                results_.additionalResults["VanillaPrice"]    = vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = inPrice;
                results_.additionalResults["BarrierOutPrice"] = outPrice;
                results_.additionalResults["lambda"]          = lambda;
            }
        }
Exemplo n.º 2
0
        public override void calculate()
        {
            double sigmaShift_vega  = 0.0001;
            double sigmaShift_volga = 0.0001;
            double spotShift_delta  = 0.0001 * spotFX_.link.value();
            double sigmaShift_vanna = 0.0001;

            Handle <Quote> x0Quote     = new Handle <Quote>(new SimpleQuote(spotFX_.link.value())); //used for shift
            Handle <Quote> atmVolQuote = new Handle <Quote>(new SimpleQuote(atmVol_.link.value())); //used for shift

            BlackVolTermStructure blackVolTS = new BlackConstantVol(Settings.evaluationDate(),
                                                                    new NullCalendar(), atmVolQuote, new Actual365Fixed());
            BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(x0Quote, foreignTS_, domesticTS_,
                                                                                   new Handle <BlackVolTermStructure>(blackVolTS));

            IPricingEngine engineBS = new AnalyticBarrierEngine(stochProcess);

            BlackDeltaCalculator blackDeltaCalculatorAtm = new BlackDeltaCalculator(
                Option.Type.Call, atmVol_.link.deltaType(), x0Quote.link.value(),
                domesticTS_.link.discount(T_), foreignTS_.link.discount(T_),
                atmVol_.link.value() * Math.Sqrt(T_));
            double atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_.link.atmType());

            double call25Vol = vol25Call_.link.value();
            double put25Vol  = vol25Put_.link.value();

            BlackDeltaCalculator blackDeltaCalculatorPut25 = new BlackDeltaCalculator(
                Option.Type.Put, vol25Put_.link.deltaType(), x0Quote.link.value(),
                domesticTS_.link.discount(T_), foreignTS_.link.discount(T_),
                put25Vol * Math.Sqrt(T_));
            double put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25);
            BlackDeltaCalculator blackDeltaCalculatorCall25 = new BlackDeltaCalculator(
                Option.Type.Call, vol25Call_.link.deltaType(), x0Quote.link.value(),
                domesticTS_.link.discount(T_), foreignTS_.link.discount(T_),
                call25Vol * Math.Sqrt(T_));
            double call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25);

            //here use vanna volga interpolated smile to price vanilla
            List <double> strikes = new List <double>();
            List <double> vols    = new List <double>();

            strikes.Add(put25Strike);
            vols.Add(put25Vol);
            strikes.Add(atmStrike);
            vols.Add(atmVol_.link.value());
            strikes.Add(call25Strike);
            vols.Add(call25Vol);
            VannaVolga vannaVolga = new VannaVolga(x0Quote.link.value(), domesticTS_.link.discount(T_),
                                                   foreignTS_.link.discount(T_), T_);
            Interpolation interpolation = vannaVolga.interpolate(strikes, strikes.Count, vols);

            interpolation.enableExtrapolation();
            StrikedTypePayoff payoff    = arguments_.payoff as StrikedTypePayoff;
            double            strikeVol = interpolation.value(payoff.strike());

            //vannila option price
            double vanillaOption = Utils.blackFormula(payoff.optionType(), payoff.strike(),
                                                      x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                      strikeVol * Math.Sqrt(T_),
                                                      domesticTS_.link.discount(T_));

            //spot > barrier up&out 0
            if (x0Quote.link.value() >= arguments_.barrier && arguments_.barrierType == Barrier.Type.UpOut)
            {
                results_.value = 0.0;
                results_.additionalResults["VanillaPrice"]    = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierOutPrice"] = 0.0;
            }
            //spot > barrier up&in vanilla
            else if (x0Quote.link.value() >= arguments_.barrier && arguments_.barrierType == Barrier.Type.UpIn)
            {
                results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["VanillaPrice"]    = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierOutPrice"] = 0.0;
            }
            //spot < barrier down&out 0
            else if (x0Quote.link.value() <= arguments_.barrier && arguments_.barrierType == Barrier.Type.DownOut)
            {
                results_.value = 0.0;
                results_.additionalResults["VanillaPrice"]    = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierOutPrice"] = 0.0;
            }
            //spot < barrier down&in vanilla
            else if (x0Quote.link.value() <= arguments_.barrier && arguments_.barrierType == Barrier.Type.DownIn)
            {
                results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["VanillaPrice"]    = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
                results_.additionalResults["BarrierOutPrice"] = 0.0;
            }
            else
            {
                //set up BS barrier option pricing
                //only calculate out barrier option price
                // in barrier price = vanilla - out barrier
                Barrier.Type barrierTyp;
                if (arguments_.barrierType == Barrier.Type.UpOut)
                {
                    barrierTyp = arguments_.barrierType;
                }
                else if (arguments_.barrierType == Barrier.Type.UpIn)
                {
                    barrierTyp = Barrier.Type.UpOut;
                }
                else if (arguments_.barrierType == Barrier.Type.DownOut)
                {
                    barrierTyp = arguments_.barrierType;
                }
                else
                {
                    barrierTyp = Barrier.Type.DownOut;
                }

                BarrierOption barrierOption = new BarrierOption(barrierTyp,
                                                                arguments_.barrier.GetValueOrDefault(),
                                                                arguments_.rebate.GetValueOrDefault(),
                                                                (StrikedTypePayoff)arguments_.payoff,
                                                                arguments_.exercise);

                barrierOption.setPricingEngine(engineBS);

                //BS price with atm vol
                double priceBS = barrierOption.NPV();

                double priceAtmCallBS = Utils.blackFormula(Option.Type.Call, atmStrike,
                                                           x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                           atmVol_.link.value() * Math.Sqrt(T_),
                                                           domesticTS_.link.discount(T_));
                double price25CallBS = Utils.blackFormula(Option.Type.Call, call25Strike,
                                                          x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                          atmVol_.link.value() * Math.Sqrt(T_),
                                                          domesticTS_.link.discount(T_));
                double price25PutBS = Utils.blackFormula(Option.Type.Put, put25Strike,
                                                         x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                         atmVol_.link.value() * Math.Sqrt(T_),
                                                         domesticTS_.link.discount(T_));

                //market price
                double priceAtmCallMkt = Utils.blackFormula(Option.Type.Call, atmStrike,
                                                            x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                            atmVol_.link.value() * Math.Sqrt(T_),
                                                            domesticTS_.link.discount(T_));

                double price25CallMkt = Utils.blackFormula(Option.Type.Call, call25Strike,
                                                           x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                           call25Vol * Math.Sqrt(T_),
                                                           domesticTS_.link.discount(T_));
                double price25PutMkt = Utils.blackFormula(Option.Type.Put, put25Strike,
                                                          x0Quote.link.value() * foreignTS_.link.discount(T_) / domesticTS_.link.discount(T_),
                                                          put25Vol * Math.Sqrt(T_),
                                                          domesticTS_.link.discount(T_));


                //Analytical Black Scholes formula for vanilla option
                NormalDistribution norm  = new NormalDistribution();
                double             d1atm = (Math.Log(x0Quote.link.value() * foreignTS_.link.discount(T_) /
                                                     domesticTS_.link.discount(T_) / atmStrike)
                                            + 0.5 * Math.Pow(atmVolQuote.link.value(), 2.0) * T_) /
                                           (atmVolQuote.link.value() * Math.Sqrt(T_));
                double vegaAtm_Analytical = x0Quote.link.value() * norm.value(d1atm) * Math.Sqrt(T_) *
                                            foreignTS_.link.discount(T_);
                double vannaAtm_Analytical = vegaAtm_Analytical / x0Quote.link.value() *
                                             (1.0 - d1atm / (atmVolQuote.link.value() * Math.Sqrt(T_)));
                double volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote.link.value() * Math.Sqrt(T_)) /
                                             atmVolQuote.link.value();

                double d125call = (Math.Log(x0Quote.link.value() * foreignTS_.link.discount(T_) /
                                            domesticTS_.link.discount(T_) / call25Strike)
                                   + 0.5 * Math.Pow(atmVolQuote.link.value(), 2.0) * T_) / (atmVolQuote.link.value() * Math.Sqrt(T_));
                double vega25Call_Analytical = x0Quote.link.value() * norm.value(d125call) * Math.Sqrt(T_) *
                                               foreignTS_.link.discount(T_);
                double vanna25Call_Analytical = vega25Call_Analytical / x0Quote.link.value() *
                                                (1.0 - d125call / (atmVolQuote.link.value() * Math.Sqrt(T_)));
                double volga25Call_Analytical = vega25Call_Analytical * d125call *
                                                (d125call - atmVolQuote.link.value() * Math.Sqrt(T_)) / atmVolQuote.link.value();

                double d125Put = (Math.Log(x0Quote.link.value() * foreignTS_.link.discount(T_) /
                                           domesticTS_.link.discount(T_) / put25Strike)
                                  + 0.5 * Math.Pow(atmVolQuote.link.value(), 2.0) * T_) / (atmVolQuote.link.value() * Math.Sqrt(T_));
                double vega25Put_Analytical = x0Quote.link.value() * norm.value(d125Put) * Math.Sqrt(T_) *
                                              foreignTS_.link.discount(T_);
                double vanna25Put_Analytical = vega25Put_Analytical / x0Quote.link.value() *
                                               (1.0 - d125Put / (atmVolQuote.link.value() * Math.Sqrt(T_)));
                double volga25Put_Analytical = vega25Put_Analytical * d125Put *
                                               (d125Put - atmVolQuote.link.value() * Math.Sqrt(T_)) / atmVolQuote.link.value();


                //BS vega
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega);
                barrierOption.recalculate();
                double vegaBarBS = (barrierOption.NPV() - priceBS) / sigmaShift_vega;

                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vega);//setback

                //BS volga

                //vegaBar2
                //base NPV
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_volga);
                barrierOption.recalculate();
                double priceBS2 = barrierOption.NPV();

                //shifted npv
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega);
                barrierOption.recalculate();
                double vegaBarBS2 = (barrierOption.NPV() - priceBS2) / sigmaShift_vega;
                double volgaBarBS = (vegaBarBS2 - vegaBarBS) / sigmaShift_volga;

                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value()
                                                                  - sigmaShift_volga
                                                                  - sigmaShift_vega);                     //setback

                //BS Delta
                //base delta
                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//shift forth
                barrierOption.recalculate();
                double priceBS_delta1 = barrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back
                barrierOption.recalculate();
                double priceBS_delta2 = barrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back
                double deltaBar1 = (priceBS_delta1 - priceBS_delta2) / (2.0 * spotShift_delta);

                //shifted delta
                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vanna); //shift sigma
                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);          //shift forth
                barrierOption.recalculate();
                priceBS_delta1 = barrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back
                barrierOption.recalculate();
                priceBS_delta2 = barrierOption.NPV();

                ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back
                double deltaBar2 = (priceBS_delta1 - priceBS_delta2) / (2.0 * spotShift_delta);

                double vannaBarBS = (deltaBar2 - deltaBar1) / sigmaShift_vanna;

                ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vanna);//set back

                //Matrix
                Matrix A = new Matrix(3, 3, 0.0);

                //analytical
                A[0, 0] = vegaAtm_Analytical;
                A[0, 1] = vega25Call_Analytical;
                A[0, 2] = vega25Put_Analytical;
                A[1, 0] = vannaAtm_Analytical;
                A[1, 1] = vanna25Call_Analytical;
                A[1, 2] = vanna25Put_Analytical;
                A[2, 0] = volgaAtm_Analytical;
                A[2, 1] = volga25Call_Analytical;
                A[2, 2] = volga25Put_Analytical;

                Vector b = new Vector(3, 0.0);
                b[0] = vegaBarBS;
                b[1] = vannaBarBS;
                b[2] = volgaBarBS;

                //Vector q = inverse(A) * b; TODO implements transpose
                Vector q = Matrix.inverse(A) * b;


                //touch probability
                CumulativeNormalDistribution cnd = new CumulativeNormalDistribution();
                double mu = domesticTS_.link.zeroRate(T_, Compounding.Continuous).value() -
                            foreignTS_.link.zeroRate(T_, Compounding.Continuous).value() -
                            Math.Pow(atmVol_.link.value(), 2.0) / 2.0;
                double h2 = (Math.Log(arguments_.barrier.GetValueOrDefault() / x0Quote.link.value()) + mu * T_) /
                            (atmVol_.link.value() * Math.Sqrt(T_));
                double h2Prime = (Math.Log(x0Quote.link.value() / arguments_.barrier.GetValueOrDefault()) + mu * T_) /
                                 (atmVol_.link.value() * Math.Sqrt(T_));
                double probTouch = 0.0;
                if (arguments_.barrierType == Barrier.Type.UpIn || arguments_.barrierType == Barrier.Type.UpOut)
                {
                    probTouch = cnd.value(h2Prime) + Math.Pow(arguments_.barrier.GetValueOrDefault() / x0Quote.link.value(),
                                                              2.0 * mu / Math.Pow(atmVol_.link.value(), 2.0)) * cnd.value(-h2);
                }
                else
                {
                    probTouch = cnd.value(-h2Prime) + Math.Pow(arguments_.barrier.GetValueOrDefault() / x0Quote.link.value(),
                                                               2.0 * mu / Math.Pow(atmVol_.link.value(), 2.0)) * cnd.value(h2);
                }
                double p_survival = 1.0 - probTouch;

                double lambda = p_survival;
                double adjust = q[0] * (priceAtmCallMkt - priceAtmCallBS)
                                + q[1] * (price25CallMkt - price25CallBS)
                                + q[2] * (price25PutMkt - price25PutBS);
                double outPrice = priceBS + lambda * adjust;//
                double inPrice;

                //adapt Vanilla delta
                if (adaptVanDelta_ == true)
                {
                    outPrice += lambda * (bsPriceWithSmile_ - vanillaOption);
                    //capfloored by (0, vanilla)
                    outPrice = Math.Max(0.0, Math.Min(bsPriceWithSmile_, outPrice));
                    inPrice  = bsPriceWithSmile_ - outPrice;
                }
                else
                {
                    //capfloored by (0, vanilla)
                    outPrice = Math.Max(0.0, Math.Min(vanillaOption, outPrice));
                    inPrice  = vanillaOption - outPrice;
                }

                if (arguments_.barrierType == Barrier.Type.DownOut || arguments_.barrierType == Barrier.Type.UpOut)
                {
                    results_.value = outPrice;
                }
                else
                {
                    results_.value = inPrice;
                }

                results_.additionalResults["VanillaPrice"]    = vanillaOption;
                results_.additionalResults["BarrierInPrice"]  = inPrice;
                results_.additionalResults["BarrierOutPrice"] = outPrice;
                results_.additionalResults["lambda"]          = lambda;
            }
        }
Exemplo n.º 3
0
        public override void update()
        {
            Vector        tmp = new Vector(size_);
            List <double> dx  = new InitializedList <double>(size_ - 1),
                          S   = new InitializedList <double>(size_ - 1);

            for (int i = 0; i < size_ - 1; ++i)
            {
                dx[i] = xBegin_[i + 1] - xBegin_[i];
                S[i]  = (yBegin_[i + 1] - yBegin_[i]) / dx[i];
            }

            // first derivative approximation
            if (da_ == CubicInterpolation.DerivativeApprox.Spline)
            {
                TridiagonalOperator L = new TridiagonalOperator(size_);
                for (int i = 1; i < size_ - 1; ++i)
                {
                    L.setMidRow(i, dx[i], 2.0 * (dx[i] + dx[i - 1]), dx[i - 1]);
                    tmp[i] = 3.0 * (dx[i] * S[i - 1] + dx[i - 1] * S[i]);
                }

                // left boundary condition
                switch (leftType_)
                {
                case CubicInterpolation.BoundaryCondition.NotAKnot:
                    // ignoring end condition value
                    L.setFirstRow(dx[1] * (dx[1] + dx[0]), (dx[0] + dx[1]) * (dx[0] + dx[1]));
                    tmp[0] = S[0] * dx[1] * (2.0 * dx[1] + 3.0 * dx[0]) + S[1] * dx[0] * dx[0];
                    break;

                case CubicInterpolation.BoundaryCondition.FirstDerivative:
                    L.setFirstRow(1.0, 0.0);
                    tmp[0] = leftValue_;
                    break;

                case CubicInterpolation.BoundaryCondition.SecondDerivative:
                    L.setFirstRow(2.0, 1.0);
                    tmp[0] = 3.0 * S[0] - leftValue_ * dx[0] / 2.0;
                    break;

                case CubicInterpolation.BoundaryCondition.Periodic:
                    // ignoring end condition value
                    throw new NotImplementedException("this end condition is not implemented yet");

                case CubicInterpolation.BoundaryCondition.Lagrange:
                    L.setFirstRow(1.0, 0.0);
                    tmp[0] = cubicInterpolatingPolynomialDerivative(
                        this.xBegin_[0], this.xBegin_[1],
                        this.xBegin_[2], this.xBegin_[3],
                        this.yBegin_[0], this.yBegin_[1],
                        this.yBegin_[2], this.yBegin_[3],
                        this.xBegin_[0]);
                    break;

                default:
                    throw new ArgumentException("unknown end condition");
                }

                // right boundary condition
                switch (rightType_)
                {
                case CubicInterpolation.BoundaryCondition.NotAKnot:
                    // ignoring end condition value
                    L.setLastRow(-(dx[size_ - 2] + dx[size_ - 3]) * (dx[size_ - 2] + dx[size_ - 3]),
                                 -dx[size_ - 3] * (dx[size_ - 3] + dx[size_ - 2]));
                    tmp[size_ - 1] = -S[size_ - 3] * dx[size_ - 2] * dx[size_ - 2] -
                                     S[size_ - 2] * dx[size_ - 3] * (3.0 * dx[size_ - 2] + 2.0 * dx[size_ - 3]);
                    break;

                case CubicInterpolation.BoundaryCondition.FirstDerivative:
                    L.setLastRow(0.0, 1.0);
                    tmp[size_ - 1] = rightValue_;
                    break;

                case CubicInterpolation.BoundaryCondition.SecondDerivative:
                    L.setLastRow(1.0, 2.0);
                    tmp[size_ - 1] = 3.0 * S[size_ - 2] + rightValue_ * dx[size_ - 2] / 2.0;
                    break;

                case CubicInterpolation.BoundaryCondition.Periodic:
                    // ignoring end condition value
                    throw new NotImplementedException("this end condition is not implemented yet");

                case CubicInterpolation.BoundaryCondition.Lagrange:
                    L.setLastRow(0.0, 1.0);
                    tmp[size_ - 1] = cubicInterpolatingPolynomialDerivative(
                        this.xBegin_[size_ - 4], this.xBegin_[size_ - 3],
                        this.xBegin_[size_ - 2], this.xBegin_[size_ - 1],
                        this.yBegin_[size_ - 4], this.yBegin_[size_ - 3],
                        this.yBegin_[size_ - 2], this.yBegin_[size_ - 1],
                        this.xBegin_[size_ - 1]);
                    break;

                default:
                    throw new ArgumentException("unknown end condition");
                }

                // solve the system
                tmp = L.solveFor(tmp);
            }
            else if (da_ == CubicInterpolation.DerivativeApprox.SplineOM1)
            {
                Matrix T_ = new Matrix(size_ - 2, size_, 0.0);
                for (int i = 0; i < size_ - 2; ++i)
                {
                    T_[i, i]     = dx[i] / 6.0;
                    T_[i, i + 1] = (dx[i + 1] + dx[i]) / 3.0;
                    T_[i, i + 2] = dx[i + 1] / 6.0;
                }
                Matrix S_ = new Matrix(size_ - 2, size_, 0.0);
                for (int i = 0; i < size_ - 2; ++i)
                {
                    S_[i, i]     = 1.0 / dx[i];
                    S_[i, i + 1] = -(1.0 / dx[i + 1] + 1.0 / dx[i]);
                    S_[i, i + 2] = 1.0 / dx[i + 1];
                }
                Matrix Up_ = new Matrix(size_, 2, 0.0);
                Up_[0, 0]         = 1;
                Up_[size_ - 1, 1] = 1;
                Matrix Us_ = new Matrix(size_, size_ - 2, 0.0);
                for (int i = 0; i < size_ - 2; ++i)
                {
                    Us_[i + 1, i] = 1;
                }
                Matrix Z_ = Us_ * Matrix.inverse(T_ * Us_);
                Matrix I_ = new Matrix(size_, size_, 0.0);
                for (int i = 0; i < size_; ++i)
                {
                    I_[i, i] = 1;
                }
                Matrix V_ = (I_ - Z_ * T_) * Up_;
                Matrix W_ = Z_ * S_;
                Matrix Q_ = new Matrix(size_, size_, 0.0);
                Q_[0, 0] = 1.0 / (size_ - 1) * dx[0] * dx[0] * dx[0];
                Q_[0, 1] = 7.0 / 8 * 1.0 / (size_ - 1) * dx[0] * dx[0] * dx[0];
                for (int i = 1; i < size_ - 1; ++i)
                {
                    Q_[i, i - 1] = 7.0 / 8 * 1.0 / (size_ - 1) * dx[i - 1] * dx[i - 1] * dx[i - 1];
                    Q_[i, i]     = 1.0 / (size_ - 1) * dx[i] * dx[i] * dx[i] + 1.0 / (size_ - 1) * dx[i - 1] * dx[i - 1] * dx[i - 1];
                    Q_[i, i + 1] = 7.0 / 8 * 1.0 / (size_ - 1) * dx[i] * dx[i] * dx[i];
                }
                Q_[size_ - 1, size_ - 2] = 7.0 / 8 * 1.0 / (size_ - 1) * dx[size_ - 2] * dx[size_ - 2] * dx[size_ - 2];
                Q_[size_ - 1, size_ - 1] = 1.0 / (size_ - 1) * dx[size_ - 2] * dx[size_ - 2] * dx[size_ - 2];
                Matrix J_ = (I_ - V_ * Matrix.inverse(Matrix.transpose(V_) * Q_ * V_) * Matrix.transpose(V_) * Q_) * W_;
                Vector Y_ = new Vector(size_);
                for (int i = 0; i < size_; ++i)
                {
                    Y_[i] = this.yBegin_[i];
                }
                Vector D_ = J_ * Y_;
                for (int i = 0; i < size_ - 1; ++i)
                {
                    tmp[i] = (Y_[i + 1] - Y_[i]) / dx[i] - (2.0 * D_[i] + D_[i + 1]) * dx[i] / 6.0;
                }
                tmp[size_ - 1] = tmp[size_ - 2] + D_[size_ - 2] * dx[size_ - 2] + (D_[size_ - 1] - D_[size_ - 2]) * dx[size_ - 2] / 2.0;
            }
            else if (da_ == CubicInterpolation.DerivativeApprox.SplineOM2)
            {
                Matrix T_ = new Matrix(size_ - 2, size_, 0.0);
                for (int i = 0; i < size_ - 2; ++i)
                {
                    T_[i, i]     = dx[i] / 6.0;
                    T_[i, i + 1] = (dx[i] + dx[i + 1]) / 3.0;
                    T_[i, i + 2] = dx[i + 1] / 6.0;
                }
                Matrix S_ = new Matrix(size_ - 2, size_, 0.0);
                for (int i = 0; i < size_ - 2; ++i)
                {
                    S_[i, i]     = 1.0 / dx[i];
                    S_[i, i + 1] = -(1.0 / dx[i + 1] + 1.0 / dx[i]);
                    S_[i, i + 2] = 1.0 / dx[i + 1];
                }
                Matrix Up_ = new Matrix(size_, 2, 0.0);
                Up_[0, 0]         = 1;
                Up_[size_ - 1, 1] = 1;
                Matrix Us_ = new Matrix(size_, size_ - 2, 0.0);
                for (int i = 0; i < size_ - 2; ++i)
                {
                    Us_[i + 1, i] = 1;
                }
                Matrix Z_ = Us_ * Matrix.inverse(T_ * Us_);
                Matrix I_ = new Matrix(size_, size_, 0.0);
                for (int i = 0; i < size_; ++i)
                {
                    I_[i, i] = 1;
                }
                Matrix V_ = (I_ - Z_ * T_) * Up_;
                Matrix W_ = Z_ * S_;
                Matrix Q_ = new Matrix(size_, size_, 0.0);
                Q_[0, 0] = 1.0 / (size_ - 1) * dx[0];
                Q_[0, 1] = 1.0 / 2 * 1.0 / (size_ - 1) * dx[0];
                for (int i = 1; i < size_ - 1; ++i)
                {
                    Q_[i, i - 1] = 1.0 / 2 * 1.0 / (size_ - 1) * dx[i - 1];
                    Q_[i, i]     = 1.0 / (size_ - 1) * dx[i] + 1.0 / (size_ - 1) * dx[i - 1];
                    Q_[i, i + 1] = 1.0 / 2 * 1.0 / (size_ - 1) * dx[i];
                }
                Q_[size_ - 1, size_ - 2] = 1.0 / 2 * 1.0 / (size_ - 1) * dx[size_ - 2];
                Q_[size_ - 1, size_ - 1] = 1.0 / (size_ - 1) * dx[size_ - 2];
                Matrix J_ = (I_ - V_ * Matrix.inverse(Matrix.transpose(V_) * Q_ * V_) * Matrix.transpose(V_) * Q_) * W_;
                Vector Y_ = new Vector(size_);
                for (int i = 0; i < size_; ++i)
                {
                    Y_[i] = this.yBegin_[i];
                }
                Vector D_ = J_ * Y_;
                for (int i = 0; i < size_ - 1; ++i)
                {
                    tmp[i] = (Y_[i + 1] - Y_[i]) / dx[i] - (2.0 * D_[i] + D_[i + 1]) * dx[i] / 6.0;
                }
                tmp[size_ - 1] = tmp[size_ - 2] + D_[size_ - 2] * dx[size_ - 2] + (D_[size_ - 1] - D_[size_ - 2]) * dx[size_ - 2] / 2.0;
            }
            else
            {
                // local schemes
                if (size_ == 2)
                {
                    tmp[0] = tmp[1] = S[0];
                }
                else
                {
                    switch (da_)
                    {
                    case CubicInterpolation.DerivativeApprox.FourthOrder:
                        throw new NotImplementedException("FourthOrder not implemented yet");

                    case CubicInterpolation.DerivativeApprox.Parabolic:
                        // intermediate points
                        for (int i = 1; i < size_ - 1; ++i)
                        {
                            tmp[i] = (dx[i - 1] * S[i] + dx[i] * S[i - 1]) / (dx[i] + dx[i - 1]);
                        }
                        // end points
                        tmp[0]         = ((2.0 * dx[0] + dx[1]) * S[0] - dx[0] * S[1]) / (dx[0] + dx[1]);
                        tmp[size_ - 1] = ((2.0 * dx[size_ - 2] + dx[size_ - 3]) * S[size_ - 2] -
                                          dx[size_ - 2] * S[size_ - 3]) / (dx[size_ - 2] + dx[size_ - 3]);
                        break;

                    case CubicInterpolation.DerivativeApprox.FritschButland:
                        // intermediate points
                        for (int i = 1; i < size_ - 1; ++i)
                        {
                            double Smin = Math.Min(S[i - 1], S[i]);
                            double Smax = Math.Max(S[i - 1], S[i]);
                            tmp[i] = 3.0 * Smin * Smax / (Smax + 2.0 * Smin);
                        }
                        // end points
                        tmp[0]         = ((2.0 * dx[0] + dx[1]) * S[0] - dx[0] * S[1]) / (dx[0] + dx[1]);
                        tmp[size_ - 1] = ((2.0 * dx[size_ - 2] + dx[size_ - 3]) * S[size_ - 2] -
                                          dx[size_ - 2] * S[size_ - 3]) / (dx[size_ - 2] + dx[size_ - 3]);
                        break;

                    case CubicInterpolation.DerivativeApprox.Akima:
                        tmp[0] = (Math.Abs(S[1] - S[0]) * 2 * S[0] * S[1] +
                                  Math.Abs(2 * S[0] * S[1] - 4 * S[0] * S[0] * S[1]) * S[0]) /
                                 (Math.Abs(S[1] - S[0]) + Math.Abs(2 * S[0] * S[1] - 4 * S[0] * S[0] * S[1]));
                        tmp[1] = (Math.Abs(S[2] - S[1]) * S[0] + Math.Abs(S[0] - 2 * S[0] * S[1]) * S[1]) /
                                 (Math.Abs(S[2] - S[1]) + Math.Abs(S[0] - 2 * S[0] * S[1]));
                        for (int i = 2; i < size_ - 2; ++i)
                        {
                            if ((S[i - 2].IsEqual(S[i - 1])) && (S[i].IsNotEqual(S[i + 1])))
                            {
                                tmp[i] = S[i - 1];
                            }
                            else if ((S[i - 2].IsNotEqual(S[i - 1])) && (S[i].IsEqual(S[i + 1])))
                            {
                                tmp[i] = S[i];
                            }
                            else if (S[i].IsEqual(S[i - 1]))
                            {
                                tmp[i] = S[i];
                            }
                            else if ((S[i - 2].IsEqual(S[i - 1])) && (S[i - 1].IsNotEqual(S[i])) && (S[i].IsEqual(S[i + 1])))
                            {
                                tmp[i] = (S[i - 1] + S[i]) / 2.0;
                            }
                            else
                            {
                                tmp[i] = (Math.Abs(S[i + 1] - S[i]) * S[i - 1] + Math.Abs(S[i - 1] - S[i - 2]) * S[i]) /
                                         (Math.Abs(S[i + 1] - S[i]) + Math.Abs(S[i - 1] - S[i - 2]));
                            }
                        }
                        tmp[size_ - 2] = (Math.Abs(2 * S[size_ - 2] * S[size_ - 3] - S[size_ - 2]) * S[size_ - 3] +
                                          Math.Abs(S[size_ - 3] - S[size_ - 4]) * S[size_ - 2]) /
                                         (Math.Abs(2 * S[size_ - 2] * S[size_ - 3] - S[size_ - 2]) +
                                          Math.Abs(S[size_ - 3] - S[size_ - 4]));
                        tmp[size_ - 1] =
                            (Math.Abs(4 * S[size_ - 2] * S[size_ - 2] * S[size_ - 3] - 2 * S[size_ - 2] * S[size_ - 3]) *
                             S[size_ - 2] + Math.Abs(S[size_ - 2] - S[size_ - 3]) * 2 * S[size_ - 2] * S[size_ - 3]) /
                            (Math.Abs(4 * S[size_ - 2] * S[size_ - 2] * S[size_ - 3] - 2 * S[size_ - 2] * S[size_ - 3]) +
                             Math.Abs(S[size_ - 2] - S[size_ - 3]));
                        break;

                    case CubicInterpolation.DerivativeApprox.Kruger:
                        // intermediate points
                        for (int i = 1; i < size_ - 1; ++i)
                        {
                            if (S[i - 1] * S[i] < 0.0)
                            {
                                // slope changes sign at point
                                tmp[i] = 0.0;
                            }
                            else
                            {
                                // slope will be between the slopes of the adjacent
                                // straight lines and should approach zero if the
                                // slope of either line approaches zero
                                tmp[i] = 2.0 / (1.0 / S[i - 1] + 1.0 / S[i]);
                            }
                        }
                        // end points
                        tmp[0]         = (3.0 * S[0] - tmp[1]) / 2.0;
                        tmp[size_ - 1] = (3.0 * S[size_ - 2] - tmp[size_ - 2]) / 2.0;
                        break;

                    case CubicInterpolation.DerivativeApprox.Harmonic:
                        // intermediate points
                        for (int i = 1; i < size_ - 1; ++i)
                        {
                            double w1 = 2 * dx[i] + dx[i - 1];
                            double w2 = dx[i] + 2 * dx[i - 1];
                            if (S[i - 1] * S[i] <= 0.0)
                            {
                                // slope changes sign at point
                                tmp[i] = 0.0;
                            }
                            else
                            {
                                // weighted harmonic mean of S[i] and S[i-1] if they
                                // have the same sign; otherwise 0
                                tmp[i] = (w1 + w2) / (w1 / S[i - 1] + w2 / S[i]);
                            }
                        }
                        // end points [0]
                        tmp[0] = ((2 * dx[0] + dx[1]) * S[0] - dx[0] * S[1]) / (dx[1] + dx[0]);
                        if (tmp[0] * S[0] < 0.0)
                        {
                            tmp[0] = 0;
                        }
                        else if (S[0] * S[1] < 0)
                        {
                            if (Math.Abs(tmp[0]) > Math.Abs(3 * S[0]))
                            {
                                tmp[0] = 3 * S[0];
                            }
                        }
                        // end points [n-1]
                        tmp[size_ - 1] = ((2 * dx[size_ - 2] + dx[size_ - 3]) * S[size_ - 2] - dx[size_ - 2] * S[size_ - 3]) / (dx[size_ - 3] + dx[size_ - 2]);
                        if (tmp[size_ - 1] * S[size_ - 2] < 0.0)
                        {
                            tmp[size_ - 1] = 0;
                        }
                        else if (S[size_ - 2] * S[size_ - 3] < 0)
                        {
                            if (Math.Abs(tmp[size_ - 1]) > Math.Abs(3 * S[size_ - 2]))
                            {
                                tmp[size_ - 1] = 3 * S[size_ - 2];
                            }
                        }
                        break;

                    default:
                        throw new ArgumentException("unknown scheme");
                    }
                }
            }

            monotonicityAdjustments_.Erase();

            // Hyman monotonicity constrained filter
            if (monotonic_)
            {
                double correction;
                double pm, pu, pd, M;
                for (int i = 0; i < size_; ++i)
                {
                    if (i == 0)
                    {
                        if (tmp[i] * S[0] > 0.0)
                        {
                            correction = tmp[i] / Math.Abs(tmp[i]) *
                                         Math.Min(Math.Abs(tmp[i]),
                                                  Math.Abs(3.0 * S[0]));
                        }
                        else
                        {
                            correction = 0.0;
                        }
                        if (correction.IsNotEqual(tmp[i]))
                        {
                            tmp[i] = correction;
                            monotonicityAdjustments_[i] = true;
                        }
                    }
                    else if (i == size_ - 1)
                    {
                        if (tmp[i] * S[size_ - 2] > 0.0)
                        {
                            correction = tmp[i] / Math.Abs(tmp[i]) *
                                         Math.Min(Math.Abs(tmp[i]), Math.Abs(3.0 * S[size_ - 2]));
                        }
                        else
                        {
                            correction = 0.0;
                        }
                        if (correction.IsNotEqual(tmp[i]))
                        {
                            tmp[i] = correction;
                            monotonicityAdjustments_[i] = true;
                        }
                    }
                    else
                    {
                        pm = (S[i - 1] * dx[i] + S[i] * dx[i - 1]) /
                             (dx[i - 1] + dx[i]);
                        M = 3.0 * Math.Min(Math.Min(Math.Abs(S[i - 1]), Math.Abs(S[i])),
                                           Math.Abs(pm));
                        if (i > 1)
                        {
                            if ((S[i - 1] - S[i - 2]) * (S[i] - S[i - 1]) > 0.0)
                            {
                                pd = (S[i - 1] * (2.0 * dx[i - 1] + dx[i - 2])
                                      - S[i - 2] * dx[i - 1]) /
                                     (dx[i - 2] + dx[i - 1]);
                                if (pm * pd > 0.0 && pm * (S[i - 1] - S[i - 2]) > 0.0)
                                {
                                    M = Math.Max(M, 1.5 * Math.Min(
                                                     Math.Abs(pm), Math.Abs(pd)));
                                }
                            }
                        }
                        if (i < size_ - 2)
                        {
                            if ((S[i] - S[i - 1]) * (S[i + 1] - S[i]) > 0.0)
                            {
                                pu = (S[i] * (2.0 * dx[i] + dx[i + 1]) - S[i + 1] * dx[i]) /
                                     (dx[i] + dx[i + 1]);
                                if (pm * pu > 0.0 && -pm * (S[i] - S[i - 1]) > 0.0)
                                {
                                    M = Math.Max(M, 1.5 * Math.Min(
                                                     Math.Abs(pm), Math.Abs(pu)));
                                }
                            }
                        }
                        if (tmp[i] * pm > 0.0)
                        {
                            correction = tmp[i] / Math.Abs(tmp[i]) *
                                         Math.Min(Math.Abs(tmp[i]), M);
                        }
                        else
                        {
                            correction = 0.0;
                        }
                        if (correction.IsNotEqual(tmp[i]))
                        {
                            tmp[i] = correction;
                            monotonicityAdjustments_[i] = true;
                        }
                    }
                }
            }

            // cubic coefficients
            for (int i = 0; i < size_ - 1; ++i)
            {
                a_[i] = tmp[i];
                b_[i] = (3.0 * S[i] - tmp[i + 1] - 2.0 * tmp[i]) / dx[i];
                c_[i] = (tmp[i + 1] + tmp[i] - 2.0 * S[i]) / (dx[i] * dx[i]);
            }

            primitiveConst_[0] = 0.0;
            for (int i = 1; i < size_ - 1; ++i)
            {
                primitiveConst_[i] = primitiveConst_[i - 1]
                                     + dx[i - 1] *
                                     (yBegin_[i - 1] + dx[i - 1] *
                                      (a_[i - 1] / 2.0 + dx[i - 1] *
                                       (b_[i - 1] / 3.0 + dx[i - 1] * c_[i - 1] / 4.0)));
            }
        }